クライアントに証明書インストール不要なSSL対応透過型プロキシを構築する

Pocket

クライアントに証明書インストール不要なSSL対応透過型プロキシを構築する

透過型ProxyをSSL対応するためにはクライアントにSSL証明書をインストールする必要がある。と思っていたのですが、
serverfaultのこの質問で証明書をインストールしないでもできるとあったので検証してみました。

環境

OS:Ubuntu 22.04
基盤:AWS

構成

Proxyサーバ構築

インストール

普通にsudo apt install squidでSquidをインストールするとコンパイルオプション--with-opensslがないものがインストールされてしまうのでsudo apt install squid-opensslにてインストールします。

sudo apt update
sudo apt install squid-openssl

コンフィグ編集

コンフィグ編集するためviで開きます。

sudo vi /etc/squid/squid.conf

コメントが大量(8000行以上)にあるので削除コマンドをviから実行します。

:%s/^\s*\n//g
:%s/^\s*#.*\n//g

下記をファイル先頭から追加します。

acl allowlist_ssl ssl::server_name "/etc/squid/allow_ssl.txt" # NOT dstdomain
acl step1 at_step SslBump1
ssl_bump peek step1
ssl_bump splice allowlist_ssl # allow everything in the allowlist
ssl_bump terminate all # block everything else
https_port 3129 intercept ssl-bump cert=/etc/squid/dummy.pem
sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/log/squid/ssl_db -M 4MB

http_access allow localhost managerを探してその下の行に下記を追記します。

http_access allow localnet

更新後のコンフィグは下記になります。

acl allowlist_ssl ssl::server_name "/etc/squid/allow_ssl.txt" # NOT dstdomain
acl step1 at_step SslBump1
ssl_bump peek step1
ssl_bump splice allowlist_ssl # allow everything in the allowlist
ssl_bump terminate all # block everything else
https_port 3129 intercept ssl-bump cert=/etc/squid/dummy.pem
sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/log/squid/ssl_db -M 4MB

acl localnet src 0.0.0.1-0.255.255.255  # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8             # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10          # RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16         # RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12          # RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16         # RFC 1918 local private network (LAN)
acl localnet src fc00::/7               # RFC 4193 local private network range
acl localnet src fe80::/10              # RFC 4291 link-local (directly plugged) machines
acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access allow localnet
http_access deny manager
include /etc/squid/conf.d/*.conf
http_access allow localhost
http_access deny all
http_port 3128
coredump_dir /var/spool/squid
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
refresh_pattern .               0       20%     4320

Squidの許可リストを作成します。

sudo vi /etc/squid/allow_ssl.txt

下記の内容にします。

www.google.com

IPフォワーディング有効化

カーネルパラメータを設定してIPフォワーディングを有効にします。

sudo vi /etc/sysctl.conf
# 下記を追記
net.ipv4.ip_forward = 1
# 更新後カーネルパラメータ反映
sysctl -p

iptables port変換設定

sudo iptables -t nat -A PREROUTING -p tcp -m tcp –dport 443 -j REDIRECT –to-ports 3129

iptables設定永続可

再起動するとiptablesの設定が消えてしまうためiptables-persistentをインストールして設定を永続化します。

sudo apt-get install iptables-persistent
sudo netfilter-persistent save

ダミー証明書作成

実際にはSSL通信を複合しませんが設定上必要なためダミーの証明書を作成します。

openssl req -new -newkey rsa:4096 -sha256 -days 365 -nodes -x509 -keyout dummy.pem -out dummy.pem
# ダミーなので全部空エンターでOK
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

ダミー証明書格納

作成したファイルをsquidディレクトリに格納します。

sudo mv dummy.pem /etc/squid/

SSL証明書キャッシュファイル

SSL証明書キャッシュファイルも必要なので作成します。

sudo /usr/lib/squid/security_file_certgen -c -s /var/log/squid/ssl_db -M 4MB

squidコンフィグチェック

構文エラーがないことを確認します。

squid -k check

インスタンスの送信元/送信先チェックを無効化

AWS VPC上ではデフォルトではNATができないためソース/宛先チェックを無効にします。

Squidサービスを再起動する

自動起動が有効であることも確認します。

sudo systemctl is-enabled squid

Squidを再起動します 。

systemctl restart squid

プライベートサブネットのデフォルトルートを設定する

クライアントのいるPrivate SubnetのルートテーブルのデフォルトルートのターゲットをProxyに設定します。

接続確認

これで接続できるかと思いPrivateからテストしましたが接続できませんでした。

$ curl -I https://www.google.com
curl: (35) error:0A00010B:SSL routines::wrong version number

原因

調べた結果、これはProxyがパケットを受け取った時にProxyも名前解決して、Proxyの名前解決結果と受け取ったパケットの宛先IPアドレスが一致しない場合、パケットを破棄するためと分かりました。 (まれにProxyとクライアントのIPアドレス一致して成功する。)
なぜ、こんなことをしているかというと実IPアドレスがドメインと違うサイトに接続できてしまうと悪意のある相手と通信できてしまうからです。
解決するにはDNSキャッシュサーバを用意してProxyとクライアントが同じIPアドレスを名前解決できるようにします。

IPアドレスチェックがない場合

Host Header Forgery

IPアドレスチェックがある場合

Host Header Forgery2

DNSキャッシュサーバがない場合
AWSのドキュメントによるとDNS serversとあるため、DNSサーバが複数あることがわかる。DNS serversのどのサーバが回答するかによって値が変わってくる

DNSキャッシュサーバがない場合

DNSキャッシュサーバがある場合

DNSキャッシュサーバがある場合

DNS構築

BIND インストール

新たにインスタンスを立ち上げてbindを構築します

sudo apt update
sudo apt install bind9
sudo vi /etc/bind/named.conf.options

下記の通りにコンフィグを編集します。

options {
        directory "/var/cache/bind";
        listen-on port 53 { VPCのCIDR; 127.0.0.0/24; };
        dump-file "/var/cache/bind/cache_dump.db";
        statistics-file "/var/cache/bind/named_stats.txt";
        memstatistics-file "/var/cache/bind/named_mem_stats.txt";
        recursing-file "/var/cache/bind/named.recursing";
        secroots-file "/var/cache/bind/named.secroots";
        allow-query { VPCのCIDR; 127.0.0.0/24; };
        allow-recursion { VPCのCIDR; 127.0.0.0/24; };
        recursion yes;
        bindkeys-file "/etc/named.root.key";
        managed-keys-directory "/var/named/dynamic";
        pid-file "/run/named/named.pid";
        session-keyfile "/run/named/session.key";
        dnssec-validation yes;
};
logging {
        channel query_log {
                file "/var/log/named/named.log" versions 10 size 20M;
                severity dynamic;
                print-time yes;
                print-severity yes;
                print-category yes;
        };
        category queries { query_log; };
};

ログとDNSSEC用ディレクトリを作成します。

sudo mkdir /var/log/named
sudo mkdir /var/cache/bind/dynamic
sudo chown -R bind:bind /var/log/named
sudo chown -R bind:bind /var/cache/bind/dynamic

コンフィグの構文チェックを行います。

named-checkconf

自動起動を確認します。
※自動起動が最初から有効でしたが念のため

sudo systemctl is-enabled named

BINDを再起動します 。

sudo systemctl restart named

DHCPオプションセットを設定

デフォルトではAmazon Provided DNSを参照しているため、構築したDNSサーバを参照するようにDHCPオプションセットを設定します。
DHCPオプションセット作成画面を開きます。

DHCPオプションセット

  1. ドメイン名はデフォルト値のap-northeast-1.compute.internalを入力します。
  2. ドメインネームサーバはDNSサーバのIPアドレスを入力します。
  3. DHCPオプションセットを作成を選択します。

DHCPオプションセット

VPCに作成したDHCPオプションを紐づけます
対象のVPCを選択し、アクション->VPCの設定を編集の順に選択します

VPCの設定を編集

  1. DHCPオプションセットは作成したものに変更します。
  2. 保存を選択します。
    DHCPオプションセット変更.png

各サーバを再起動

DHCPオプションセットで設定したDNSサーバ設定を反映するため各サーバを再起動します。

DNS動作確認

各サーバのDHCPオプションが反映されていることを確認します。
nameserverの値が反映されてればOKです。

cat /run/systemd/resolve/resolv.conf

# resolv.confの内容
nameserver DNSサーバのIPアドレス
search ap-northeast-1.compute.internal

digコマンドを使って名前解決します。
※127.0.0.53へ問い合わせをなぜかするがBINDのログに問い合わせが来ているます。AWS Provided DNS経由でBINDに問い合わせが来ている?

$ dig www.google.com

; <<>> DiG 9.18.1-1ubuntu1.3-Ubuntu <<>> www.google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6124
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;www.google.com.                        IN      A

;; ANSWER SECTION:
www.google.com.         300     IN      A       142.250.196.132

;; Query time: 44 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Mon Feb 13 07:21:22 UTC 2023
;; MSG SIZE  rcvd: 59

DNSサーバのBINDのログを確認します。以下のようなログがあればOKです。

13-Feb-2023 07:26:55.724 queries: info: client @0x7fc1501fe0f8 172.31.27.33#51250 (www.google.com): query: www.google.com IN A +E(0) (172.31.28.171)

接続確認

DNS構築後、クライアントからテストすると無事に接続できました。

$ curl -I https://www.google.com
HTTP/2 200
content-type: text/html; charset=ISO-8859-1
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
date: Mon, 13 Feb 2023 07:29:21 GMT
server: gws
x-xss-protection: 0
x-frame-options: SAMEORIGIN
expires: Mon, 13 Feb 2023 07:29:21 GMT
cache-control: private
set-cookie: 1P_JAR=2023-02-13-07; expires=Wed, 15-Mar-2023 07:29:21 GMT; path=/; domain=.google.com; Secure
set-cookie: AEC=ARSKqsLA1ySDnuZGwOc87j7sKna3v_PWUTGFW1MtL4V4jrc0T81Jchw3wA; expires=Sat, 12-Aug-2023 07:29:21 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax
set-cookie: NID=511=W187sQFCp35jKhV6y-CZNzg2SMy81dzy3qT-8a5ZTWvhyzv8E9bA_hd0HM9lL264lVqvV6xbIPzgbM40CFLmhWBjzUFvISynEN6CobrxI2TAZJYUikF_KMqUPes6HHx-WUzVkLozhQwwvlo-ULIy2re6263Ov5zrLKpVQKX672M; expires=Tue, 15-Aug-2023 07:29:21 GMT; path=/; domain=.google.com; HttpOnly
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000

最終的な構成

DNS構築後の最終的な構成は下記です。

構成2

まとめ

クライアントに証明書をインストールしなくても透過型Proxyを使用できることがわかりましたが、DNSキャッシュサーバを構築する必要があることがわかりました。
また、ProxyとDNSの冗長化も以下の課題があります。

  • AWSで実装する場合、ルートテーブルのデフォルトルートにProxyのENIを指定する必要があるため、オートスケーリング不可
  • NLBはTCPを終端する必要があるため、透過型Proxyの冗長化として使用不可
  • DNSキャッシュサーバは冗長化する場合、キャッシュを引き継ぐ方法を考える必要がある
    • 通常のクライアント側にてプライマリ、セカンダリを指定する冗長化ではキャッシュを引き継げない
      • BINDでは無理そう
Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です