stunnel で SSH over SSL する (forward proxy 経由も可)

# SSH したいのにできない、そんな時
Firewall で制限されているなどの理由で外部に ssh できない場合がある。
直接通信は NG で HTTP プロキシサーバ (forward proxy) 経由の通信しか通らない場合などもある。

多くの環境では HTTPS を通す意図で tcp/443 の SSL は通る。
その場合は stunnel を使えば SSL の上で SSH できる。(forward proxy 経由にすることもできる)

# 注意点
SSH は TCP ハンドシェイク後にサーバ側から開始するプロトコルなので、スキャンされるとその先で SSH を待ち受けていることがすぐに分かる。

ここでは オレオレCAライクな証明書とその配下にサーバ証明書・クライアント証明書を用意し、クライアント証明書検証を必須にすることでスキャンしても普通の SSL であることしか分からないようにする。
(検証結果が NG なら SSL のレイヤでエラーになって sshd に届けない)

証明書の revoke や CRL など、まともな CA の作業は扱わない。

# 環境
CentOS で server として stunnel を動かし、
Cygwin で client として stunnel を動かして CentOS に SSH する。
stunnel の間にプロキシがあっても問題ない。

```
┃ クライアントマシン(Cygwin) ┃          ┃   サーバマシン (CentOS)      ┃
┃ ssh =(SSH)=> stunnel:9999 ===(SSL)==> stunnel:443 =(SSH)=> sshd:22 ┃


```


# インストール
CentOS なら EPEL リポジトリから yum でインストールできるし、
Cygwin なら setup.exe でインストールできる。


# stunnel の設定

## 証明書などの準備
```
CA(Root) ┳ サーバ証明書
         ┗ クライアント証明書
```

中間CA なし 2階層のみ。
KeyUsage とか、その他細かいことは言わない。

```console
$# CA 証明書
$ openssl genrsa -aes256 -out ca.key 2048
$ openssl req -new -key ca.key -out ca.csr -subj "/CN=TestCA"
$ openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt -days 7300

$# サーバ証明書 (サーバ証明書の CN は検証していないようだが、FQDN にしておく)
$ openssl genrsa -aes256 -out server.key 2048
$ openssl req -new -key server.key -out server.csr -subj "/CN=example.com"
$ openssl x509 -req -CA ca.crt -CAkey ca.key -set_serial 1 -in server.csr -out server.crt -days 3650

$# クライアント証明書
$ openssl genrsa -aes256 -out client.key 2048
$ openssl req -new -key client.key -out client.csr -subj "/CN=TestClient"
$ openssl x509 -req -CA ca.crt -CAkey ca.key -set_serial 2 -in client.csr -out client.crt -days 3650

```

## サーバ設定 (CentOS)
iptables を設定している場合は tcp/443 を受け付けられるようにする必要がある。
(この方法で SSH する場合は) tcp/22 は localhost からのみ許可すれば良く、外部から受け付ける必要は無い。

yum インストールしても起動スクリプトは付属していないので、必要なら自分で書く。

```console
## 事前に用意した CA証明書, サーバ証明書とその秘密鍵を設置
## 秘密鍵は mode を 400 にしておく
# ls /etc/stunnel
ca.crt  server.crt  server.key

## パスフレーズは解除しておく
# openssl rsa -in server.key -out server-nopass.key

## 設定ファイルを作成
# vi /etc/stunnel/stunnel.conf
cert = /etc/stunnel/server.crt
key = /etc/stunnel/server-nopass.key
sslVersion = TLSv1
chroot = /var/run/stunnel
setuid = nobody
setgid = nobody
pid = /stunnel.pid
socket = l:TCP_NODELAY=1
socket = r:TCP_NODELAY=1
compression = zlib
verify = 2
CAfile = /etc/stunnel/ca.crt
debug = authpriv.info
[ssh over ssl]
accept  = 443
connect = 22
TIMEOUTclose = 0

## chroot の準備
# mkdir /var/run/stunnel
# chown nobody:nobody /var/run/stunnel

## stunnel を起動
# stunnel /etc/stunnel/stunnel.conf
```


## クライアント設定 (Cygwin)
```console
$# 事前に用意した CA証明書, クライアント証明書とその秘密鍵を設置
$# 秘密鍵は mode を 400 にしておく
$ ls /etc/stunnel
ca.crt  client.crt client.key

$# 設定ファイルを作成
$ vi /etc/stunnel/stunnel.conf
debug = info
output = /var/log/stunnel.log
cert = /etc/stunnel/client.crt
key = /etc/stunnel/client.key
verify = 2
CAfile = /etc/stunnel/ca.crt
options = NO_SSLv2
[ssh_over_ssl]
client = yes
accept = 9999
connect = example.com:443
```


### forward proxy を通す場合
proxy が httpproxy という名前でアクセスできて 8888 を Listen しているとして、
[ssh_over_ssl] の部分を以下のようにする

```
[ssh_over_ssl]
client = yes
accept = 9999
protocol = connect
protocolHost = example.com:443
connect = httpproxy:8888
```

### SSH over SSL でログイン

```console
$ stunnel /etc/stunnel/stunnel.conf
$ ssh -p 9999 localhost
```


# もう少し動作テスト

つながったから終わり、にすると痛い目を見るかもしれない。

証明書の準備でやったことを別のファイル名で一通り実施し、
異なるチェーンの CA, サーバ, クライアントの証明書を作る。

stunnel サーバとクライアントには別々のチェーンの証明書を設定してつながらないことを確認する。

Wireshark の画面キャプチャは 192.168.37.7 がクライアントで 192.168.37.250 がサーバ。

## クライアント認証の確認
 * 適当なクライアント証明書では接続できないこと、を確認

以前 openssl s_server で verify オプションつけても切断してくれなかった思い出があるので、切断まで確認する。

![ssl_clientauth.jpg](https://qiita-image-store.s3.amazonaws.com/0/44206/3397b766-59a3-6f08-66fb-f1e8575d2c4c.jpeg)

サーバ側が Fatal な Alert Protocol を投げて RST も投げている。
stunnel.conf で verify をコメントアウトすると認証できなくてもつながった。


## サーバ認証の確認
 * 適当なサーバ証明書だったら接続しないこと、を確認

![ssl_serverauth.jpg](https://qiita-image-store.s3.amazonaws.com/0/44206/d73d694a-6706-2e6d-1898-c5b76967ca4f.jpeg)


RST はサーバ側が先だったが、Fatal な Alert Protocol はクライアントから投げているので、多分大丈夫。
stunnel.conf で verify をコメントアウトすると認証できなくてもつながった。


## ところで...

パケットを見てて思い出したが、ハンドシェイク時に Root CA の証明書も送信するということが普通に行われている。
世間一般のサーバでもそういう挙動をよく見かける気がする。

クライアントは受信してもそれを信頼してはいけないし、送信すること自体無駄な気がするが、何か送信すべき理由があるのだろうか。