blob: 2a8297bee73acb33f0a8a37a389f3e73efd361d2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.client;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.rmi.RemoteException;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.apache.sshd.client.config.hosts.HostConfigEntry;
import org.apache.sshd.client.config.hosts.KnownHostHashValue;
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
import org.apache.sshd.client.keyverifier.RejectAllServerKeyVerifier;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.PublicKeyEntry;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.forward.AcceptAllForwardingFilter;
import org.apache.sshd.util.test.BaseTestSupport;
import org.apache.sshd.util.test.CommandExecutionHelper;
import org.junit.FixMethodOrder;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ProxyTest extends BaseTestSupport {
@Rule
public TemporaryFolder tmpClientDir = new TemporaryFolder();
protected final Logger logger = LoggerFactory.getLogger(getClass());
private ClientSession proxySession;
public ProxyTest() {
super();
}
@Test
public void testProxy() throws Exception {
try (SshServer server = setupTestServer();
SshServer proxy = setupTestServer();
SshClient client = setupTestClient()) {
// setup server with an echo command
server.setCommandFactory((session, command) -> new CommandExecutionHelper(command) {
@Override
protected boolean handleCommandLine(String command) throws Exception {
OutputStream stdout = getOutputStream();
stdout.write(command.getBytes(StandardCharsets.US_ASCII));
stdout.flush();
return false;
}
});
server.start();
// setup proxy with a forwarding filter to allow the local port forwarding
proxy.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
proxy.start();
// setup client
client.start();
logger.info("Proxy: " + proxy.getPort() + ", server: " + server.getPort());
// Connect through to the proxy.
client.addPasswordIdentity("user2");
try (ClientSession session = createSession(
client,
"localhost", server.getPort(), "user1", "user1",
"user2@localhost:" + proxy.getPort())) {
assertTrue(session.isOpen());
doTestCommand(session, "ls -al");
}
assertTrue(proxySession == null || proxySession.isClosing() || proxySession.isClosed());
}
}
@Test
public void testDirectWithHostKeyVerification() throws Exception {
// This test exists only to show that the knownhosts setup is correct
try (SshServer server = setupTestServer();
SshServer proxy = setupTestServer();
SshClient client = setupTestClient()) {
File knownHosts = prepareHostKeySetup(server, proxy);
// Setup client with a standard ServerKeyVerifier
client.setServerKeyVerifier(
new KnownHostsServerKeyVerifier(RejectAllServerKeyVerifier.INSTANCE, knownHosts.toPath()));
client.start();
logger.info("Proxy: " + proxy.getPort() + ", server: " + server.getPort());
// Connect to the server directly to verify the knownhosts setup.
try (ClientSession session = createSession(
client, "localhost", server.getPort(), "user1", "user1", null)) {
assertTrue(session.isOpen());
doTestCommand(session, "ls -al");
}
assertTrue(proxySession == null || proxySession.isClosing() || proxySession.isClosed());
// Connect through to the proxy.
try (ClientSession session = createSession(
client, "localhost", proxy.getPort(), "user2", "user2", null)) {
assertTrue(session.isOpen());
assertThrows(RemoteException.class,
() -> doTestCommand(session, "ls -al"));
}
assertTrue(proxySession == null || proxySession.isClosing() || proxySession.isClosed());
}
}
@Test
public void testProxyWithHostKeyVerification() throws Exception {
try (SshServer server = setupTestServer();
SshServer proxy = setupTestServer();
SshClient client = setupTestClient()) {
File knownHosts = prepareHostKeySetup(server, proxy);
// Setup client with a standard ServerKeyVerifier
client.setServerKeyVerifier(
new KnownHostsServerKeyVerifier(RejectAllServerKeyVerifier.INSTANCE, knownHosts.toPath()));
client.start();
logger.info("Proxy: " + proxy.getPort() + ", server: " + server.getPort());
// Connect via the proxy
client.addPasswordIdentity("user2");
try (ClientSession session = createSession(
client, "localhost", server.getPort(), "user1", "user1",
"user2@localhost:" + proxy.getPort())) {
assertTrue(session.isOpen());
doTestCommand(session, "ls -la");
}
// make sure the proxy session is closed / closing
assertTrue(proxySession == null || proxySession.isClosing() || proxySession.isClosed());
}
}
@Test
public void testProxyWithHostKeyVerificationAndCustomConfig() throws Exception {
try (SshServer server = setupTestServer();
SshServer proxy = setupTestServer();
SshClient client = setupTestClient()) {
File knownHosts = prepareHostKeySetup(server, proxy);
// Setup client with a standard ServerKeyVerifier
client.setServerKeyVerifier(
new KnownHostsServerKeyVerifier(RejectAllServerKeyVerifier.INSTANCE, knownHosts.toPath()));
client.start();
client.setHostConfigEntryResolver(HostConfigEntry.toHostConfigEntryResolver(Arrays.asList(
new HostConfigEntry("server", "localhost", server.getPort(), "user1", "proxy"),
new HostConfigEntry("proxy", "localhost", proxy.getPort(), "user2"))));
logger.info("Proxy: " + proxy.getPort() + ", server: " + server.getPort());
// Connect via the proxy
client.addPasswordIdentity("user1");
client.addPasswordIdentity("user2");
try (ClientSession session = client.connect("server")
.verify(CONNECT_TIMEOUT).getSession()) {
session.auth().verify(AUTH_TIMEOUT);
assertTrue(session.isOpen());
doTestCommand(session, "ls -la");
}
// make sure the proxy session is closed / closing
assertTrue(proxySession == null || proxySession.isClosing() || proxySession.isClosed());
}
}
@Test
@Ignore
public void testExternal() throws Exception {
try (SshServer server = setupTestServer();
SshServer proxy = setupTestServer()) {
server.setCommandFactory((session, command) -> new CommandExecutionHelper(command) {
@Override
protected boolean handleCommandLine(String command) throws Exception {
OutputStream stdout = getOutputStream();
stdout.write(command.getBytes(StandardCharsets.US_ASCII));
stdout.flush();
return false;
}
});
server.start();
// setup proxy with a forwarding filter to allow the local port forwarding
proxy.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
proxy.start();
logger.info("Proxy: " + proxy.getPort() + ", server: " + server.getPort());
Thread.sleep(TimeUnit.MINUTES.toMillis(5));
}
}
protected void doTestCommand(ClientSession session, String command) throws IOException {
String result;
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream errors = new ByteArrayOutputStream();
session.executeRemoteCommand(command, out, errors, StandardCharsets.UTF_8);
result = out.toString();
assertEquals(command, result);
}
protected File prepareHostKeySetup(SshServer server, SshServer proxy) throws Exception {
// setup server with an echo command
server.setCommandFactory((session, command) -> new CommandExecutionHelper(command) {
@Override
protected boolean handleCommandLine(String command) throws Exception {
OutputStream stdout = getOutputStream();
stdout.write(command.getBytes(StandardCharsets.US_ASCII));
stdout.flush();
return true;
}
});
server.start();
File knownHosts = tmpClientDir.newFile("knownhosts");
writeKnownHosts(server, knownHosts);
// setup proxy with a forwarding filter to allow the local port forwarding
proxy.setForwardingFilter(AcceptAllForwardingFilter.INSTANCE);
proxy.start();
writeKnownHosts(proxy, knownHosts);
return knownHosts;
}
protected File writeKnownHosts(SshServer server, File knownHosts) throws Exception {
KeyPair serverHostKey = GenericUtils.head(server.getKeyPairProvider().loadKeys(null));
try (BufferedWriter writer = Files.newBufferedWriter(knownHosts.toPath(), StandardCharsets.US_ASCII,
StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
KnownHostHashValue.appendHostPattern(writer, "localhost", server.getPort());
writer.append(' ');
PublicKeyEntry.appendPublicKeyEntry(writer, serverHostKey.getPublic());
writer.append('\n');
}
return knownHosts;
}
@SuppressWarnings("checkstyle:ParameterNumber")
protected ClientSession createSession(
SshClient client,
String host, int port, String user, String password,
String proxyJump)
throws IOException {
ClientSession session = client.connect(new HostConfigEntry(
"", host, port, user,
proxyJump))
.verify(CONNECT_TIMEOUT).getSession();
session.addPasswordIdentity(password);
session.auth().verify(AUTH_TIMEOUT);
return session;
}
}