diff --git a/pom.xml b/pom.xml index 57440bf..6045943 100644 --- a/pom.xml +++ b/pom.xml @@ -322,6 +322,13 @@ 2.0.3 test + + com.github.stefanbirkner + system-lambda + 1.2.1 + test + + diff --git a/src/main/java/net/juniper/netconf/Device.java b/src/main/java/net/juniper/netconf/Device.java index 208b724..aa55f0e 100644 --- a/src/main/java/net/juniper/netconf/Device.java +++ b/src/main/java/net/juniper/netconf/Device.java @@ -11,7 +11,10 @@ import com.jcraft.jsch.ChannelSubsystem; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.ProxyHTTP; +import com.jcraft.jsch.ProxySOCKS5; import com.jcraft.jsch.Session; +import com.jcraft.jsch.SocketFactory; import lombok.Builder; import lombok.Getter; import lombok.NonNull; @@ -22,6 +25,7 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; +import javax.net.ssl.SSLSocketFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -29,6 +33,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -228,6 +234,50 @@ private Session loginWithUserPass(int timeoutMilliSeconds) throws NetconfExcepti session.setConfig("userauth", "password"); session.setConfig("StrictHostKeyChecking", isStrictHostKeyChecking() ? "yes" : "no"); session.setPassword(password); + + final String socksProxyHost = System.getenv("SOCKS_PROXY_HOST"); + final String socksProxyPort = System.getenv("SOCKS_PROXY_PORT"); + final String socksProxyUser = System.getenv("SOCKS_PROXY_USER"); + final String socksProxyPass = System.getenv("SOCKS_PROXY_PASS"); + if (socksProxyHost != null && socksProxyPort != null && socksProxyUser != null && socksProxyPass != null) { + log.info("Using socks5 proxy"); + final ProxySOCKS5 proxy = new ProxySOCKS5(socksProxyHost, Integer.parseInt(socksProxyPort)); + proxy.setUserPasswd(socksProxyUser, socksProxyPass); + session.setProxy(proxy); + } + else { + final String httpProxyHost = System.getenv("HTTP_PROXY_HOST"); + final String httpProxyPort = System.getenv("HTTP_PROXY_PORT"); + final String httpProxyUser = System.getenv("HTTP_PROXY_USER"); + final String httpProxyPass = System.getenv("HTTP_PROXY_PASS"); + if(httpProxyHost != null && httpProxyPort != null && httpProxyUser != null && httpProxyPass != null) { + log.info("Using http proxy"); + final ProxyHTTP proxy = new ProxyHTTP(httpProxyHost, Integer.parseInt(httpProxyPort)); + proxy.setUserPasswd(httpProxyUser, httpProxyPass); + session.setProxy(proxy); + // ProxyHTTP class doesn't know anything about TLS. We are supplying a socket + // factory which takes care of the low level TLS tunnel between the client + // and the proxy server + session.setSocketFactory((new SocketFactory() { + final javax.net.SocketFactory fact = SSLSocketFactory.getDefault(); + @Override + public Socket createSocket(String host, int port) throws IOException { + return fact.createSocket(host, port); + } + + @Override + public InputStream getInputStream(Socket socket) throws IOException { + return socket.getInputStream(); + } + + @Override + public OutputStream getOutputStream(Socket socket) throws IOException { + return socket.getOutputStream(); + } + })); + } + } + session.connect(timeoutMilliSeconds); return session; } catch (JSchException e) { diff --git a/src/test/java/net/juniper/netconf/DeviceTest.java b/src/test/java/net/juniper/netconf/DeviceTest.java index c72d2db..d06edf6 100644 --- a/src/test/java/net/juniper/netconf/DeviceTest.java +++ b/src/test/java/net/juniper/netconf/DeviceTest.java @@ -1,13 +1,17 @@ package net.juniper.netconf; +import com.github.stefanbirkner.systemlambda.SystemLambda; import com.jcraft.jsch.ChannelSubsystem; import com.jcraft.jsch.HostKeyRepository; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.ProxyHTTP; +import com.jcraft.jsch.ProxySOCKS5; import com.jcraft.jsch.Session; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.mockito.ArgumentCaptor; import org.xmlunit.assertj.XmlAssert; import java.io.ByteArrayInputStream; @@ -136,6 +140,90 @@ public void GIVEN_sshAvailableNetconfNot_THEN_closeDevice() throws Exception { verifyNoMoreInteractions(sshClient); } + @Test + public void GIVEN_netConfWithHttpProxy_THEN_setHttpProxy() throws Exception { + + String httpProxyHost = "testHttpProxyHost"; + String httpProxyPort = "8080"; + String httpProxyUser = "username"; + String httpProxyPass = "password"; + SystemLambda.withEnvironmentVariable("HTTP_PROXY_HOST", httpProxyHost) + .and("HTTP_PROXY_PORT", httpProxyPort) + .and("HTTP_PROXY_USER", httpProxyUser) + .and("HTTP_PROXY_PASS", httpProxyPass) + .execute(() -> { + JSch sshClient = mock(JSch.class); + Session session = mock(Session.class); + HostKeyRepository hostKeyRepository = mock(HostKeyRepository.class); + + when(sshClient.getSession(eq(TEST_USERNAME), eq(TEST_HOSTNAME), + eq(DEFAULT_NETCONF_PORT))).thenReturn(session); + when(sshClient.getHostKeyRepository()).thenReturn(hostKeyRepository); + + try (Device device = Device.builder() + .sshClient(sshClient) + .hostName(TEST_HOSTNAME) + .userName(TEST_USERNAME) + .password(TEST_PASSWORD) + .strictHostKeyChecking(false) + .build()) { + device.connect(); + } + catch (NetconfException e) { + // Do nothing + } + + ProxyHTTP httpProxy = new ProxyHTTP(httpProxyHost, Integer.parseInt(httpProxyPort)); + httpProxy.setUserPasswd(httpProxyUser, httpProxyPass); + ArgumentCaptor actualProxyCaptor = ArgumentCaptor.forClass(ProxyHTTP.class); + verify(session).setProxy(actualProxyCaptor.capture()); + ProxyHTTP actualHttpProxy = actualProxyCaptor.getValue(); + assertThat(actualHttpProxy).usingRecursiveComparison().isEqualTo(httpProxy); + }); + } + + @Test + public void GIVEN_netConfWithSocksProxy_THEN_setHttpProxy() throws Exception { + + String socksProxyHost = "testSocksProxyHost"; + String socksProxyPort = "8080"; + String socksProxyUser = "username"; + String socksProxyPass = "password"; + SystemLambda.withEnvironmentVariable("SOCKS_PROXY_HOST", socksProxyHost) + .and("SOCKS_PROXY_PORT", socksProxyPort) + .and("SOCKS_PROXY_USER", socksProxyUser) + .and("SOCKS_PROXY_PASS", socksProxyPass) + .execute(() -> { + JSch sshClient = mock(JSch.class); + Session session = mock(Session.class); + HostKeyRepository hostKeyRepository = mock(HostKeyRepository.class); + + when(sshClient.getSession(eq(TEST_USERNAME), eq(TEST_HOSTNAME), + eq(DEFAULT_NETCONF_PORT))).thenReturn(session); + when(sshClient.getHostKeyRepository()).thenReturn(hostKeyRepository); + + try (Device device = Device.builder() + .sshClient(sshClient) + .hostName(TEST_HOSTNAME) + .userName(TEST_USERNAME) + .password(TEST_PASSWORD) + .strictHostKeyChecking(false) + .build()) { + device.connect(); + } + catch (NetconfException e) { + // Do nothing + } + + ProxySOCKS5 socksProxy = new ProxySOCKS5(socksProxyHost, Integer.parseInt(socksProxyPort)); + socksProxy.setUserPasswd(socksProxyUser, socksProxyPass); + ArgumentCaptor actualProxyCaptor = ArgumentCaptor.forClass(ProxySOCKS5.class); + verify(session).setProxy(actualProxyCaptor.capture()); + ProxySOCKS5 actualSocksProxy = actualProxyCaptor.getValue(); + assertThat(actualSocksProxy).usingRecursiveComparison().isEqualTo(socksProxy); + }); + } + @Test public void GIVEN_newDevice_WHEN_withNullUserName_THEN_throwsException() { assertThatThrownBy(() -> Device.builder().hostName("foo").build()) @@ -222,4 +310,5 @@ private JSch givenConnectingSshClient() throws IOException, JSchException { .thenReturn(sshSession); return sshClient; } + }