/* * Copyright 2015 Red Hat, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. * * * Copyright (c) 2015 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. * */ package io.vertx.ext.shell; import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.UserInfo; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.VertxException; import io.vertx.core.json.JsonObject; import io.vertx.core.net.JksOptions; import io.vertx.ext.auth.shiro.ShiroAuthOptions; import io.vertx.ext.auth.shiro.ShiroAuthRealmType; import io.vertx.ext.shell.term.SSHTermOptions; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ @RunWith(VertxUnitRunner.class) public abstract class SSHTestBase { protected Vertx vertx; @Before public void before() { vertx = Vertx.vertx(); } @After public void after() throws Exception { CountDownLatch latch = new CountDownLatch(1); Handler<AsyncResult<Void>> handler = ar -> { latch.countDown(); }; vertx.close(handler); assertTrue(latch.await(10, TimeUnit.SECONDS)); } protected abstract void startShell(SSHTermOptions options) throws ExecutionException, InterruptedException, TimeoutException; protected void startShell() throws Exception { startShell(new SSHTermOptions().setPort(5000).setHost("localhost").setKeyPairOptions( new JksOptions().setPath("src/test/resources/server-keystore.jks").setPassword("wibble")). setAuthOptions(new ShiroAuthOptions().setType(ShiroAuthRealmType.PROPERTIES).setConfig( new JsonObject().put("properties_path", "classpath:test-auth.properties")))); } protected Session createSession(String username, String password, boolean interactive) throws Exception { JSch jsch= new JSch(); Session session = jsch.getSession(username, "localhost", 5000); if (!interactive) { session.setPassword(password); } session.setUserInfo(new UserInfo() { @Override public String getPassphrase() { return null; } @Override public String getPassword() { return interactive ? password : null; } @Override public boolean promptPassword(String s) { return interactive; } @Override public boolean promptPassphrase(String s) { return false; } @Override public boolean promptYesNo(String s) { ; // Accept all server keys return true; } @Override public void showMessage(String s) { } }); return session; } @Test public void testAuthenticate() throws Exception { startShell(); for (boolean interactive : new boolean[]{false, true}) { Session session = createSession("paulo", "secret", interactive); session.connect(); Channel channel = session.openChannel("shell"); channel.connect(); InputStream in = channel.getInputStream(); byte[] out = new byte[2]; assertEquals(2, in.read(out)); assertEquals("% ", new String(out)); channel.disconnect(); session.disconnect(); } } @Test public void testAuthenticationFail() throws Exception { startShell(); for (boolean interactive : new boolean[]{false, true}) { Session session = createSession("paulo", "secret_", interactive); try { session.connect(); fail(); } catch (JSchException e) { String msg = e.getMessage(); assertTrue("Unexpected failure message " + msg, "Auth cancel".equals(msg) || "Auth fail".equals(msg)); } } } @Test public void testNoAuthenticationConfigured() throws Exception { try { startShell(new SSHTermOptions().setPort(5000).setHost("localhost").setKeyPairOptions( new JksOptions().setPath("src/test/resources/server-keystore.jks").setPassword("wibble")) ); fail(); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof VertxException); assertEquals("No authenticator", e.getCause().getMessage()); } } @Test public void testNoKeyPairConfigured() throws Exception { try { startShell(new SSHTermOptions().setPort(5000).setHost("localhost"). setAuthOptions(new ShiroAuthOptions().setType(ShiroAuthRealmType.PROPERTIES).setConfig( new JsonObject().put("properties_path", "classpath:test-auth.properties"))) ); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof VertxException); assertEquals("No key pair store configured", e.getCause().getMessage()); } } @Test(timeout = 5000) public void testExec(TestContext context) throws Exception { startShell(); Session session = createSession("paulo", "secret", false); session.connect(); ChannelExec channel = (ChannelExec) session.openChannel("exec"); channel.setCommand("the-command arg1 arg2"); channel.connect(); InputStream in = channel.getInputStream(); StringBuilder input = new StringBuilder(); while (!input.toString().equals("the_output")) { int a = in.read(); if (a == -1) { break; } input.append((char)a); } OutputStream out = channel.getOutputStream(); out.write("the_input".getBytes()); out.flush(); while (channel.isConnected()) { Thread.sleep(1); } assertEquals(2, channel.getExitStatus()); session.disconnect(); } }