diff --git a/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/RemoteAccessInfo.java b/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/RemoteAccessInfo.java index 384a2468dda..7f240016254 100644 --- a/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/RemoteAccessInfo.java +++ b/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/RemoteAccessInfo.java @@ -33,6 +33,7 @@ public class RemoteAccessInfo implements Serializable { private int port = 22; private String agentName; private boolean rememberMe; + private boolean hostAuthorized = false; public RemoteAccessInfo(String host, String user, byte[] key) { this.host = host; @@ -120,4 +121,21 @@ public boolean getRememberMe() { public void setRememberMe(boolean rememberMe) { this.rememberMe = rememberMe; } + + /** + * If the host to be connected to is not known, but it is authorized, this will be true. + * Otherwise, an unknown host (that is, a host with an unknown SSH key fingerprint) will fail to be connected to. + * + * If the host is already known, but the known fingerprint is different than the real fingerprint, then + * this must be true in order to connect to it. Otherwise, the connection attempt will fail. + * + * @return flag to indicate if the host is authorized + */ + public boolean isHostAuthorized() { + return hostAuthorized; + } + + public void setHostAuthorized(boolean hostAuthorized) { + this.hostAuthorized = hostAuthorized; + } } diff --git a/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/SSHAccessException.java b/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/SSHAccessException.java new file mode 100644 index 00000000000..c5177cc9e9b --- /dev/null +++ b/modules/core/domain/src/main/java/org/rhq/core/domain/install/remote/SSHAccessException.java @@ -0,0 +1,32 @@ +/* + * RHQ Management Platform + * Copyright (C) 2005-2008 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 as published by + * the Free Software Foundation version 2 of the License. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package org.rhq.core.domain.install.remote; + +/** + * Indicates if things like the SSH handshake is missing data (e.g. a password or passphrase). + * + * @author John Mazzitelli + */ +public class SSHAccessException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public SSHAccessException(String message) { + super(message); + } +} diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/server/gwt/RemoteInstallGWTServiceImpl.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/server/gwt/RemoteInstallGWTServiceImpl.java index 7b44a0e493d..3abcf7be1a4 100644 --- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/server/gwt/RemoteInstallGWTServiceImpl.java +++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/coregui/server/gwt/RemoteInstallGWTServiceImpl.java @@ -22,6 +22,8 @@ */ package org.rhq.coregui.server.gwt; +import javax.ejb.EJBException; + import org.rhq.core.domain.install.remote.AgentInstallInfo; import org.rhq.core.domain.install.remote.CustomAgentInstallData; import org.rhq.core.domain.install.remote.RemoteAccessInfo; @@ -112,4 +114,18 @@ public String[] remotePathDiscover(RemoteAccessInfo remoteAccessInfo, String par throw getExceptionToThrowToClient(t); } } + + @Override + protected RuntimeException getExceptionToThrowToClient(Throwable t) throws RuntimeException { + // if the SSH connection failed because of a bad or missing SSH key fingerprint, a SecurityException will be thrown. + // We want that SecurityException sent back as-is to the GWT UI. + if (t instanceof SecurityException) { + return (SecurityException) t; + } else if (t instanceof EJBException) { + if (t.getCause() instanceof SecurityException) { + return (SecurityException) t.getCause(); + } + } + return getExceptionToThrowToClient(t); + } } diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/RemoteInstallManagerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/RemoteInstallManagerBean.java index 4e05d254ee5..3bc7105f230 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/RemoteInstallManagerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/RemoteInstallManagerBean.java @@ -23,6 +23,7 @@ package org.rhq.enterprise.server.install.remote; import java.io.File; +import java.io.IOException; import javax.ejb.EJB; import javax.ejb.Stateless; @@ -41,6 +42,7 @@ 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. @@ -312,7 +314,18 @@ private SSHInstallUtility getSSHConnection(RemoteAccessInfo remoteAccessInfo) { creds = new SSHInstallUtility.Credentials(username, password); } - SSHInstallUtility sshUtil = new SSHInstallUtility(remoteAccessInfo, creds); + 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; } diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/SSHInstallUtility.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/SSHInstallUtility.java index 6e6d5f82006..d55fd3238bc 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/SSHInstallUtility.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/install/remote/SSHInstallUtility.java @@ -35,6 +35,7 @@ import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; +import com.jcraft.jsch.UserInfo; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -77,6 +78,76 @@ public void setPassword(String p) { } } + static class SSHConfiguration { + public static enum StrictHostKeyChecking { + yes, no, ask + }; + + private StrictHostKeyChecking strictHostKeyChecking = StrictHostKeyChecking.ask; + private String knownHostsFile = null; + + public SSHConfiguration() { + } + + public StrictHostKeyChecking getStrictHostKeyChecking() { + return strictHostKeyChecking; + } + + public void setStrictHostKeyChecking(StrictHostKeyChecking strictHostKeyChecking) { + this.strictHostKeyChecking = strictHostKeyChecking; + } + + public String getKnownHostsFile() { + return knownHostsFile; + } + + public void setKnownHostsFile(String knownHostsFile) { + this.knownHostsFile = knownHostsFile; + } + } + + private class SSHUserInfo implements UserInfo { + + @Override + public void showMessage(String msg) { + //System.out.println(msg); + } + + @Override + public boolean promptYesNo(String ques) { + // this is asking either to add the fingerprint for an unknown host or, more troubling, to replace + // a known host's fingerprint. If we were told to authorize this host, then accept both. + // If we need to do separate processing for either conditions, we can see which question it is via: + // ques.matches("(?s).*authenticity of host.*can't be established.*Are you sure you want to continue connecting.*") + // and + // ques.matches("(?s).*NASTY.*") + if (accessInfo.isHostAuthorized()) { + return true; + } + throw new SecurityException(ques); + } + + @Override + public boolean promptPassword(String arg0) { + return false; + } + + @Override + public boolean promptPassphrase(String arg0) { + return false; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getPassphrase() { + return null; + } + }; + public static final String AGENT_STATUS_NOT_INSTALLED = "Agent Not Installed"; private static final String RHQ_AGENT_LATEST_VERSION_PROP = "rhq-agent.latest.version"; @@ -89,17 +160,24 @@ public void setPassword(String p) { private final RemoteAccessInfo accessInfo; private final Credentials defaultCredentials; + private final SSHConfiguration sshConfiguration; private Session session; - public SSHInstallUtility(RemoteAccessInfo accessInfo, Credentials defaultCredentials) { + public SSHInstallUtility(RemoteAccessInfo accessInfo, Credentials defaultCredentials, SSHConfiguration sshConfig) { this.accessInfo = accessInfo; this.defaultCredentials = defaultCredentials; + + if (sshConfig == null) { + sshConfig = new SSHConfiguration(); + } + this.sshConfiguration = sshConfig; + connect(); } public SSHInstallUtility(RemoteAccessInfo accessInfo) { - this(accessInfo, null); + this(accessInfo, null, null); } public RemoteAccessInfo getRemoteAccessInfo() { @@ -110,6 +188,10 @@ public void connect() { try { JSch jsch = new JSch(); + if (sshConfiguration.getKnownHostsFile() != null) { + jsch.setKnownHosts(sshConfiguration.getKnownHostsFile()); + } + //if (accessInfo.getKey() != null) { // jsch.addIdentity(...); //} @@ -122,9 +204,13 @@ public void connect() { session.setPassword(credentials.getPassword()); } - Properties config = new Properties(); - config.put("StrictHostKeyChecking", "no"); - session.setConfig(config); + if (sshConfiguration.getStrictHostKeyChecking() != null) { + Properties config = new Properties(); + config.put("StrictHostKeyChecking", sshConfiguration.getStrictHostKeyChecking().name()); + session.setConfig(config); + } + + session.setUserInfo(new SSHUserInfo()); session.connect(CONNECTION_TIMEOUT); // making a connection with timeout. } catch (JSchException e) {