Carpe Diem

備忘録

SquidをForward Proxyサーバとして使う

概要

Forward Proxyを導入することで以下のメリットを得ることができます。

  • DNS lookupをキャッシュして名前解決を高速化
  • Targetからのレスポンスをキャッシュして高速化
  • TargetがIP制限している場合に、送信元IPを固定するサーバにする
  • ↑と逆にTargetの制限をかけられる(社内→インターネットのパターン)

今回Forward Proxyとして有名なSquidを使って簡単な検証を行ってみます。

環境

構成

大まかな構成は以下です。

f:id:quoll00:20210119055534p:plain

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を制限したい

先程は送信元の制限ですが、今度は宛先の制限です。
dstdomainurl_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へアクセス

f:id:quoll00:20210119060404p:plain

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
...

シーケンス

イメージとしては以下です。

f:id:quoll00:20210118155325p:plain

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内でよろしくやっているようです。

f:id:quoll00:20210119055917p:plain

Connectメソッドの設定は?

443ポート以外の接続はデフォルトで以下の設定が入っており禁止されています。

acl SSL_ports port 443
acl CONNECT method CONNECT
http_access deny CONNECT !SSL_ports

まとめ

SquidをForward Proxyサーバとして利用してみました。
その際によくある設定例の紹介と、HTTP/HTTPSでの簡単な検証をしてみました。

参考