クライアントに証明書インストール不要な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アドレスチェックがない場合
IPアドレスチェックがある場合
DNSキャッシュサーバがない場合
※AWSのドキュメントによるとDNS serversとあるため、DNSサーバが複数あることがわかる。DNS serversのどのサーバが回答するかによって値が変わってくる
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オプションセット作成画面を開きます。
- ドメイン名はデフォルト値のap-northeast-1.compute.internalを入力します。
- ドメインネームサーバはDNSサーバのIPアドレスを入力します。
- DHCPオプションセットを作成を選択します。
VPCに作成したDHCPオプションを紐づけます
対象のVPCを選択し、アクション->VPCの設定を編集の順に選択します
- DHCPオプションセットは作成したものに変更します。
- 保存を選択します。
各サーバを再起動
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構築後の最終的な構成は下記です。
まとめ
クライアントに証明書をインストールしなくても透過型Proxyを使用できることがわかりましたが、DNSキャッシュサーバを構築する必要があることがわかりました。
また、ProxyとDNSの冗長化も以下の課題があります。
- AWSで実装する場合、ルートテーブルのデフォルトルートにProxyのENIを指定する必要があるため、オートスケーリング不可
- NLBはTCPを終端する必要があるため、透過型Proxyの冗長化として使用不可
- DNSキャッシュサーバは冗長化する場合、キャッシュを引き継ぐ方法を考える必要がある
- 通常のクライアント側にてプライマリ、セカンダリを指定する冗長化ではキャッシュを引き継げない
- BINDでは無理そう
- 通常のクライアント側にてプライマリ、セカンダリを指定する冗長化ではキャッシュを引き継げない