stunnel を使ってTLS通信にする。
stunnel を使うと、TLS非対応な通信をTLS化して暗号化通信に昇格(?)にして、盗聴・改竄・詐称を防止することができる。
stunnel 利用例
stunnel をつかうと、主に3つ(2+1)のパターンを使って通信ができる。
最初の2つが、主な使い方で、3つ目は最初の2つを組み合わせる使い方になるとおもう。
サーバーが非対応のときにTLS化するなら、nginxが同じ用途で使えるので、stunnelをわざわざ使う理由が殆ど無いと思う。
stunnel はTCPとIP:ポート
StunnelはTCPパケットを扱う。TCP以外のパケットを扱うには、別のソフトウェアにするか、VPNを経由させたほうが良さそう。
stunnelはIPアドレスとポートをTCPでリッスンして、パケットを別IPへ転送する感じになる。
stunnel でクライアント通信を暗号化通信に対応させる。
curl で localhost に接続すると、www.google.com
へ通信するTLSプロキシ的な使い方。
設定の流れ
設定を作って、再起動してチェック
sudo apt install stunnel4 sudo -E vim /etc/stunnel/sample-google.conf sudo systemctl restart stunnel4.service sudo systemctl status stunnel4.service
/etc/stunnel/sample-google.conf
設定例
[google-web] client = yes accept = 127.0.0.1:80 connect = www.google.com:443 verifyChain = yes CApath = /etc/ssl/certs checkHost = www.google.com OCSPaia = yes
設定では、stunnel を client
で起動して、www.google.com へ接続を流すようにしている。
接続チェック
curl で localhost につないで、Host: www.google.com
を GET要求する。
curl -v http://www.google.com --resolve www.google.com:80:127.0.0.1
接続ログ
* Added www.google.com:80:127.0.0.1 to DNS cache * Hostname www.google.com was found in DNS cache * Trying 127.0.0.1:80... * Connected to www.google.com (127.0.0.1) port 80 (#0) > GET / HTTP/1.1 > Host: www.google.com > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Date: Mon, 12 Jun 2023 19:27:05 GMT < Expires: -1 < Cache-Control: private, max-age=0 < Content-Type: text/html; charset=ISO-8859-1 < Content-Security-Policy-Report-Only: object-src 'none';base-uri 'self';script-src 'nonce-XwddKS52xPTrqMcdQA2hRA' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:;report-uri https://csp.withgoogle.com/csp/gws/other-hp < P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info." < Server: gws < X-XSS-Protection: 0 < X-Frame-Options: SAMEORIGIN < Set-Cookie: 1P_JAR=2023-06-12-19; expires=Wed, 12-Jul-2023 19:27:05 GMT; path=/; domain=.google.com; Secure < Set-Cookie: AEC=AUEFqZdBYqov6rAlrzNsHUjJlvMjiGUrWhE-IcKDXUR9w-ELQ9AU3GWT8g; expires=Sat, 09-Dec-2023 19:27:05 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax < Set-Cookie: NID=511=iwt3HlrtxpVVdWo3e3xZBhWA1wSaNp8LKiSrv-qDhyvwyjJptMan27Mn6kaaPv210b4cp87FcqSNVeNFz9MLUUZ08qErdSxsBiz9xOqOim2C8rogg_V7wKar_Ej4Kdi3MqeJCZAzpdqthRp_-64VcuZwU_U1qxZXB13nquvDYr8; expires=Tue, 12-Dec-2023 19:27:05 GMT; path=/; domain=.google.com; HttpOnly < Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 < Accept-Ranges: none < Vary: Accept-Encoding < Transfer-Encoding: chunked < <!doctype html><html itemscope=""
ログを見る限り、通常アクセスで www.google.com にアクセスしたときと同じように動作しているのがわかる。
平文通信なサーバーをTLS対応させる。
今度は、平文通信をするサーバーをTLS化する。nginxのリバプロでよく行うあれである。
準備するもの
ウェブサーバーの準備
今回は、pythonを使ってサクッとWEBサーバー起動する。
mkdir sample-web cd sample-web echo 'Hello World.' > hello.txt python3 -m http.server
WEBサーバの起動後にアクセスのテスト
curl http://127.0.0.1:8000/hello.txt
curl でリクエストすると、ファイルの中身がしっかり返される。
Hello World.
証明書の準備。
自己署名なCAを作って、サーバー証明書に署名します。
証明書を配置するディレクトリ作成
mkdir /etc/stunnel/certs/ cd /etc/stunnel/certs/
MyCAを作って。
cd /etc/stunnel/certs/ sudo openssl req -nodes -new -x509\ -days 365\ -subj "/C=JP/ST=Kyoto/O=my/CN=MyCA" \ -newkey rsa:4096\ -keyout myCA.key \ -out myCA.crt
サーバーの証明書を作る
cd /etc/stunnel/certs/ sudo openssl req -new -nodes -x509 \ -subj "/C=JP/ST=Kyoto/L=Kyoto City/O=acid/CN=raspi3.lan" \ -newkey rsa:4096\ -keyout server.priv.key \ -days 365 \ -CA myCA.crt \ -CAkey myCA.key \ -out server.crt
証明書と鍵ができました。
ls -l /etc/stunnel/certs/ total 16 -rw-r--r-- 1 root root 1907 6月 13 04:34 myCA.crt -rw------- 1 root root 3272 6月 13 04:34 myCA.key -rw-r--r-- 1 root root 1948 6月 13 04:34 server.crt -rw------- 1 root root 3272 6月 13 04:34 server.priv.key
stunnel でリバプロする
stunnel の設定ファイルを作成
## 最初の実験用ファイルは片付ける。 sudo mv sample-google.conf sample-google.conf.back ## 今回の実験用ファイルを作る sudo -E vim /etc/stunnel/sample-server.conf
/etc/stunnel/sample-server.conf
[python-webserver] accept = 8443 connect = 8000 cert = /etc/stunnel/certs/server.crt key = /etc/stunnel/certs/server.priv.key
再起動する
sudo systemctl restart stunnel4.service sudo systemctl status stunnel4.service
通信チェック
stunnelに対してHTTPSでアクセスする
## GET / curl -v \ --cacert /etc/stunnel/certs/myCA.crt \ --resolve raspi3.lan:8443:127.0.0.1 \ https://raspi3.lan:8443 ## GET /hello.txt curl \ --cacert /etc/stunnel/certs/myCA.crt \ --resolve raspi3.lan:8443:127.0.0.1 \ https://raspi3.lan:8443/hello.txt
curl通信のログは次の通り
* Added raspi3.lan:8443:127.0.0.1 to DNS cache * Hostname raspi3.lan was found in DNS cache * Trying 127.0.0.1:8443... * Connected to raspi3.lan (127.0.0.1) port 8443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * CAfile: /etc/stunnel/certs/myCA.crt * CApath: /etc/ssl/certs * TLSv1.0 (OUT), TLS header, Certificate Status (22): (略 * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 * ALPN, server did not agree to a protocol * Server certificate: * subject: C=JP; ST=Kyoto; L=Kyoto City; O=acid; CN=raspi3.lan (略 <h1>Directory listing for /</h1>
pythonウェブサーバーの通信ログ
python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 127.0.0.1 - - [13/Jun/2023 04:31:51] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [13/Jun/2023 04:31:55] "GET /hello.txt HTTP/1.1" 200 -
接続元は、127.0.0.1になっている。まぁ仕方ないですね。かりに、別のIPをcurlで指定してもstunnelからパケットが飛んでくるのでウェブサーバーからは識別が困難である。
この手の使い方であれば、nginx のほうが良いと思うけど、nginxなしでもHTTPをHTTPSにラッピングすることができた。
経路をstunnel で暗号化する。
ここまでの2つを組み合わせて、通信経路を暗号化するような使い方にしてみる。
作る設定は次の通り。
stunnel設定を作る
証明書はすでに作っているので、クライアントを追加する。
[python-http-1] accept = 127.0.0.1:8443 connect = 127.0.0.1:8000 cert = /etc/stunnel/certs/server.crt key = /etc/stunnel/certs/server.priv.key [python-http-2] client = yes accept = 127.0.0.1:8001 connect = 127.0.0.1:8443 verifyChain = yes CAfile = /etc/stunnel/certs/myCA.crt checkHost = raspi3.lan OCSPaia = yes
通信をチェックする。
再起動して、通信をチェックする。
再起動
sudo systemctl restart stunnel4.service sudo systemctl status stunnel4.service
通信チェック
stunnelに対してHTTPSでアクセスする
curl -v http://127.0.0.1:8001/hello.txt
結果
* Trying 127.0.0.1:8001... * Connected to 127.0.0.1 (127.0.0.1) port 8001 (#0) > GET /hello.txt HTTP/1.1 > Host: 127.0.0.1:8001 > User-Agent: curl/7.81.0 > Accept: */* > * Mark bundle as not supporting multiuse * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Server: SimpleHTTP/0.6 Python/3.10.6 < Date: Mon, 12 Jun 2023 19:46:21 GMT < Content-type: text/plain < Content-Length: 13 < Last-Modified: Mon, 12 Jun 2023 07:01:51 GMT < Hello World.
上記のように結果が出てきた。 途中の経路TLS化された通信が可能になっているとわかる。
SSH通信をTLSの上に乗せる。
最後に、おまけ。としてSSH over TLSをやってみる。
stunnel の設定を追加する。
[ssh-server-1] accept = 127.0.0.1:2201 connect = 127.0.0.1:22 cert = /etc/stunnel/certs/server.crt key = /etc/stunnel/certs/server.priv.key [ssh-server-2] client = yes accept = 127.0.0.1:2222 connect = 127.0.0.1:2201 verifyChain = yes CAfile = /etc/stunnel/certs/myCA.crt checkHost = raspi3.lan OCSPaia = yes
再起動
sudo systemctl restart stunnel4.service
通信の確認
ssh で ポートを指定して stunnelを通してsshサーバーにアクセスしてみる。
ssh -v 127.0.0.1 -p 2222 OpenSSH_8.9p1 Ubuntu-3ubuntu0.1, OpenSSL 3.0.2 15 Mar 2022 debug1: Reading configuration data /home/takuya/.ssh/config debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files debug1: /etc/ssh/ssh_config line 21: Applying options for * debug1: Connecting to 127.0.0.1 [127.0.0.1] port 2222. debug1: Connection established. (略 debug1: Local version string SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.1 debug1: Remote protocol version 2.0, remote software version OpenSSH_8.9p1 Ubuntu-3ubuntu0.1 debug1: compat_banner: match: OpenSSH_8.9p1 Ubuntu-3ubuntu0.1 pat OpenSSH* compat 0x04000000 debug1: Authenticating to 127.0.0.1:2222 as 'takuya'
問題なくつながるとわかる。
TSLが transport レイヤで有ることがよく分かる。
stunnel のインストール
sudo apt list stunnel* sudo apt install stunnel4
ただし、これは、stunnel4 と名乗りながらも、5である。
/usr/bin/stunnel4 -v [ ] Initializing inetd mode configuration [ ] Clients allowed=500 [.] stunnel 5.63 on aarch64-unknown-linux-gnu platform [.] Compiled/running with OpenSSL 3.0.2 15 Mar 2022 [.] Threading:PTHREAD Sockets:POLL,IPv6,SYSTEMD TLS:ENGINE,OCSP,PSK,SNI Auth:LIBWRAP [ ] errno: (*__errno_location ()) [!] Invalid configuration file name "-v" [!] realpath: No such file or directory (2)
また、stunnel4 と stunnel3 が同時に入っている。
ls -l /usr/bin/stunnel* lrwxrwxrwx 1 root root 8 3月 23 2022 /usr/bin/stunnel -> stunnel4 -rwxr-xr-x 1 root root 2788 3月 23 2022 /usr/bin/stunnel3 -rwxr-xr-x 1 root root 208968 3月 23 2022 /usr/bin/stunnel4
systemdの雛形が用意されていた(サービスファイルはあるけど有効にはなってない)
systemctl status stunnel stunnel.target stunnel4.service
systemctl cat stunnel4.service # /run/systemd/generator.late/stunnel4.service # Automatically generated by systemd-sysv-generator [Unit] Documentation=man:systemd-sysv-generator(8) SourcePath=/etc/init.d/stunnel4 Description=LSB: Start or stop stunnel 4.x (TLS tunnel for network daemons) After=remote-fs.target [Service] Type=forking Restart=no TimeoutSec=5min IgnoreSIGPIPE=no KillMode=process GuessMainPID=no RemainAfterExit=yes SuccessExitStatus=5 6 ExecStart=/etc/init.d/stunnel4 start ExecStop=/etc/init.d/stunnel4 stop ExecReload=/etc/init.d/stunnel4 reload
systemctl cat stunnel.target # /lib/systemd/system/stunnel.target [Unit] Description=TLS tunnels for network services - per-config-file target [Install] WantedBy=multi-user.target
設定ファイルを探す 設定を見てみる。となにもないのでREADMEを読む
cat /etc/stunnel/README
サンプルがあるらしいが、ファイルがない
A sample configuration file with defaults may be found at /usr/share/doc/stunnel4/examples/stunnel.conf-sample
no-man , no-doc でapt 構成してるで何も入ってない。 stunnel.conf-sample のファイルは、github にファイルがあった。
/etc/init.d/stunnel4
を確認すると
cat /etc/init.d/stunnel4 | grep -F .conf
下記のファイルが設定ファイルだとわかる。
FILES="/etc/stunnel/$2.conf" echo >&2 "/etc/stunnel/$2.conf does not exist." FILES="/etc/stunnel/*.conf"
このことから、設定ファイルはFILES="/etc/stunnel/*.conf"
であるとわかる。
他の類似ソフトウェア
- stunnel / tcp をリッスンして転送するだけ
- ssh ポートフォワーディング / 手軽に使えて良いがTLSにならない。
- nginx / リバプロとして使うならこっちの方がいい
- sslh / 同じポート(443)を共有できるので便利
- wireguard / udp を使いたいときはこっちかも
- openvpn / VPN張る方が楽な場合もある。
- ipsec / 経路を透過的に暗号化するのでポートなど他に影響が少ない
TLSで暗号化するなら、nginx が代わりに使えるし、なんならapacheでもいい。
SSHポート転送はputty などに搭載されているしコマンドからも使えるので便利であるが、SSHプロトコルなのでTLSじゃないと通してくれないファイアウォールで止められてしまう。 wireguardは、UDPなので止められてしまう可能性がある。OpenVPNはパケットのフラグがあるので識別されてしまう。ipsecもencapsuleパケットなのでFWで識別が可能。
stunnel はニッチな分野でサクッとポートを開けてデータを転送したいときに使える技術かもしれない。ただしTCP限定である。
また最近のブラウザ設計ではwebsocketやWebRTCなども使えるので、HTTPS内部で割と自由に任意の通信ができる。
まとめ
stunnel を使うと、手軽に経路の暗号化ができて便利だ。
ただもっと手軽なnginxやSSHポートフォワーディングがあるし、通信経路を暗号化するならIPSecやwiregaurdがあるので、これといった用途がないかもしれない。
一番使えそうなのは、「クライアント」かな。「TLS非対応クライアント」をTLSなサーバーに繋ぐという用途ではまだ活躍の機会がありそうだ。
TLS非対応クライアントが残るとしたら業務用機器を使い続ける場合である。FAX複合機のメール転送機能やPOSレジの古いやつなど、機器をアップデートするより途中の経路をstunnelしたほうが良いかもしれない。
SSH over TLSを使うことで、TLSじゃないと通してくれないFWを通過できる可能性が上がるのも面白そうな使い方だと思う。443 ポートでSSHをリッスンしてても、非TLSをHTTP以外拒否する設定が世の中にはあるらしいので、フォワードプロキシなどと組み合わせると通過できるかもしれない。(まぁその時はフォワードプロキシにTLSを入れるだろうけど・・・)
stunnel を使うと、TLSは単なる暗号化レイヤで通信のペイロードはそのままというのがちょっとわかりやすかも。と思う。
stunnelからTLS単体で使うことで通信レイヤが浮かび上がる。初級から中級へレベルアップする教材、通信レイヤの学習素材に向いてるかもしれない。
ポエム
stunnelを調べてるとさ、社内・社外の境界プロキシとの戦いがよく出てくるんだけよね。 社内プロキシが存在したとしても、CONNECTプロキシになってるなら、FWでパケットを絞るのと何ら変わらないよね。CONNECT通しちゃう社内プロキシって人的リソースCPUリソース、電気リソースなど、リソースの無駄だと思う。プロキシを設置する場合は、「社員の作業用」ではなく「運用サーバー群」に対してだよね。作業用だとCONNECTを使わないと不便すぎると思う。仮にCONNECTを制限した場合、世の中のWEBサーバーで使えないものが増えすぎて対応の人的リソースに負荷がやばそう。そうなるとセキュリティの意味がわからなくなるわ。仮にDNSで止めるとしても、ホワイトリスト方式は現実的じゃないしなぁ。DNSがDoHで抜けられるし。PCの管理者権限でChromeの各種設定に制限書けるしか無いよねぇ。stunnelを調べていると、プロキシを超える話と合わせて出てくるんだけどさ、開発系の会社の社員PCにプロキシって「勘違いのセキュリティ」のような気がしてならない。
また最近のブラウザ設計ではwebsocketも使えるので、HTTPSを「フォワードプロキシ」する系の<セキュリティ対策>は限界を迎えつつある。 websocketでWebRTCがChromeがある現代おいて、社内プロキシなんてボトルネック以外の何物でないと思うんだがどうなんだろう。ボトルネックで喪失する社員の時間のほうが目に見えづらいコストなんじゃないのこれ。プロキシを考えるより社員教育のほうが先じゃないの。流出リスクが高いデータを扱う職種をわけるなどリスク・アセスメントをプロキシより先にちゃんとやってるのかなぁ。などと考えながら検索結果を眺めていた。