sniproxy(TLSリバースプロキシ)の使い方 (公式ドキュメント通りにできなかった(´・ω・`) )

# sniproxyとは

sniproxyは、TLS接続(httpsなど)をドメイン名によって他のサーバーに振り分けるプロキシサーバーである。基本的にはリバースプロキシとして使うことになる。なお、HTTPも扱える。

すなわち、sniproxyではこういうことができる(雑な図)

```
|クライアント| ─(HTTPS)─ |sniproxy| ┬─(HTTPS)─ |nginxなど|┬─(HTTP)─|サーバー|
                                   │                     └─(HTTP)─|サーバー|
                                   │
                                   ├─(HTTPS)─ |他のサーバー|
                                   └─(HTTP)─ |また他のサーバー|
```
sniproxyと同じ事ができるソフトにはHAProxyなどがある。

リバースプロキシの定番ことnginxでは、ドメイン名毎に異なるポートやIPアドレスにHTTPリクエストを振り分けられるが、TLS接続を「TLSのままドメイン名毎に」振り分けることはできない。(ドメイン毎の振り分けが不要なら ```--with-stream``` をつけてビルドすれば可能。)




## ダウンロード
https://github.com/dlundquist/sniproxy
↑からダウンロードできる。git cloneでもzipでもいい。

## ビルドとインストール

公式ドキュメントの通りにやればできる…はずだったのだが、Fedora 31ではrpmの作成ができなかった。
ビルド(実行ファイル等の作成)まではできたので、その状態から必要なファイルを適切な場所に設置すれば使用自体はできる。

とりあえずこれでビルドする。

```bash
# 必要なパッケージをインストール
dnf install autoconf automake curl gettext-devel libev-devel pcre-devel perl pkgconfig rpm-build udns-devel 

cd (sniproxyをダウンロードしたフォルダ)
./autogen.sh && ./configure && make dist
```

その後、必要なファイルをコピーしていく。

```bash
cd (sniproxyをダウンロードしたフォルダ)
cp src/sniproxy /usr/local/bin
cp debian/logrotate.conf /etc/logrotate.d/sniproxy
cp sniproxy.conf /etc
```

systemdで動かすための設定ファイルを作る。
```/etc/systemd/system/sniproxy.service``` というファイルを作る。
内容は↓のようにする。

```:/etc/systemd/system/sniproxy.service
[Unit]
Description=Transparent TLS proxy
Documentation=https://github.com/dlundquist/sniproxy
After=syslog.target
After=network-online.target

[Service]
Type=forking
ExecStart=/usr/local/bin/sniproxy
Restart=always

[Install]
WantedBy=multi-user.target
```

後は設定完了後に

```
systemctl enable sniproxy
systemctl start sniproxy
```

などとすればよい。

## 設定

```/etc/sniproxy.conf``` を編集する。
多くは公式ドキュメントや、このファイルに最初からコメントで書いてある例に倣えばできる。

### Proxy Protocol

プロキシ先のサーバーに対して、アクセス元のアドレスやポートを伝えるプロトコルである。
もともとはHAProxyが採用した方式である。詳細は↓
https://www.haproxy.com/blog/haproxy/proxy-protocol/

HTTPの場合は ```X-Forwarded-For``` などのヘッダを使えるが、TLSではそういうのが用意されていない。
そのため、TLS接続開始に先立って所定のフォーマットで接続元の情報を送るようになっている。
プロキシ先のサーバーも対応が必要である。(nginxの設定例は後述)

下の「設定例」にもあるが、sniproxyでは80,443ポート宛てではこれを設定しても黙って無視されるのでハマる。
ポート番号を変えれば正常に動作する。

### 設定例

```:/etc/sniproxy.conf

listen [::]:80 {
    proto http
    table http_hosts
}
listen [::]:443 {
    proto tls
    table https_hosts
}

table http_hosts {
    # 80ポートではproxy_protocolが無視されるので別のポートにする
    twigaten.204504byse.info     [::1]:8080 proxy_protocol

    # UNIXソケットでは特に工夫しなくてもproxy_protocolが効く
    204504byse.info      unix:/var/run/nginx_http.sock proxy_protocol
    homoo.social         unix:/var/run/nginx_http.sock proxy_protocol
}

table https_hosts {
    # 443ポートではproxy_protocolが無視されるので別のポートにする
    twigaten.204504byse.info     [::1]:8443 proxy_protocol

    #UNIXソケットでhttpとhttpsを分けるには別のソケットにする必要がある
    204504byse.info      unix:/var/run/nginx_tls.sock proxy_protocol
    homoo.social         unix:/var/run/nginx_tls.sock proxy_protocol
}
```

## nginxの設定

nginxの公式ドキュメントが充実している。
https://docs.nginx.com/nginx/admin-guide/load-balancer/using-proxy-protocol/

注意点は、sniproxyが80,443ポートではproxy_protocolを使ってくれないことくらいである。

```:/etc/nginx/nginx.conf
http {
    # ログなどの設定も必要(省略)

    server {
        # sniproxyは80,443ポートではproxy_protocolを使えないので別のポートにする
        listen 8080 proxy_protocol;
        listen 8443 ssl http2 proxy_protocol;

        set_real_ip_from 1234:5678:90ab:cdef::/64;

        # 実際にはプロキシ先が使うやつだけ入れればよい
        proxy_set_header X-Real-IP       $proxy_protocol_addr;
        proxy_set_header X-Forwarded-For $proxy_protocol_addr;

        # プロキシ先によって適切な設定は異なる
        # ↓例えばmastodon(pumaに送る分)はこうする必要がある
        # proxy_set_header X-Real-IP $remote_addr;
        # proxy_set_header X-Forwarded-For $proxy_protocol_addr;

        # HTTPヘッダの設定もする(省略)
-------------------
    server {
        listen unix:/var/run/nginx.sock proxy_protocol ssl http2;
        # UNIXソケットの場合はこうする
        set_real_ip_from unix:;
```

これで動くはず(｀・ω・´)