CLOVER🍀

That was when it all began.

stunnelを使って、バックエンドにフォワードプロキシ越しにアクセスする

これは、なにをしたくて書いたもの?

stunnelでバックエンドのサーバーに転送する際に、フォワードプロキシサーバーを経由することができないかなぁと調べたら、
できそうだったので試してみようかなと。

環境

今回の環境は、こちらです。Ubuntu Linux 18.04 LTS。

$ uname -srvmpio
Linux 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

お題としては、https://www.google.com/へのアクセスを以下のような経路で行ってみましょう。

  • curl → stunnel(HTTP) → Apache(HTTP) → www.google.com(HTTPS)

間のApacheは、HTTP CONNECTでHTTPS通信をトンネリングさせます。

フォワードプロキシ(Apache)のインストール

まずは、Apacheをインストール。

$ sudo apt install apache2

バージョン。

$ apache2 -v
Server version: Apache/2.4.29 (Ubuntu)
Server built:   2020-03-13T12:26:16

mod_proxy_connectを有効にします。

$ sudo a2enmod proxy proxy_connect

フォワードプロキシとしての設定。

Listen 8080
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On

  <Proxy *>
    Require host localhost
  </Proxy>

  ErrorLog ${APACHE_LOG_DIR}/proxy_error.log
  CustomLog ${APACHE_LOG_DIR}/proxy_access.log combined
</VirtualHost>

Apacheを再起動したら、準備完了です。

$ sudo systemctl restart apache2

確認。

$ https_proxy=http://localhost:8080 curl https://www.google.com

アクセスログを確認すると、HTTP CONNECT経由でアクセスしていることがわかります。

localhost - - [09/Apr/2020:13:45:00 +0000] "CONNECT www.google.com:443 HTTP/1.1" 200 20118 "-" "curl/7.58.0"

stunnelをインストール、設定する

続いて、stunnelをインストールします。

$ sudo apt install stunnel4

バージョン。

$ stunnel4 -version
stunnel 5.44 on x86_64-pc-linux-gnu platform
Compiled with OpenSSL 1.1.0g  2 Nov 2017
Running  with OpenSSL 1.1.1  11 Sep 2018
Update OpenSSL shared libraries or rebuild stunnel
Threading:PTHREAD Sockets:POLL,IPv6,SYSTEMD TLS:ENGINE,FIPS,OCSP,PSK,SNI Auth:LIBWRAP
 
Global options:
pid                    = /var/run/stunnel4.pid
RNDbytes               = 64
RNDfile                = /dev/urandom
RNDoverwrite           = yes
 
Service-level options:
ciphers                = FIPS (with "fips = yes")
ciphers                = HIGH:!DH:!aNULL:!SSLv2 (with "fips = no")
curve                  = prime256v1
debug                  = daemon.notice
logId                  = sequential
options                = NO_SSLv2
options                = NO_SSLv3
sessionCacheSize       = 1000
sessionCacheTimeout    = 300 seconds
stack                  = 65536 bytes
TIMEOUTbusy            = 300 seconds
TIMEOUTclose           = 60 seconds
TIMEOUTconnect         = 10 seconds
TIMEOUTidle            = 43200 seconds
verify                 = none

設定ファイル「/etc/default/stunnel4」のENABLEDを「1」に変更して、stunnelを有効化します。

ENABLED=1

設定ファイルを作成。
/etc/stunnel/stunnel.conf

[google-web]
client = yes
accept = 127.0.0.1:8000
connect = localhost:8080
protocol = connect
protocolHost = www.google.com:443
verifyChain = yes
CApath = /etc/ssl/certs
checkHost = www.google.com
OCSPaia = yes

stunnelでリッスンするポートは、8000とします。

ポイントは、以下です。

  • クライアントモードにしていること
  • connectで指定するのは、プロキシサーバーのアドレス、ポートとすること
  • protocolで「connect」と指定すること
  • protocolHostに、本来の転送先のバックエンドサーバーを書くこと

stunnel TLS Proxy

設定ができたので、stunnelを起動します

$ sudo systemctl start stunnel4

確認

では、確認してみましょう。

ローカルのポート8000に、HTTPでアクセスします。Hostヘッダーだけは合わせておきました。

$ curl -v localhost:8000 -H 'Host: www.google.com'

結果、プロキシサーバーにHTTP CONNECTでアクセスしていることがわかります。

localhost - - [09/Apr/2020:13:53:46 +0000] "CONNECT www.google.com:443 HTTP/1.1" 200 20332 "-" "-"

もうひとつ

結果がちょっとわかりづらかったかもなので、もうひとつ試してみましょう。

たとえば、www.yahoo.co.jpにHTTPでアクセスするとリダイレクトを要求されます。

$ curl -i http://www.yahoo.co.jp
HTTP/1.1 301 Redirect
Date: Thu, 09 Apr 2020 14:22:43 GMT
Connection: keep-alive
Via: http/1.1 edge2623.img.djm.yahoo.co.jp (ApacheTrafficServer [c s f ])
Server: ATS
Cache-Control: no-store
Location: https://www.yahoo.co.jp:443/
Content-Type: text/html
Content-Language: en
Content-Length: 1

ここで、stunnelの設定をwww.yahoo.co.jp向けに設定します。
/etc/stunnel/stunnel.conf

[yahoo-web]
client = yes
accept = 127.0.0.1:8000
connect = localhost:8080
protocol = connect
protocolHost = www.yahoo.co.jp:443
verifyChain = yes
CApath = /etc/ssl/certs
checkHost = www.yahoo.co.jp
OCSPaia = yes

stunnelを再起動。

$ sudo systemctl restart stunnel4

確認。

$ curl localhost:8000 -H 'Host: www.yahoo.co.jp' -v -I
* Rebuilt URL to: localhost:8000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> HEAD / HTTP/1.1
> Host: www.yahoo.co.jp
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Cache-Control: private, no-cache, no-store, must-revalidate
Cache-Control: private, no-cache, no-store, must-revalidate
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8
< Date: Thu, 09 Apr 2020 14:25:01 GMT
Date: Thu, 09 Apr 2020 14:25:01 GMT
< Expires: -1
Expires: -1
< Pragma: no-cache
Pragma: no-cache
< Set-Cookie: B=5k0q8u5f8uc1t&b=3&s=93; expires=Sun, 10-Apr-2022 14:25:01 GMT; path=/; domain=.yahoo.co.jp
Set-Cookie: B=5k0q8u5f8uc1t&b=3&s=93; expires=Sun, 10-Apr-2022 14:25:01 GMT; path=/; domain=.yahoo.co.jp
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
X-Frame-Options: SAMEORIGIN
< X-Vcap-Request-Id: eef40b90-5531-4aff-55f1-a9e3203a3eef
X-Vcap-Request-Id: eef40b90-5531-4aff-55f1-a9e3203a3eef
< X-Xss-Protection: 1; mode=block
X-Xss-Protection: 1; mode=block
< Age: 0
Age: 0
< Connection: keep-alive
Connection: keep-alive
< Via: http/1.1 edge1659.img.bbt.yahoo.co.jp (ApacheTrafficServer [c sSf ])
Via: http/1.1 edge1659.img.bbt.yahoo.co.jp (ApacheTrafficServer [c sSf ])
< Server: ATS
Server: ATS
< Set-Cookie: XB=5k0q8u5f8uc1t&b=3&s=93; expires=Sun, 10-Apr-2022 14:25:01 GMT; path=/; domain=.yahoo.co.jp; secure; samesite=none
Set-Cookie: XB=5k0q8u5f8uc1t&b=3&s=93; expires=Sun, 10-Apr-2022 14:25:01 GMT; path=/; domain=.yahoo.co.jp; secure; samesite=none

< 
* Connection #0 to host localhost left intact

今度は、リダイレクトされません。

アクセスログ側も確認してみます。

localhost - - [09/Apr/2020:14:25:00 +0000] "CONNECT www.yahoo.co.jp:443 HTTP/1.1" 200 7047 "-" "-"

OKそうですね。

これで、HTTP CONNECT越しに、クライアントをSSL/TLS化できました、と。

curl+フォワードプロキシで、HTTP CONNECTトンネリング(proxytunnel)する

これは、なにをしたくて書いたもの?

curlの「--proxytunnel」オプションを使うと、フォワードプロキシサーバー+HTTP CONNECTでトンネリングができるというのを知り。

試してみましょうかと。

環境

今回の環境は、Ubuntu Linux 18.04 LTSです。

$ uname -srvmpio
Linux 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

curlのバージョンはこちら。

$ curl --version
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

お題として、Apacheをフォワードプロキシサーバーに、背後に簡単なHTTPサーバーを立て、curlを使ってHTTP CONNECTで
トンネリングできることを確認してみましょう。

ApacheでProxyサーバーを作る

まずは、Apacheをインストールします。

$ sudo apt install apache2

バージョン。

$ apache2 -v
Server version: Apache/2.4.29 (Ubuntu)
Server built:   2020-03-13T12:26:16

プロキシ系のモジュールを有効にします。HTTP CONNECTだけを使うという目線なら「proxy_http」は不要なのですが、今回は
比較用に入れておきました。

$ sudo a2enmod proxy proxy_connect proxy_http

フォワードプロキシの設定は、こんな感じで。8080ポートでリッスンするようにしました。

Listen 8080
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On

  AllowCONNECT 443 8000

  <Proxy *>
    Require host localhost
  </Proxy>

  ErrorLog ${APACHE_LOG_DIR}/proxy_error.log
  CustomLog ${APACHE_LOG_DIR}/proxy_access.log combined
</VirtualHost>

背後のHTTPサーバーはポート8000を使うことにするので、AllowCONNECTで443、8000を指定。

設定したら、Apacheを再起動します。

$ sudo systemctl restart apache2

これで、準備は完了です。

Pythonで簡単なHTTPサーバーを立てる

では、フォワードプロキシ越しにアクセスするHTTPサーバーを用意します。

Pythonで簡単に。

$ echo 'Hello Python!!' > hello.txt
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

単体で確認。

$ curl localhost:8000/hello.txt
Hello Python!!

HTTP CONNECTでトンネリングする

それでは、プロキシ越しにアクセスしてみましょう。最初はなにも考えずにアクセスしてみます。

$ curl -v localhost:8000/hello.txt --proxy http://localhost:8080
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET http://localhost:8000/hello.txt HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 200 OK
< Date: Thu, 09 Apr 2020 12:46:30 GMT
< Server: SimpleHTTP/0.6 Python/3.6.9
< Content-type: text/plain
< Content-Length: 15
< Last-Modified: Thu, 09 Apr 2020 12:41:29 GMT
< Via: 1.1 ubuntu1804.localdomain:8000
< Vary: Accept-Encoding
< 
Hello Python!!
* Connection #0 to host localhost left intact

まあ、ふつうにこうなりますよね。

> GET http://localhost:8000/hello.txt HTTP/1.1

アクセスログ。

localhost - - [09/Apr/2020:12:46:30 +0000] "GET http://localhost:8000/hello.txt HTTP/1.1" 200 261 "-" "curl/7.58.0"

ここで、「--proxytunnel」オプションを付けると挙動が変わります。

$ curl -v localhost:8000/hello.txt --proxy http://localhost:8080 --proxytunnel
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!
* CONNECT phase completed!
> GET /hello.txt HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.6.9
< Date: Thu, 09 Apr 2020 12:46:56 GMT
< Content-type: text/plain
< Content-Length: 15
< Last-Modified: Thu, 09 Apr 2020 12:41:29 GMT
< 
Hello Python!!
* Closing connection 0

HTTP CONNECTでのアクセスになりましたね。

> CONNECT localhost:8000 HTTP/1.1

アクセスログ。

localhost - - [09/Apr/2020:12:47:09 +0000] "CONNECT localhost:8000 HTTP/1.1" 200 276 "-" "curl/7.58.0"

無事、できました、と。

ちなみに、HTTPの場合はhttp_proxy環境変数と「--proxytunnel」オプションでも良いのですが、プロトコルを変えると効かなくなるので、
今回は「--proxy」オプションを使用しています。

$ http_proxy=http://localhost:8080 curl -v http://localhost:8000/hello.txt --proxytunnel

続いて、telnetプロトコルでアクセスしてみましょう。

$ curl -v telnet://localhost:8000 --proxy http://localhost:8080
* Rebuilt URL to: telnet://localhost:8000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!
GET /hello.txt

Hello Python!!

この場合、実は「--proxytunnel」オプションを使用しなくてもHTTP CONNECTでのアクセスとなります。

localhost - - [09/Apr/2020:12:56:47 +0000] "CONNECT localhost:8000 HTTP/1.1" 200 91 "-" "curl/7.58.0"

「--proxytunnel」オプションをつけてもいいんですけど、挙動は変わらずです。

$ curl -v telnet://localhost:8000 --proxy http://localhost:8080 --proxytunnel
* Rebuilt URL to: telnet://localhost:8000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!

curlのオプションの確認と、HTTP CONNECTでのトンネリングの確認ができました、と。