それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

stunnel を使ってTLS通信を肩代わりする

stunnel を使ってTLS通信にする。

stunnel を使うと、TLS非対応な通信をTLS化して暗号化通信に昇格(?)にして、盗聴・改竄・詐称を防止することができる。

stunnel 利用例

stunnel をつかうと、主に3つ(2+1)のパターンを使って通信ができる。

  1. TLS非対応なクライアントをTLS通信させる
  2. サーバーが非TLSのときにTLS通信させる
  3. 途中の経路をTLS通信にすることができる。

最初の2つが、主な使い方で、3つ目は最初の2つを組み合わせる使い方になるとおもう。

サーバーが非対応のときにTLS化するなら、nginxが同じ用途で使えるので、stunnelをわざわざ使う理由が殆ど無いと思う。

stunnel はTCPとIP:ポート

StunnelはTCPパケットを扱う。TCP以外のパケットを扱うには、別のソフトウェアにするか、VPNを経由させたほうが良さそう。

stunnelはIPアドレスとポートをTCPでリッスンして、パケットを別IPへ転送する感じになる。

stunnel でクライアント通信を暗号化通信に対応させる。

curllocalhost に接続すると、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 へ接続を流すようにしている。

接続チェック

curllocalhost につないで、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>

ちゃんと、curlHTTPS通信ができているとわかる。

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'

問題なくつながるとわかる。

TLS通信の上に、SSHを載せることができた。

TSLが transport レイヤで有ることがよく分かる。

stunnel のインストール

debian / ubuntu の場合のインストール

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がある現代おいて、社内プロキシなんてボトルネック以外の何物でないと思うんだがどうなんだろう。ボトルネックで喪失する社員の時間のほうが目に見えづらいコストなんじゃないのこれ。プロキシを考えるより社員教育のほうが先じゃないの。流出リスクが高いデータを扱う職種をわけるなどリスク・アセスメントをプロキシより先にちゃんとやってるのかなぁ。などと考えながら検索結果を眺めていた。

参考資料