/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.enterprise.server.install.remote;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.common.composite.SystemSetting;
import org.rhq.core.domain.common.composite.SystemSettings;
import org.rhq.core.domain.install.remote.AgentInstall;
import org.rhq.core.domain.install.remote.AgentInstallInfo;
import org.rhq.core.domain.install.remote.AgentInstallStep;
import org.rhq.core.domain.install.remote.CustomAgentInstallData;
import org.rhq.core.domain.install.remote.RemoteAccessInfo;
import org.rhq.core.domain.install.remote.SSHSecurityException;
import org.rhq.core.util.file.FileUtil;
import org.rhq.enterprise.server.authz.RequiredPermission;
import org.rhq.enterprise.server.core.AgentManagerLocal;
import org.rhq.enterprise.server.system.SystemManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* Installs, starts and stops remote agents via SSH.
*
* @author Greg Hinkle
* @author John Mazzitelli
*/
@Stateless
public class RemoteInstallManagerBean implements RemoteInstallManagerLocal, RemoteInstallManagerRemote {
@EJB
private AgentManagerLocal agentManager;
@EJB
private SystemManagerLocal systemSettingsManager;
/**
* Call this when an SSH session is already connected and this will store the credentials if the user wanted
* them remembered. If the user did not want them remembers, the credentials will be nulled out from the backend data store.
*
* If the session is not connected, nothing will be remembered - this method will just end up as a no-op in that case.
*
* @param subject user making the request
* @param sshSession the session that is currently connected
*/
private void processRememberMe(Subject subject, SSHInstallUtility sshSession) {
RemoteAccessInfo remoteAccessInfo = sshSession.getRemoteAccessInfo();
String agentName = remoteAccessInfo.getAgentName();
if (agentName == null) {
return; // nothing we can do, don't know what agent this is for
}
boolean credentialsOK = sshSession.isConnected();
if (!credentialsOK) {
return; // do not store anything - the credentials are probably bad and why we aren't connected so no sense remembering them
}
AgentInstall ai = agentManager.getAgentInstallByAgentName(subject, agentName);
if (ai == null) {
ai = new AgentInstall();
ai.setAgentName(agentName);
}
// ai.setSshHost(remoteAccessInfo.getHost()); do NOT change the host
ai.setSshPort(remoteAccessInfo.getPort());
if (remoteAccessInfo.getRememberMe()) {
ai.setSshUsername(remoteAccessInfo.getUser());
ai.setSshPassword(remoteAccessInfo.getPassword());
} else {
// user doesn't want to remember the creds, set them to "" which tells our persistence layer to null them out
ai.setSshUsername("");
ai.setSshPassword("");
}
try {
agentManager.updateAgentInstall(subject, ai);
} catch (Exception e) {
// TODO: I don't think we want to abort this - we don't technically need the install info persisted, user can manually give it again
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void checkSSHConnection(Subject subject, RemoteAccessInfo remoteAccessInfo) throws SSHSecurityException {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
if (!sshUtil.isConnected()) {
throw new IllegalStateException("Is not connected to [" + remoteAccessInfo.getHost() + ":"
+ remoteAccessInfo.getPort() + "]");
}
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public boolean agentInstallCheck(Subject subject, RemoteAccessInfo remoteAccessInfo, String agentInstallPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
boolean results = sshUtil.agentInstallCheck(agentInstallPath);
// don't bother remembering the info if the agent isn't even installed
if (results) {
processRememberMe(subject, sshUtil);
}
return results;
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public AgentInstallInfo installAgent(Subject subject, RemoteAccessInfo remoteAccessInfo, String parentPath) {
CustomAgentInstallData data = new CustomAgentInstallData(parentPath, false);
return installAgent(subject, remoteAccessInfo, data);
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public AgentInstallInfo installAgent(Subject subject, RemoteAccessInfo remoteAccessInfo,
CustomAgentInstallData customData) {
try {
String parentPath = customData.getParentPath();
boolean agentAlreadyInstalled = agentInstallCheck(subject, remoteAccessInfo, parentPath);
if (agentAlreadyInstalled) {
if (!customData.isOverwriteExistingAgent()) {
throw new IllegalStateException("Agent appears to already be installed under: " + parentPath);
} else {
// if the caller passed in the actual install directory and not the parent directory
// (e.g. if "parentPath" is "/opt/rhq/rhq-agent" instead of "/opt/rhq") we don't want to install
// in the path given to us ("/opt/rhq/rhq-agent"); we want to use the parent path ("/opt/rhq").
// The directory passed into us must be the parent directory (hence the name parentPath),
// but we don't want to have to thrown an error and ask the user to give us the parent - if we know
// this is where an install is, its obvious this is where the user wants to install the new agent.
// So let's just do what they really want and use the parent directory, stripping the child
// "rhq-agent" directory name for them.
// Here we want to make sure that's what happened first and if so we need to strip off the
// child path to get the parent path.
if (parentPath.endsWith("/rhq-agent")) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String results = sshUtil.findAgentInstallPath(parentPath);
if (parentPath.equals(results)) {
// yup, there is an agent installed in the exact "parentPath" - so its not really
// the parent path; let's strip the child "rhq-agent" dir name and get the real parent path
parentPath = parentPath.substring(0, parentPath.lastIndexOf("/rhq-agent"));
customData.setParentPath(parentPath);
}
} finally {
sshUtil.disconnect();
}
}
// we were asked to overwrite it; make sure we shut it down first before the install happens (which will remove it)
stopAgent(subject, remoteAccessInfo, parentPath);
}
}
// we know the uploaded files had to have their contents obfuscated, we need to deobfuscate them
if (customData.getAgentConfigurationXmlFile() != null) {
deobfuscateFile(new File(customData.getAgentConfigurationXmlFile()));
}
if (customData.getRhqAgentEnvFile() != null) {
deobfuscateFile(new File(customData.getRhqAgentEnvFile()));
}
// before we install, let's create a AgentInstall and pass its ID
// as the install ID so the agent can link up with it when it registers.
AgentInstall agentInstall = new AgentInstall();
agentInstall.setSshHost(remoteAccessInfo.getHost());
agentInstall.setSshPort(remoteAccessInfo.getPort());
if (remoteAccessInfo.getRememberMe()) {
agentInstall.setSshUsername(remoteAccessInfo.getUser());
agentInstall.setSshPassword(remoteAccessInfo.getPassword());
}
AgentInstall ai = agentManager.updateAgentInstall(subject, agentInstall);
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
AgentInstallInfo info = sshUtil.installAgent(customData, String.valueOf(ai.getId()));
List<AgentInstallStep> steps = info.getSteps();
AgentInstallStep lastInstallStep = steps.get(steps.size() - 1);
// At the moment, SSHInstallUtility might throw RuntimeException as well if it fails. Lets unify this for now.
if(lastInstallStep.getResultCode() != 0) {
throw new RuntimeException(lastInstallStep.getDescription() + " failed, " + lastInstallStep.getResult());
}
return info;
} finally {
sshUtil.disconnect();
}
} finally {
// don't leave these around - whether we succeeded or failed, its a one-time-chance with these.
// we want to delete them in case they have some sensitive info
if (customData.getAgentConfigurationXmlFile() != null) {
new File(customData.getAgentConfigurationXmlFile()).delete();
}
if (customData.getRhqAgentEnvFile() != null) {
new File(customData.getRhqAgentEnvFile()).delete();
}
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String uninstallAgent(Subject subject, RemoteAccessInfo remoteAccessInfo, String agentInstallPath) {
String agentName = remoteAccessInfo.getAgentName();
if(agentName != null) {
AgentInstall ai = agentManager.getAgentInstallByAgentName(subject, agentName);
if (ai == null || ai.getInstallLocation() == null || ai.getInstallLocation().trim().length() == 0) {
throw new IllegalArgumentException("Agent [" + agentName
+ "] does not have a known install location. For security purposes, the uninstall will not be allowed."
+ " You will have to manually uninstall it from that machine.");
}
// for security reasons, don't connect to a different machine than where the AgentInstall thinks the agent is.
// If there is no known host in AgentInstall, then we accept the caller's hostname.
if (ai.getSshHost() != null && !ai.getSshHost().equals(remoteAccessInfo.getHost())) {
throw new IllegalArgumentException("Agent [" + agentName + "] is not known to be on host ["
+ remoteAccessInfo.getHost() + "] - aborting uninstall");
}
}
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
return sshUtil.uninstallAgent(agentInstallPath);
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String startAgent(Subject subject, RemoteAccessInfo remoteAccessInfo, String agentInstallPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String results = sshUtil.startAgent(agentInstallPath);
processRememberMe(subject, sshUtil);
return results;
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String stopAgent(Subject subject, RemoteAccessInfo remoteAccessInfo, String agentInstallPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String results = sshUtil.stopAgent(agentInstallPath);
processRememberMe(subject, sshUtil);
return results;
} finally {
sshUtil.disconnect();
}
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String agentStatus(Subject subject, RemoteAccessInfo remoteAccessInfo, String agentInstallPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String results = sshUtil.agentStatus(agentInstallPath);
// do not update the install information if the agent isn't even installed
if (!SSHInstallUtility.AGENT_STATUS_NOT_INSTALLED.equals(results)) {
processRememberMe(subject, sshUtil);
}
return results;
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String findAgentInstallPath(Subject subject, RemoteAccessInfo remoteAccessInfo, String parentPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String results = sshUtil.findAgentInstallPath(parentPath);
processRememberMe(subject, sshUtil);
return results;
} finally {
sshUtil.disconnect();
}
}
@RequiredPermission(Permission.MANAGE_INVENTORY)
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public String[] remotePathDiscover(Subject subject, RemoteAccessInfo remoteAccessInfo, String parentPath) {
SSHInstallUtility sshUtil = getSSHConnection(remoteAccessInfo);
try {
String[] results = sshUtil.pathDiscovery(parentPath);
processRememberMe(subject, sshUtil);
return results;
} finally {
sshUtil.disconnect();
}
}
private SSHInstallUtility getSSHConnection(RemoteAccessInfo remoteAccessInfo) {
if (remoteAccessInfo.getHost() == null) {
throw new RuntimeException("Enter a host");
}
SystemSettings settings = systemSettingsManager.getUnmaskedSystemSettings(false);
String username = settings.get(SystemSetting.REMOTE_SSH_USERNAME_DEFAULT);
String password = settings.get(SystemSetting.REMOTE_SSH_PASSWORD_DEFAULT);
SSHInstallUtility.Credentials creds = null;
if ((username != null && username.length() > 0) || (password != null && password.length() > 0)) {
creds = new SSHInstallUtility.Credentials(username, password);
}
SSHInstallUtility.SSHConfiguration sshConfig = new SSHInstallUtility.SSHConfiguration();
File dataDir = LookupUtil.getCoreServer().getJBossServerDataDir();
File knownHosts = new File(dataDir, "rhq_known_hosts");
try {
knownHosts.createNewFile(); // make sure it exists - this creates an empty one if there isn't one yet
} catch (IOException e) {
throw new RuntimeException("Cannot create a known_hosts file for SSH communication - aborting");
}
sshConfig.setKnownHostsFile(knownHosts.getAbsolutePath());
SSHInstallUtility sshUtil = new SSHInstallUtility(remoteAccessInfo, creds, sshConfig);
return sshUtil;
}
private void deobfuscateFile(File f) {
if (!f.exists()) {
throw new RuntimeException("Uploaded file has been purged and no longer available: " + f);
}
try {
FileUtil.decompressFile(f); // we really just compressed it with our special compressor since its faster than obsfucation
} catch (Exception e) {
throw new RuntimeException("Cannot unobfuscate uploaded file [" + f + "]", e);
}
}
}