概要
Forward Proxyを導入することで以下のメリットを得ることができます。
- DNS lookupをキャッシュして名前解決を高速化
- Targetからのレスポンスをキャッシュして高速化
- TargetがIP制限している場合に、送信元IPを固定するサーバにする
- ↑と逆にTargetの制限をかけられる(社内→インターネットのパターン)
今回Forward Proxyとして有名なSquidを使って簡単な検証を行ってみます。
環境
構成
大まかな構成は以下です。
Vagrantで以下の設定で構築しています。
役割 | IP |
---|---|
Client | 192.168.1.10 |
Forward Proxy | 192.168.1.20 |
Target | 192.168.1.30 |
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.box = "ubuntu/bionic64" config.vm.define :client do |client| client.vm.network "private_network", type: "dhcp" client.vm.network "private_network", ip: "192.168.1.10" end config.vm.define :proxy do |proxy| proxy.vm.network "private_network", type: "dhcp" proxy.vm.network "private_network", ip: "192.168.1.20" end config.vm.define :target do |target| target.vm.network "private_network", type: "dhcp" target.vm.network "private_network", ip: "192.168.1.30" end end
事前準備
インストール
Proxy
aptでsquidをインストールします。
$ sudo apt-get update $ sudo apt-get install squid
Target
Webサーバを構築するためnginxをインストールします。
$ sudo apt-get update $ sudo apt-get install nginx
Squid設定
デフォルトの設定は以下です。
詳細は squid : Optimising Web Delivery を確認してください。
$ grep -v '^#' /etc/squid/squid.conf | grep -v '^$' 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 acl CONNECT method CONNECT acl localnet src 192.168.1.0/24 http_access deny !Safe_ports http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access allow localnet 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 (Release|Packages(.gz)*)$ 0 20% 2880 refresh_pattern . 0 20% 4320
Squidサーバ自体がListenしているのは3128
ポートで、
http_port 3128
変更したい場合はここを修正します。
特定のCIDRからのアクセスを許可する
デフォルトの設定でForward Proxyとして利用しようとするとERR_ACCESS_DENIED
が出てしまうので、特定のCIDRからのアクセスを許可する設定をACLに入れます。
#acl localnet src 10.0.0.0/8 # RFC1918 possible internal network #acl localnet src 172.16.0.0/12 # RFC1918 possible internal network #acl localnet src 192.168.0.0/16 # RFC1918 possible internal network #acl localnet src fc00::/7 # RFC 4193 local private network range #acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines acl localnet src 192.168.1.0/24 # 追加
localnet
というACLを追加しています。
これをhttp_access
ディレクティブで許可します。
# Example rule allowing access from your local networks. # Adapt localnet in the ACL section to list your (internal) IP networks # from where browsing should be allowed http_access allow localnet # 追加 http_access allow localhost
Targetを制限したい
先程は送信元の制限ですが、今度は宛先の制限です。
dstdomain
やurl_regex
を指定することで設定できます。
同じようにACLを登録し、それをhttp_access
ディレクティブで許可します。
acl Whitelist dstdomain example.com test.co.jp http_access allow Whitelist
キャッシュ
Squidはデフォルト設定でオンメモリにキャッシュします。ディスクにはキャッシュしません。
キャッシュのTTLなどはrefresh_pattern
を設定します。
# # Add any of your own refresh_pattern entries above these. # refresh_pattern ^ftp: 1440 20% 10080 refresh_pattern ^gopher: 1440 0% 1440 refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 refresh_pattern (Release|Packages(.gz)*)$ 0 20% 2880 # example lin deb packages #refresh_pattern (\.deb|\.udeb)$ 129600 100% 129600 refresh_pattern . 0 20% 4320
キャッシュしたくない場合
キャッシュしたくない場合は以下の設定を入れます。
cache deny all
再起動時間
デフォルトだと再起動が長いので短くしたい場合は以下
# TAG: shutdown_lifetime time-units # When SIGTERM or SIGHUP is received, the cache is put into # "shutdown pending" mode until all active sockets are closed. # This value is the lifetime to set for all open descriptors # during shutdown mode. Any active clients after this many # seconds will receive a 'timeout' message. #Default: shutdown_lifetime 30 seconds
設定後
設定が完了したらリロードしてconfigを読み込ませます。
$ sudo systemctl reload squid
動作検証
HTTPとHTTPSで検証してみます。
[HTTP] clientからproxyを経由してtargetへアクセス
curlの-x
オプションでproxyを指定してリクエストを送ってみます。
$ curl 192.168.1.30 -x http://192.168.1.20:3128 -v * Rebuilt URL to: 192.168.1.30/ * Trying 192.168.1.20... * TCP_NODELAY set * Connected to 192.168.1.20 (192.168.1.20) port 3128 (#0) > GET http://192.168.1.30/ HTTP/1.1 > Host: 192.168.1.30 > User-Agent: curl/7.58.0 > Accept: */* > Proxy-Connection: Keep-Alive > < HTTP/1.1 200 OK < Server: nginx/1.14.0 (Ubuntu) < Date: Mon, 18 Jan 2021 05:27:48 GMT < Content-Type: text/html < Content-Length: 613 < Last-Modified: Mon, 18 Jan 2021 00:14:55 GMT < ETag: "6004d2ff-265" < Accept-Ranges: bytes < X-Cache: MISS from ubuntu-bionic < X-Cache-Lookup: MISS from ubuntu-bionic:3128 < Via: 1.1 ubuntu-bionic (squid/3.5.27) < Connection: keep-alive ...
キャッシュ
上記検証では
X-Cache: MISS from ubuntu-bionic
とありますが、キャッシュは主に以下のパターンがあります。
X-Cache
もしくはSquidのアクセスログ/var/log/squid/access.log
で確認できます。
X-Cache | Squidアクセスログ | Squidキャッシュ | Originへのリクエスト | Originのレスポンス |
---|---|---|---|---|
MISS from xxx | TCP_MISS | なし | あり | 2xxでリソースを返す |
HIT from xxx | TCP_MEM_HIT | あり | なし | なし |
HIT from xxx | TCP_REFRESH_UNMODIFIED | あるがTTL切れ | あり | 304でリソースは返さない |
MISS from xxx | TCP_REFRESH_MODIFIED | あるがTTL切れ | あり | 2xxでリソースを返す |
[HTTPS] proxyを経由してgoogle.comへアクセス
Squidはデフォルト設定でHTTPSも対応しています。
Connectメソッドを使って透過的にリクエストを送ることができます。
$ curl https://google.com -x http://192.168.1.20:3128 -v * Rebuilt URL to: https://google.com/ * Trying 192.168.1.20... * TCP_NODELAY set * Connected to 192.168.1.20 (192.168.1.20) port 3128 (#0) * allocate connect buffer! * Establish HTTP proxy tunnel to google.com:443 > CONNECT google.com:443 HTTP/1.1 > Host: google.com:443 > User-Agent: curl/7.58.0 > Proxy-Connection: Keep-Alive > < HTTP/1.1 200 Connection established < * Proxy replied 200 to CONNECT request * CONNECT phase completed! * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/certs/ca-certificates.crt CApath: /etc/ssl/certs ... * ALPN, server accepted to use h2 ... > GET / HTTP/2 > Host: google.com > User-Agent: curl/7.58.0 > Accept: */* > * TLSv1.3 (IN), TLS Unknown, Certificate Status (22): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS Unknown, Unknown (23): * Connection state changed (MAX_CONCURRENT_STREAMS updated)! * TLSv1.3 (OUT), TLS Unknown, Unknown (23): * TLSv1.3 (IN), TLS Unknown, Unknown (23): * TLSv1.3 (IN), TLS Unknown, Unknown (23): < HTTP/2 301 < location: https://www.google.com/ < content-type: text/html; charset=UTF-8 < date: Mon, 18 Jan 2021 06:16:51 GMT < expires: Wed, 17 Feb 2021 06:16:51 GMT < cache-control: public, max-age=2592000 < server: gws < content-length: 220 < x-xss-protection: 0 < x-frame-options: SAMEORIGIN ...
シーケンス
イメージとしては以下です。
ref: Thick Client Proxying - Part 6: How HTTP(s) Proxies Work
最初はパケットのIPヘッダをいじってNATのように転送しているのかな?と思いましたが、
$ sudo tcpdump -n -i any -w pcap-proxy
でパケットキャプチャしてみたところ
- Client <-> Proxy
- Proxy <-> Target
でパケットサイズは大きく異なるのでパケットのデータ部分をProxy内でよろしくやっているようです。
Connectメソッドの設定は?
443ポート以外の接続はデフォルトで以下の設定が入っており禁止されています。
acl SSL_ports port 443 acl CONNECT method CONNECT http_access deny CONNECT !SSL_ports
まとめ
SquidをForward Proxyサーバとして利用してみました。
その際によくある設定例の紹介と、HTTP/HTTPSでの簡単な検証をしてみました。