2012-06-18 8 views
7

Webアプリケーションからリモートマシンに接続できるように、HTTPトンネルを作成しようとしています。関係するセキュリティリスクを認識していますが、それは私たちがやりたいことです。インターネット上ではなくプライベートネットワーク上でホストされるため、リスクは低いと考えられています。HTTPトンネルサーブレット(Java)

基本的な要件は、Javaデバッグツールがサーブレットを介してマシンに接続できるようにすることです。開発ボックスをファイアウォール側に置くことを主張するクライアントがいくつかあり、Javaデバッグサーバーの戻りポートは固定されていないため、特定のポートを開くように要求することはできません。

コードはまだ完全ではありません。私は単純に双方向のやり取りをすることを試みてきました。

いくつかのコンポーネントがあります。 EclipseのJavaデバッグが接続するスタンドアロンのサーバー。このサーバーは、接続先のポートに基づいてどこに向かっているのかを知るように構成されています。したがって、ポート1166がヒットした場合、マシンxのサーブレットに接続することがわかります。

すなわち、Eclipseのデバッガ - >デバッグプロキシサーバー - >アプリケーションのサーブレット - >アプリケーションJVM

これまでのところ私の努力のために、私は接続することができるように見えるが、流れは完全には機能しません。 EclipseはJDWPハンドシェイクをJVMに送信します。これはJDWPハンドシェイクで返信する予定です。私は、JDWPハンドシェイクがEclipseによって送信されると、デバッグ・プロキシ・サーバーに書き込まれてサーブレットにリレーされるが、サーブレットでは無視されているように見える。私が受けていますログは次のとおりです。

[INFO] Started Jetty Server 
2012-06-18 10:00:53,356 INFO ProxySocket - Connection received, forwarding to tidevwls03:1166 via http://localhost:8080/tunnel/debug-proxy 
2012-06-18 10:00:53,361 INFO ProxySocket - Connected to http://localhost:8080/tunnel/debug-proxy 
2012-06-18 10:00:53,603 INFO ProxyServlet - Received incoming http connection, attempting to forward to endpoint tidevwls03:1166 
2012-06-18 10:00:53,604 INFO ProxyServlet - Connecting to endpoint tidevwls03:1166 
2012-06-18 10:00:53,613 INFO StreamProxy - [endpoint-read -> http-write ] beginning proxy transport. 
2012-06-18 10:00:53,613 INFO StreamProxy - [http-read  -> endpoint-write] beginning proxy transport. 
2012-06-18 10:00:53,619 INFO ProxySocket - Response Header: HTTP/1.1 200 OK 
2012-06-18 10:00:53,619 INFO ProxySocket - Response Header: Content-Length: 0 
2012-06-18 10:00:53,623 INFO ProxySocket - Response Header: Server: Jetty(6.1.22) 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] beginning proxy transport. 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'J' 
2012-06-18 10:00:53,624 INFO StreamProxy - [servlet-read -> client-write ] beginning proxy transport. 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'D' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'W' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'P' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] '-' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'H' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'a' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'n' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'd' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 's' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'h' 
2012-06-18 10:00:53,624 INFO StreamProxy - [client-read -> servlet-write ] 'a' 
2012-06-18 10:00:53,625 INFO StreamProxy - [client-read -> servlet-write ] 'k' 
2012-06-18 10:00:53,625 INFO StreamProxy - [client-read -> servlet-write ] 'e' 

私はストリームが複数の要求に分割され、セッションベースの接続が使用されているように、この上の私の考え方を変更する必要がある場合、私は思ったんだけど。 1つのリクエストは決して下流には決して終わらない(すなわち、無限レスポンス)。そして、クライアントがサーブレットに送るとき、毎回新しいリクエストを作成するだろう。これはこれを稼働させるための鍵ですか?

以下は、スタンドアロンで実行できるデバッグプロキシサーバーのコードです。または、一時的なテストのためにJettyサーバー上でサーブレットとして実行するように一時的に構成しています。 (ProxySocket.java)

import java.io.BufferedReader; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 
import java.net.URL; 
import java.util.List; 

import javax.net.SocketFactory; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 

import org.apache.commons.io.IOUtils; 
import org.apache.commons.lang.StringUtils; 
import org.apache.log4j.Logger; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 

public class ProxySocket extends HttpServlet { 
    private static final Logger logger = Logger.getLogger(ProxySocket.class); 
    private static final ApplicationContext springContext = new ClassPathXmlApplicationContext("env-spring/applicationContext*.xml"); 

    @Override 
    public void init() throws ServletException { 
    List<HttpDebugConfig> configs = (List<HttpDebugConfig>) springContext.getBean("DebugProxyHosts"); 
    for (HttpDebugConfig config : configs) { 
     ProxyServer proxyServer = new ProxyServer(config); 
     proxyServer.start(); 
    } 
    } 

    class ProxyServer extends Thread { 
    private HttpDebugConfig config; 

    public ProxyServer(HttpDebugConfig config) { 
     this.config = config; 
    } 

    public void run() { 
     ServerSocket ss = null; 
     StreamProxy streamToTunnel = null; 
     StreamProxy streamToClient = null; 

     try { 
     ss = new ServerSocket(config.getLocalPort()); 
     Socket inbound = null; 
     Socket outbound = null; 
     logger.info(String.format("Listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort())); 
     while ((inbound = ss.accept()) != null) { 
      try { 
      logger.info(String.format("Connection received, forwarding to %s:%d via %s", config.getRemoteHost(), config.getRemotePort(), config.getProxyUrl())); 

      URL proxy = new URL(config.getProxyUrl()); 

      outbound = SocketFactory.getDefault().createSocket(proxy.getHost(), proxy.getPort()); 
      logger.info(String.format("Connected to %s", config.getProxyUrl())); 

      OutputStream out = outbound.getOutputStream(); 
      BufferedReader in = new BufferedReader(new InputStreamReader(outbound.getInputStream())); 

      writeLine(out, String.format("POST %s HTTP/1.1", config.getProxyUrl())); 
      writeLine(out, String.format("Host: http://%s:%s", proxy.getHost(), proxy.getPort())); 
      writeLine(out, "Connection: keep-alive"); 
      writeLine(out, String.format("tunnel_host: %s", config.getRemoteHost())); 
      writeLine(out, String.format("tunnel_port: %s", String.valueOf(config.getRemotePort()))); 
      writeLine(out, ""); 

      // read the http response and then we can start tunnelling. 
      for (String line = ""; StringUtils.isNotBlank(line = in.readLine());) { 
       logger.info(String.format("Response Header: %s", line)); 
      } 

      streamToTunnel = new StreamProxy("[client-read -> servlet-write ]", inbound.getInputStream(), outbound.getOutputStream()); 
      streamToClient = new StreamProxy("[servlet-read -> client-write ]", outbound.getInputStream(), inbound.getOutputStream()); 
      streamToTunnel.start(); 
      streamToClient.start(); 

      while (streamToClient.isAlive() || streamToTunnel.isAlive()) { 
       try { Thread.sleep(100); } catch (InterruptedException e) { } 
      } 

      logger.info(String.format("Shutting down socket-to-%s.", config.getProxyUrl())); 
      } finally { 
      IOUtils.closeQuietly(inbound); 
      IOUtils.closeQuietly(outbound); 
      } 
     } 
     } catch (IOException e) { 
     logger.error(String.format("No longer listening for connections on port %d. Proxying to %s:%d", config.getLocalPort(), config.getRemoteHost(), config.getRemotePort()), e); 
     } finally { 
     if (ss != null) { 
      try { ss.close(); } catch (Exception e) { } 
     } 
     } 
    } 

    private void writeLine(OutputStream out, String msg) throws IOException { 
     out.write(String.format("%s\n", StringUtils.defaultString(msg)).getBytes()); 
    } 
    } 
} 

コードの次のセクションでは、ばね構成(/env-spring/applicationContext.xml)です。

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:util="http://www.springframework.org/schema/util" 
    xsi:schemaLocation=" 
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
     http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd 
    "> 
    <util:list id="DebugProxyHosts" list-class="java.util.ArrayList"> 
     <bean class="HttpDebugConfig"> 
      <property name="localPort" value="1166" /> 
      <property name="proxyUrl" value="http://localhost:8080/tunnel/debug-proxy" /> 
      <property name="remoteHost" value="tidevwls03" /> 
      <property name="remotePort" value="1166" /> 
     </bean> 
    </util:list> 
</beans> 

構成ビーン(HttpDebugConfig.java)。

public class HttpDebugConfig { 
    private int localPort; 
    private String remoteHost; 
    private int remotePort; 
    private String proxyUrl; 

    public int getLocalPort() { 
    return localPort; 
    } 

    public void setLocalPort(int localPort) { 
    this.localPort = localPort; 
    } 

    public String getRemoteHost() { 
    return remoteHost; 
    } 

    public void setRemoteHost(String remoteHost) { 
    this.remoteHost = remoteHost; 
    } 

    public int getRemotePort() { 
    return remotePort; 
    } 

    public void setRemotePort(int remotePort) { 
    this.remotePort = remotePort; 
    } 

    public String getProxyUrl() { 
    return proxyUrl; 
    } 

    public void setProxyUrl(String proxyUrl) { 
    this.proxyUrl = proxyUrl; 
    } 
} 

入力ストリーム出力ストリーム複写機(StreamProxy.java)

import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 

import org.apache.commons.io.IOUtils; 
import org.apache.log4j.Logger; 

public class StreamProxy extends Thread { 
    private static final Logger logger = Logger.getLogger(StreamProxy.class); 

    private InputStream in; 
    private OutputStream out; 

    private boolean kill = false; 

    public StreamProxy(String name, InputStream in, OutputStream out) { 
    this.in = in; 
    this.out = out; 
    setName(name); 
    } 

    @Override 
    public void interrupt() { 
    this.kill = true; 
    super.interrupt(); 
    } 

    @Override 
    public void run() { 
    try { 
     logger.info(String.format("%s beginning proxy transport.", getName())); 
     do { 
     int n = 0; 
     while (-1 != (n = in.read())) { 
      logger.info(getName() + " '" + (char) n + "'"); 
      out.write(n); 
      // out.flush(); 
     } 
     try { Thread.sleep(1); } catch (Exception e) { } 
     } while (! kill); 
     logger.info(String.format("%s completed proxy transport.", getName())); 
    } catch (IOException e) { 
     logger.error(String.format("%s Failed to copy from input stream to output stream. Aborting thread.", getName()), e); 
     kill = true; 
    } finally { 
     IOUtils.closeQuietly(in); 
     IOUtils.closeQuietly(out); 
    } 
    } 
} 

にこのセクションでは、トンネルサーブレット(ProxyServlet.java)

import java.io.IOException; 
import java.net.Socket; 

import javax.net.SocketFactory; 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 

import org.apache.commons.io.IOUtils; 
import org.apache.commons.lang.StringUtils; 
import org.apache.commons.lang.math.NumberUtils; 
import org.apache.log4j.Logger; 

public class ProxyServlet extends HttpServlet { 
    private static final Logger logger = Logger.getLogger(ProxyServlet.class); 
    private static final long serialVersionUID = -686421490573011755L; 

    @Override 
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
    new Runner(request, response).start(); 
    } 

    class Runner extends Thread { 
    private HttpServletRequest request; 
    private HttpServletResponse response; 

    public Runner(HttpServletRequest request, HttpServletResponse response) { 
     this.request = request; 
     this.response = response; 
    } 

    @Override 
    public void run() { 
     Socket endpoint = null; 
     StreamProxy streamToHttp = null; 
     StreamProxy streamToEndpoint = null; 

     String host = StringUtils.defaultIfEmpty(request.getHeader("tunnel_host"), "localhost"); 
     int port = NumberUtils.toInt(request.getHeader("tunnel_port"), 8000); 

     try { 
     logger.info(String.format("Received incoming http connection, attempting to forward to endpoint %s:%d", host, port)); 

     logger.info(String.format("Connecting to endpoint %s:%d", host, port)); 
     endpoint = SocketFactory.getDefault().createSocket(host, port); 

     streamToHttp = new StreamProxy("[endpoint-read -> http-write ]", endpoint.getInputStream(), response.getOutputStream()); 
     streamToEndpoint = new StreamProxy("[http-read  -> endpoint-write]", request.getInputStream(), endpoint.getOutputStream()); 
     streamToHttp.start(); 
     streamToEndpoint.start(); 

     while (streamToEndpoint.isAlive() || streamToHttp.isAlive()) { 
      try { Thread.sleep(100); } catch (InterruptedException e) { } 
     } 

     logger.info(String.format("Safely shut down servlet-to-%s:%d proxy.", host, port)); 
     } catch (IOException e) { 
     logger.error(String.format("Shutting down servlet-to-%s:%d proxy.", host, port), e); 
     } finally { 
     if (streamToHttp != null) { 
      streamToHttp.interrupt(); 
     } 
     if (streamToEndpoint != null) { 
      streamToEndpoint.interrupt(); 
     } 
     IOUtils.closeQuietly(endpoint); 
     } 
    } 
    } 
} 

あるアプリケーションコンテナ設定(ウェブ.xml)

<?xml version="1.0" encoding="ISO-8859-1"?> 
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" 
    version="2.4"> 

    <display-name>tunnel</display-name> 

    <context-param> 
     <param-name>log4jConfigLocation</param-name> 
     <param-value>classpath:log4j.properties 
     </param-value> 
    </context-param> 

    <context-param> 
     <param-name>contextConfigLocation</param-name> 
     <param-value>classpath:env-spring/applicationContext*.xml</param-value> 
    </context-param> 

    <listener> 
     <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> 
    </listener> 

    <listener> 
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
    </listener> 

    <servlet> 
     <servlet-name>Debug Proxy</servlet-name> 
     <servlet-class>ProxyServlet</servlet-class> 
     <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>Debug Proxy</servlet-name> 
     <url-pattern>/debug-proxy</url-pattern> 
    </servlet-mapping> 

    <servlet> 
     <servlet-name>Debug Socket</servlet-name> 
     <servlet-class>ProxySocket</servlet-class> 
     <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet-mapping> 
     <servlet-name>Debug Socket</servlet-name> 
     <url-pattern>/debug-socket</url-pattern> 
    </servlet-mapping> 

    <welcome-file-list> 
     <welcome-file>index.jsp</welcome-file> 
    </welcome-file-list> 
</web-app> 

最後に、私はpaven.xmlをmavenでビルドしています。私は、次のMavenと突堤サーバが

jetty:stop clean install jetty:run-war 

を対象に実行

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>tunnel</groupId> 
    <artifactId>tunnel</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <packaging>war</packaging> 

    <properties> 
     <version.spring>3.1.1.RELEASE</version.spring> 
    </properties> 

    <build> 
     <plugins> 
      <plugin> 
       <groupId>org.mortbay.jetty</groupId> 
       <artifactId>maven-jetty-plugin</artifactId> 
       <version>6.1.22</version> 
       <configuration> 
        <webApp>${project.build.directory}/${project.build.finalName}.${project.packaging}</webApp> 
        <stopPort>9966</stopPort> 
        <stopKey>foo</stopKey> 
       </configuration> 
      </plugin> 
     </plugins> 
    </build> 

    <dependencies> 
     <dependency> 
      <groupId>javax.servlet</groupId> 
      <artifactId>servlet-api</artifactId> 
      <version>2.5</version> 
     </dependency> 

     <!-- Utilities --> 
     <dependency> 
      <groupId>commons-lang</groupId> 
      <artifactId>commons-lang</artifactId> 
      <version>2.6</version> 
     </dependency> 
     <dependency> 
      <groupId>commons-io</groupId> 
      <artifactId>commons-io</artifactId> 
      <version>2.0.1</version> 
     </dependency> 
     <dependency> 
      <groupId>commons-collections</groupId> 
      <artifactId>commons-collections</artifactId> 
      <version>3.2.1</version> 
     </dependency> 
     <dependency> 
      <groupId>commons-logging</groupId> 
      <artifactId>commons-logging</artifactId> 
      <version>1.1.1</version> 
     </dependency> 
     <dependency> 
      <groupId>commons-httpclient</groupId> 
      <artifactId>commons-httpclient</artifactId> 
      <version>3.0.1</version> 
      <exclusions> 
       <exclusion> 
        <artifactId>junit</artifactId> 
        <groupId>junit</groupId> 
       </exclusion> 
      </exclusions> 
     </dependency> 
     <dependency> 
      <groupId>commons-codec</groupId> 
      <artifactId>commons-codec</artifactId> 
      <version>1.2</version> 
     </dependency> 

     <!-- Logging --> 
     <dependency> 
      <groupId>log4j</groupId> 
      <artifactId>log4j</artifactId> 
      <version>1.2.16</version> 
     </dependency> 

     <!-- Spring Framework --> 
     <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-core</artifactId> 
      <version>${version.spring}</version> 
     </dependency> 
    <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-context</artifactId> 
      <version>${version.spring}</version> 
     </dependency> 
    <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-web</artifactId> 
      <version>${version.spring}</version> 
     </dependency> 
    <dependency> 
      <groupId>org.springframework</groupId> 
      <artifactId>spring-webmvc</artifactId> 
      <version>${version.spring}</version> 
     </dependency> 
    </dependencies> 
</project> 

は、あなたがこの小さなプロジェクトが面白いホープ!あなたのアイデアやコメントをお待ちしております。

おかげで、 スチュアート

+0

Jettyでリモートデバッグポートを指定することはできませんか?私はあなたがどんなサーバーでもそれを行うことができると思った... – Thihara

+0

このトンネルサーブレットの開発中にJettyだけをテストしています。実際の実装は、ファイアウォールの背後にあるWebLogicサーバーに移動します。これは、ファイアウォールがポートをブロックし、またJVMからの戻りポートがランダムで設定できないため、ポートに接続できないことを意味します。したがって、デバッグクライアントからポートを開く場合でも、 JVMは、クライアントに戻るときに使用します。 – mrswadge

+0

+1興味深い問題のために、私はJVMのランダムポートの事を知りませんでした... – Thihara

答えて

0

SSHトンネル - 私の選択http://en.wikipedia.org/wiki/Tunneling_protocol

ssh -L [bind_address:]port:sshserverhostname:targetmachinehostname port 

-Lはローカル(クライアント)ホスト上の指定したポートが与えられたホスト とポートに転送することを指定します遠隔地にいる。これはローカル 側のポートをリッスンするソケットを割り当て、指定したbind_addressにオプションでバインドして動作します。この ポートに接続すると、接続はセキュアチャネルを介して転送され、リモートマシンからホストポート ポートに接続されます。ポートフォワーディングは、コンフィギュレーションファイルでも指定できます。 IPv6アドレスは、アドレスを角カッコで囲むことで指定できます。 スーパーユーザーだけが特権ポートを転送できます。デフォルトでは、ローカルポートは、GatewayPorts設定に合わせて調整されています( )。ただし、明示的なbind_addressを使用して、特定のアドレスへの接続をバインドすることができます。 `localhost'' indicates that the listen- ing port be bound for local use only, while an empty address or * 'のbind_addressは、すべてのインターフェイスからポート が使用可能であることを示します。

関連する問題