All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.elastic.support.util.RemoteSystem Maven / Gradle / Ivy

/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 *  or more contributor license agreements. Licensed under the Elastic License
 *  2.0; you may not use this file except in compliance with the Elastic License
 *  2.0.
 */
package co.elastic.support.util;

import co.elastic.support.Constants;
import co.elastic.support.diagnostics.DiagnosticException;
import com.jcraft.jsch.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class RemoteSystem extends SystemCommand {

    public static final String sudoPrefix = "echo '{{PASSWORD}}' | sudo -S -p '' ";
    public static final String sudoNoPasswordPrefix = "sudo -S -p '' ";

    private static final Logger logger = LogManager.getLogger(RemoteSystem.class);

    private     boolean disabledForPlatform = false;
    private     Session session;
    private     String sudo = "";

    public RemoteSystem(String osName,
                        String remoteUser,
                        String remotePassword,
                        String host,
                        int port,
                        String keyFile,
                        String keyFilePass,
                        String knownHostsFile,
                        boolean trustRemote,
                        boolean isSudo) throws DiagnosticException {

        this.osName = osName;

        try {
            if(osName.equals(Constants.winPlatform)){
                logger.info(Constants.CONSOLE, "Windows is not supported for remote calls at this time. Session not created.");
                disabledForPlatform = true;
                return;
            }

            JSch jsch; jsch = new JSch();
            if(StringUtils.isNotEmpty(keyFile)){
                jsch.addIdentity(keyFile);
                if(StringUtils.isEmpty(keyFilePass)){
                    keyFilePass = null;
                }
            }
            if(StringUtils.isNotEmpty(remotePassword)){
                if(isSudo){
                    sudo = sudoPrefix.replace("{{PASSWORD}}", remotePassword);
                }
            }
            else{
                remotePassword = null;
                if(isSudo){
                    sudo = sudoNoPasswordPrefix;
                }
            }

            if(StringUtils.isNotEmpty(knownHostsFile)){
                jsch.setKnownHosts(knownHostsFile);
            }

            String hostKeyChecking = "yes";
            if (trustRemote) {
                hostKeyChecking = "no";
            }

            UserInfo userInfo = new RemoteUserInfo(remoteUser, remotePassword, keyFilePass);
            session = jsch.getSession(remoteUser, host, port);
            final Hashtable config = new Hashtable();

            config.put("StrictHostKeyChecking", hostKeyChecking);
            config.put("PreferredAuthentications",
                    "publickey,keyboard-interactive,password");

            session.setConfig(config);
            session.setUserInfo(userInfo);
            session.setServerAliveCountMax(3);
            session.setServerAliveInterval(10000);
            session.connect();

        } catch (JSchException e) {
            throw new DiagnosticException("Error obtaining session for remote server - try running with diagnostic type: api.");
        }
    }

    public String runCommand(String cmd) {
        if(disabledForPlatform){
            logger.info(Constants.CONSOLE, "Windows is not supported for remote calls at this time");
            return "No Content available: Incompatible platform.";
        }

        // If it's sudo we need to pipe the password into the input stream
        // Only there if if's been set, otherwise just prepending an empty string
        cmd = sudo + cmd;

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final ByteArrayOutputStream errout = new ByteArrayOutputStream();
        StringBuffer sb = new StringBuffer();

        InputStream istream = null;
        ChannelExec channel = null;
        try {
            session.setTimeout(0);
            channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(cmd);
            channel.setInputStream(null);
            channel.setPty(true);
            istream = channel.getInputStream();
            channel.connect();
            byte[] tmp=new byte[1024];
            while(true){
                while(istream.available()>0){
                    int i=istream.read(tmp, 0, 1024);
                    if(i<0)break;
                    sb.append(new String(tmp, 0, i));
                }
                if(channel.isClosed()){
                    if(istream.available()>0) continue;
                    logger.error( "Cmd: {}, Exit status: {}", scrubCommandText(cmd), channel.getExitStatus());
                    break;
                }
                try{
                    TimeUnit.SECONDS.sleep(1);}catch(Exception ee){}
            }
        }
        catch(Exception e){
            logger.info(Constants.CONSOLE, "Failed remote command: {}. {}", cmd, Constants.CHECK_LOG) ;
            logger.error("System command failed.", e);
        }
        finally {
            if (channel!= null && channel.isConnected()) {
                channel.disconnect();
            }
        }

        return sb.toString();
    }

    @Override
    public void copyLogs(List entries, String logDir, String targetDir) {

        ChannelSftp channelSftp = null;
        String tempDir = "templogs";
        String mkdir = "mkdir templogs";
        String copy = " cp '{{LOGPATH}}/{{FILENAME}}'  '{{TEMP}}/{{FILENAME}}'";
        copy = copy.replace("{{LOGPATH}}", logDir);
        copy = copy.replace("{{TEMP}}", tempDir);
        // Because logs are only available to the elasticsearch user, other accounts with access,
        // or sudo, we need to copy them into the home directory of the account being used. If sudo was
        // specified or the appropriate account level it will work. If not, we would have gotten an empty
        // file listing and exited by now.
        try {
            runCommand(mkdir);
            for(String filename : entries){
                String cmd = copy.replace("{{FILENAME}}", filename);
                runCommand(cmd);
            }

            // Now they have been moved from the log directory into the temp dir created in the user's home
            // we can move them to the the calling host. It should pull them all in one pass.
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            channelSftp.get("templogs/*", targetDir);
            channelSftp.exit();

            // clean up the temp logs on the remote host
            runCommand(" rm -Rf templogs");

        } catch (Exception e) {
            logger.info(Constants.CONSOLE, "Error occurred copying remote logfiles. {}", Constants.CHECK_LOG);
            logger.error( e);
        } finally {
            if(channelSftp != null && channelSftp.isConnected()){
                channelSftp.disconnect();
            }
        }
    }

   /**
    * Some services as Kibana installed with the RPM package will give the access to the logs using the journalctl command
    * We will create a temperory file to copy those logs, then move them to the calling host.
    *
    * @param  serviceName service name defined by RPM
    * @param  targetDir temporary path where the data need to be stored
    */
    @Override
    public void copyLogsFromJournalctl(String serviceName, String targetDir) {

        ChannelSftp channelSftp = null;
        String tempDir = "templogs";
        String mkdir = "mkdir templogs";
        String journalctl = "journalctl -u {{SERVICE}} > '{{TEMP}}/{{SERVICE}}.log'";
        journalctl = journalctl.replace("{{SERVICE}}", serviceName);
        journalctl = journalctl.replace("{{TEMP}}", tempDir);
       
        try {
            runCommand(mkdir);
            runCommand(journalctl);

            // Now they have been moved from the log directory into the temp dir created in the user's home
            // we can move them to the the calling host. It should pull them all in one pass.
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            channelSftp.get("templogs/*", targetDir);
            channelSftp.exit();

            // clean up the temp logs on the remote host
            runCommand("rm -Rf templogs");
        } catch (Exception e) {
            logger.info(Constants.CONSOLE, "Error occurred copying remote logfiles. {}", Constants.CHECK_LOG);
            logger.error( e);
        } finally {
            if(channelSftp != null && channelSftp.isConnected()){
                channelSftp.disconnect();
            }
        }
    }


    private String scrubCommandText(String command){
        // If we had to pipe in suoo information with the command remove it before
        // displaying the command status.
        if(command.startsWith("echo") && command.contains("sudo")) {
            int idx = command.indexOf("-p") + 6;
            return command.substring(idx);
        }
        return command;

    }

    @Override
    public void close() throws IOException {
        if(session != null && session.isConnected()){
            session.disconnect();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy