From a3a32744665643f645949c7b504c0dfe768d3325 Mon Sep 17 00:00:00 2001 From: Shireesh Anjal Date: Mon, 9 May 2011 15:20:04 +0530 Subject: Introducing SshUtil for executing commands on remote machines using ssh --- .../management/core/constants/CoreConstants.java | 1 + .../storage/management/core/utils/LRUCache.java | 55 +++++ .../management/core/utils/ProcessResult.java | 1 + .../storage/management/gui/utils/ImageUtil.java | 1 + .../storage/management/gui/utils/LRUCache.java | 55 ----- .../WEB-INF/lib/ganymed-ssh2-build250-LICENSE.txt | 87 +++++++ .../WEB-INF/lib/ganymed-ssh2-build250.jar | Bin 0 -> 248915 bytes .../storage/management/server/utils/SshUtil.java | 268 +++++++++++++++++++++ 8 files changed, 413 insertions(+), 55 deletions(-) create mode 100644 src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/LRUCache.java delete mode 100644 src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/LRUCache.java create mode 100644 src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250-LICENSE.txt create mode 100644 src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250.jar create mode 100644 src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/SshUtil.java (limited to 'src') diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java index 8a1e1f37..b5e25ce7 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/constants/CoreConstants.java @@ -25,6 +25,7 @@ package com.gluster.storage.management.core.constants; public class CoreConstants { public static final String NEWLINE = System.getProperty("line.separator"); public static final String FILE_SEPARATOR = System.getProperty("file.separator"); + public static final String USER_HOME = System.getProperty("user.home"); public static final String ENCODING_UTF8 = "UTF-8"; public static final String ALL = "ALL"; public static final String DATE_WITH_TIME_FORMAT = "MM/dd/yyyy HH:mm:ss"; diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/LRUCache.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/LRUCache.java new file mode 100644 index 00000000..f3c9c72d --- /dev/null +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/LRUCache.java @@ -0,0 +1,55 @@ +/** + * LRUCache.java + * + * Copyright (c) 2011 Gluster, Inc. + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * . + */ +package com.gluster.storage.management.core.utils; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An LRU cache, based on LinkedHashMap. + *

+ * This cache has a fixed maximum number of elements (cacheSize). If the cache is full and another entry is + * added, the LRU (least recently used) entry is dropped. + * + */ +public class LRUCache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private static final float loadFactor = 0.75f; + private int cacheSize; + + /** + * Creates a new LRU cache. + * + * @param cacheSize + * the maximum number of entries that will be kept in this cache. + */ + public LRUCache(int cacheSize) { + super((int) Math.ceil(cacheSize / loadFactor) + 1, loadFactor, true); + this.cacheSize = cacheSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > cacheSize; + } + +} diff --git a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ProcessResult.java b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ProcessResult.java index 6c8b857d..9d6ddc93 100644 --- a/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ProcessResult.java +++ b/src/com.gluster.storage.management.core/src/com/gluster/storage/management/core/utils/ProcessResult.java @@ -28,6 +28,7 @@ import javax.xml.bind.annotation.XmlRootElement; public class ProcessResult { public static final int SUCCESS = 0; + public static final int FAILURE = 1; private int exitValue; private String output; diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/ImageUtil.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/ImageUtil.java index efa169de..d5e568b3 100644 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/ImageUtil.java +++ b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/ImageUtil.java @@ -24,6 +24,7 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.plugin.AbstractUIPlugin; +import com.gluster.storage.management.core.utils.LRUCache; import com.gluster.storage.management.gui.Application; /** diff --git a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/LRUCache.java b/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/LRUCache.java deleted file mode 100644 index 3c805ac8..00000000 --- a/src/com.gluster.storage.management.gui/src/com/gluster/storage/management/gui/utils/LRUCache.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * LRUCache.java - * - * Copyright (c) 2011 Gluster, Inc. - * This file is part of Gluster Management Console. - * - * Gluster Management Console is free software; you can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * Gluster Management Console 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 Affero General Public License - * for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see - * . - */ -package com.gluster.storage.management.gui.utils; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * An LRU cache, based on LinkedHashMap. - *

- * This cache has a fixed maximum number of elements (cacheSize). If the cache is full and another entry is - * added, the LRU (least recently used) entry is dropped. - * - */ -public class LRUCache extends LinkedHashMap { - - private static final long serialVersionUID = 1L; - private static final float loadFactor = 0.75f; - private int cacheSize; - - /** - * Creates a new LRU cache. - * - * @param cacheSize - * the maximum number of entries that will be kept in this cache. - */ - public LRUCache(int cacheSize) { - super((int) Math.ceil(cacheSize / loadFactor) + 1, loadFactor, true); - this.cacheSize = cacheSize; - } - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > cacheSize; - } - -} diff --git a/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250-LICENSE.txt b/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250-LICENSE.txt new file mode 100644 index 00000000..3eddd42f --- /dev/null +++ b/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250-LICENSE.txt @@ -0,0 +1,87 @@ +Copyright (c) 2006 - 2010 Christian Plattner. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +a.) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +b.) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +c.) Neither the name of Christian Plattner nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +This software includes work that was released under the following license: + +Copyright (c) 2005 - 2006 Swiss Federal Institute of Technology (ETH Zurich), + Department of Computer Science (http://www.inf.ethz.ch), + Christian Plattner. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +a.) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +b.) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +c.) Neither the name of ETH Zurich nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +The Java implementations of the AES, Blowfish and 3DES ciphers have been +taken (and slightly modified) from the cryptography package released by +"The Legion Of The Bouncy Castle". + +Their license states the following: + +Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle +(http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250.jar b/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250.jar new file mode 100644 index 00000000..c0a9ac7b Binary files /dev/null and b/src/com.gluster.storage.management.server/WebContent/WEB-INF/lib/ganymed-ssh2-build250.jar differ diff --git a/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/SshUtil.java b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/SshUtil.java new file mode 100644 index 00000000..258bdea3 --- /dev/null +++ b/src/com.gluster.storage.management.server/src/com/gluster/storage/management/server/utils/SshUtil.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2011 Gluster, Inc. + * This file is part of Gluster Management Console. + * + * Gluster Management Console is free software; you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * Gluster Management Console 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 Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + * . + *******************************************************************************/ +package com.gluster.storage.management.server.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.Date; + +import ch.ethz.ssh2.ChannelCondition; +import ch.ethz.ssh2.Connection; +import ch.ethz.ssh2.Session; +import ch.ethz.ssh2.StreamGobbler; + +import com.gluster.storage.management.core.constants.CoreConstants; +import com.gluster.storage.management.core.exceptions.GlusterRuntimeException; +import com.gluster.storage.management.core.utils.LRUCache; +import com.gluster.storage.management.core.utils.ProcessResult; + +/** + * + */ +public class SshUtil { + private LRUCache sshConnCache = new LRUCache(10); + private static final File PEM_FILE = new File(CoreConstants.USER_HOME + "/" + ".ssh/id_rsa"); + // TODO: Make user name configurable + private static final String USER_NAME = "root"; + // TODO: Make default password configurable + private static final String DEFAULT_PASSWORD = "syst3m"; + + private Connection getConnectionWithPassword(String serverName) { + Connection conn = createConnection(serverName); + authenticateWithPassword(conn); + return conn; + } + + private synchronized Connection getConnection(String serverName) { + Connection conn = sshConnCache.get(serverName); + if (conn != null) { + return conn; + } + + conn = createConnection(serverName); + authenticateWithPublicKey(conn); + sshConnCache.put(serverName, conn); + return conn; + } + + private void authenticateWithPublicKey(Connection conn) { + try { + if (!supportsPublicKeyAuthentication(conn)) { + throw new GlusterRuntimeException("Public key authentication not supported on [" + conn.getHostname() + + "]"); + } + + // TODO: Introduce password for the PEM file (third argument) so that it is more secure + if (!conn.authenticateWithPublicKey(USER_NAME, PEM_FILE, null)) { + throw new GlusterRuntimeException("SSH Authentication (public key) failed for server [" + + conn.getHostname() + "]"); + } + } catch (IOException e) { + e.printStackTrace(); + throw new GlusterRuntimeException("Exception during SSH authentication (public key) for server [" + + conn.getHostname() + "]", e); + } + } + + private void authenticateWithPassword(Connection conn) { + try { + if (!supportsPasswordAuthentication(conn)) { + throw new GlusterRuntimeException("Password authentication not supported on [" + conn.getHostname() + + "]"); + } + + // TODO: Introduce password for the PEM file (third argument) so that it is more secure + if (!conn.authenticateWithPassword(USER_NAME, DEFAULT_PASSWORD)) { + throw new GlusterRuntimeException("SSH Authentication (password) failed for server [" + + conn.getHostname() + "]"); + } + } catch (IOException e) { + e.printStackTrace(); + throw new GlusterRuntimeException("Exception during SSH authentication (password) for server [" + + conn.getHostname() + "]", e); + } + } + + private boolean supportsPasswordAuthentication(Connection conn) throws IOException { + return Arrays.asList(conn.getRemainingAuthMethods(USER_NAME)).contains("password"); + } + + private boolean supportsPublicKeyAuthentication(Connection conn) throws IOException { + return Arrays.asList(conn.getRemainingAuthMethods(USER_NAME)).contains("publickey"); + } + + private Connection createConnection(String serverName) { + Connection conn; + conn = new Connection(serverName); + try { + conn.connect(); + } catch (IOException e) { + e.printStackTrace(); + throw new GlusterRuntimeException("Exception while creating SSH connection with server [" + serverName + + "]", e); + } + return conn; + } + + private boolean wasTerminated(int condition) { + return ((condition | ChannelCondition.EXIT_SIGNAL) == condition); + } + + private boolean hasErrors(int condition, Session session) { + return (hasErrorStream(condition) || (exitedGracefully(condition) && exitedWithError(session))); + } + + private boolean exitedWithError(Session session) { + return session.getExitStatus() != ProcessResult.SUCCESS; + } + + private boolean exitedGracefully(int condition) { + return (condition | ChannelCondition.EXIT_STATUS) == condition; + } + + private boolean hasErrorStream(int condition) { + return (condition | ChannelCondition.STDERR_DATA) == condition; + } + + private ProcessResult executeCommand(Connection sshConnection, String command) { + try { + Session session = sshConnection.openSession(); + BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(new StreamGobbler( + session.getStdout()))); + BufferedReader stderrReader = new BufferedReader(new InputStreamReader(new StreamGobbler( + session.getStderr()))); + session.execCommand(command); + ProcessResult result = getResultOfExecution(session, stdoutReader, stderrReader); + session.close(); + return result; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + private ProcessResult getResultOfExecution(Session session, BufferedReader stdoutReader, BufferedReader stderrReader) { + // Wait for program to come out either + // a) gracefully with an exit status, OR + // b) because of a termination signal + int condition = session.waitForCondition(ChannelCondition.EXIT_SIGNAL | ChannelCondition.EXIT_STATUS, 5000); + StringBuilder output = new StringBuilder(); + + try { + readFromStream(stdoutReader, output); + if (hasErrors(condition, session)) { + readFromStream(stderrReader, output); + } + + return prepareProcessResult(session, condition, output); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + private ProcessResult prepareProcessResult(Session session, int condition, StringBuilder output) { + ProcessResult result = null; + if (wasTerminated(condition)) { + result = new ProcessResult(ProcessResult.FAILURE, output.toString()); + } else { + if (hasErrors(condition, session)) { + Integer exitStatus = session.getExitStatus(); + int statusCode = (exitStatus == null ? ProcessResult.FAILURE : exitStatus); + result = new ProcessResult(statusCode, output.toString()); + } else { + result = new ProcessResult(ProcessResult.SUCCESS, output.toString()); + } + } + return result; + } + + private void readFromStream(BufferedReader streamReader, StringBuilder output) throws IOException, + UnsupportedEncodingException { + while (true) { + String line = streamReader.readLine(); + if (line == null) { + break; + } + output.append(line + CoreConstants.NEWLINE); + } + } + + /** + * Executes given command on remote machine using password authentication + * + * @param serverName + * @param command + * @return Result of remote execution + */ + public ProcessResult executeRemoteWithPassword(String serverName, String command) { + return executeCommand(getConnectionWithPassword(serverName), command); + } + + /** + * Executes given command on remote machine using public key authentication + * + * @param serverName + * @param command + * @return Result of remote execution + */ + public ProcessResult executeRemote(String serverName, String command) { + return executeCommand(getConnection(serverName), command); + } + + /** + * Checks if public key of management gateway is configured on given server + * + * @param serverName + * @return true if public key is configured, else false + */ + public boolean isPublicKeySetup(String serverName) { + try { + getConnection(serverName); + return true; + } catch (Exception e) { + return false; + } + } + + public void cleanup() { + for (Connection conn : sshConnCache.values()) { + conn.close(); + } + } + + public static void main(String[] args) { + SshUtil sshUtil = new SshUtil(); + System.out.println(new Date()); + ProcessResult result = sshUtil.executeRemote("dev.gluster.com", "/bin/pwd"); + System.out.println(result.getOutput()); + result = sshUtil.executeRemote("dev.gluster.com", "/bin/pwd1"); + System.out.println(new Date() + " - " + result.getExitValue() + " - " + result.getOutput()); + result = sshUtil.executeRemote("dev.gluster.com", "/bin/ls -lrt"); + System.out.println(new Date() + " - " + result.getExitValue() + " - " + result.getOutput()); + + sshUtil.cleanup(); + } +} -- cgit