社内から社外にhttp(s)で出る場合に、何かある度に認証プロキシに対して認証情報を入れる必要があって邪魔だったため、mac内に立てたプロキシに認証を代替してもらうようにしてみました。
ついでに、プロキシの設定をいろんな箇所に設定するのも面倒だったため、透過プロキシの設定もしてみました。ただし、最終的にはHTTPSの場合には独自の証明書を設定する必要があったため、HTTPのみ透過プロキシを介するようにしています。
環境
- プロキシ認証が必要な環境
- OS X El Capitan
- Homebrew 0.9.9
手順
squidユーザーの追加
プロキシとして動作するsquidプロセスをsquidユーザーで動作させたいため、squidユーザーを「システム環境設定」の「ユーザとグループ」から追加します。
squidのインストール
Homebrewでインストールします。
$ brew install squid
==> Downloading https://homebrew.bintray.com/bottles/squid-3.5.15.el_capitan.bottle.1.tar.gz
Already downloaded: /Library/Caches/Homebrew/squid-3.5.15.el_capitan.bottle.1.tar.gz
==> Pouring squid-3.5.15.el_capitan.bottle.1.tar.gz
==> Caveats
To have launchd start squid at login:
mkdir -p ~/Library/LaunchAgents
ln -sfv /usr/local/opt/squid/*.plist ~/Library/LaunchAgents
Then to load squid now:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.squid.plist
==> Summary
🍺 /usr/local/Cellar/squid/3.5.15: 2,061 files, 9.5M
squid.confの編集
デフォルトの設定から以下を変更しています。
$ diff $(brew --prefix)/etc/squid.conf.default $(brew --prefix)/etc/squid.conf
4a5,6
> cache_effective_user squid
>
14a17
> acl SSL_ports port 2376
59c62,63
< http_port 3128
---
> http_port 8080
> http_port 8081 transparent
62a67,69
> no_cache deny all
> cache_dir null /dev/null
> cache_store_log none
73a81,96
>
> visible_hostname <ホスト名>
> hosts_file /etc/hosts
>
> acl localdomain dstdomain .example.co.jp
> acl localdomain dstdomain .example.com
> acl localdomain dstdomain .example.local
> acl localseg dst 10.0.0.0/8
> acl localseg dst 172.16.0.0/12
> acl localseg dst 192.168.0.0/16
> always_direct allow localdomain localseg
>
> cache_peer <上位のプロキシサーバ> parent 8080 0 no-query login=<プロキシユーザー>:<プロキシパスワード>
> cache_peer_access <上位のプロキシサーバ> deny localdomain
> cache_peer_access <上位のプロキシサーバ> deny localseg
> never_direct allow !localdomain !localseg
主な設定項目の説明です。
-
cache_effective_user squid
:
squidプロセスをsquidユーザーで動作させるための設定です。後述する透過プロキシのためのポートフォワード設定で、ユーザーに応じて転送先を変更するために追加しています。 -
acl SSL_ports port 2376
:
デフォルトでは、ポート443以外のHTTPS通信を許さない設定となっていたのですが、ポート2376で動作するプログラムがいたため、追加しています。 -
http_port 8080
:
通常のプロキシをポート8080で動作させています。 -
http_port 8081 transparent
:
透過プロキシをポート8081で動作させています。 -
always_direct allow localdomain localseg
:
ローカルのドメイン(この例では.example.(co.jp|com|local)
)とローカルのセグメント(10.0.0.0/8, 172.16.0.0.12, 192.168.0.0/16)については、上位のプロキシを介さずに直接通信することを許可します。 -
cache_peer <上位のプロキシサーバ> 8080 0 no-query login=<プロキシユーザー>:<プロキシパスワード>
:
上位のプロキシの転送設定です。login=
にてプロキシ認証用のユーザーとパスワードを指定しています。 -
cache_peer_access <上位のプロキシサーバ> deny localdomain|localseg
: ローカルドメインとローカルセグメントについては、上位のプロキシサーバへの転送を拒否します (4/14追記) -
never_direct allow !localdomain !localseg
: ローカルドメインとローカルセグメント以外は、直接接続を拒否します (4/14追記)
4/14追記:
allow_direct
を指定しておけば直接接続されるかと思っていましたが、実際にはcache_peer_access
で拒否しない限りは上位のプロキシに接続しに行ってしまっていたため、修正しました。squidの設定、難しい。。。
squidの起動設定
インストール時のメッセージにはユーザーのエージェントとして起動するように記載されていましたが、squid
ユーザーで動作させるためにデーモンとして設定しています。
起動のための設定ファイルをシステムにコピーします。
$ sudo cp $(brew --prefix squid)/homebrew.mxcl.squid.plist /Library/LaunchDaemons
LaunchDaemonにsquidをデーモンとして登録します。
$ sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.squid.plist
システムのプロキシ設定
システム環境設定のネットワークのHTTP(S)プロキシの設定にlocalhost:8080
を設定すれば、認証情報を入力することなくプロキシを利用できるようになります。
これで当初の目的は達したのですが、VMやらコンテナやらを作成するたびにhttp_proxy
環境変数を設定するというのも面倒なので、以降では透過プロキシを設定します。
ポートフォワードの設定
80ポートに対して外部に出て行く通信を、squidに転送するように設定します。これを実現するために、pf (Packet Filter)を利用します。
pf.confの編集
/etc/pf.conf
から転送用の設定を読み込むようにします。
(5/13修正) 以下の行を/etc/pf.conf
に追加しています。rdr
ルールの後ろに各種フィルタールール(anchor
で読み込まれるpass
等のルール。)を記載する必要があるため、もし他のルールが存在している場合、anchor
等の前にrdr-anchor
を記載する必要があります。
# ...
rdr-anchor "proxy"
# ...
anchor "proxy"
load anchor "proxy" from "/etc/pf.anchors/proxy"
/etc/pf.anchors/proxy
を以下のように編集します。
ext_if = "en0"
table <rfc1918> const { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }
rdr on lo0 inet proto tcp from any to any port 80 -> lo0 port 8081
pass out quick on $ext_if inet proto tcp from any to <rfc1918>
pass out on $ext_if route-to lo0 inet proto tcp from any to any port 80 user != squid
この設定により、以下のような動作をします。
- NIC
en0
上のポート80に対して外部に出て行く通信を、ループバックインターフェースlo0
に転送します。 - ループバックインターフェース
lo0
上のポート80に対する通信をsquid
に転送します。
単純に外部に出て行く通信をsquid
に転送するだけだと、squid
から出て行く通信も再度転送されてしまうため、user != squid
の条件によりsquid
ユーザーで動作するプロセスからの通信は転送しないように設定しています。
(4/15追記) また、pfからsquidに転送後、上位プロキシに転送せずに直接接続した場合にうまく接続できていなかったようなので、社内 (192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8) への通信はsquidに転送しないような設定も追加しています。この追加により、squidプロセスをsquidユーザーで動作させる必要がなくなっている気もしますが。
pfの起動設定
pfをデーモンとして起動させるには、自分で設定を記述する必要があります。これは参考に記載したブログの設定をほぼそのまま利用させていただきました。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>pf.plist</string>
<key>Program</key>
<string>/sbin/pfctl</string>
<key>ProgramArguments</key>
<array>
<string>/sbin/pfctl</string>
<string>-e</string>
<string>-f</string>
<string>/etc/pf.anchors/proxy</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceDescription</key>
<string>FreeBSD Packet Filter (pf) daemon</string>
<key>StandardErrorPath</key>
<string>/var/log/pf.log</string>
<key>StandardOutPath</key>
<string>/var/log/pf.log</string>
</dict>
</plist>
LaunchDaemonにpfをデーモンとして登録します。
$ sudo launchctl load /Library/LaunchDaemons/pf.plist
ファイアウォールの設定
本環境にはセキュリティ対策ソフトとしてMcAfee Endpoint Protection for Macが入っているのですが、どこかの通信をブロックしてしまっているようです。とりあえずはファイアウォールを無効化することで回避しましたが、うまくルールを設定できたら、情報を追記したいと思います。
DNSの設定
弊社だけかもしれませんが、社内用のDNSサーバに問い合わせても社外のドメイン名を解決してくれません。普通にプロキシを設定している場合には特にこれで困ることはないのですが、透過プロキシの場合には、社外の名前も解決できないとうまく動作しませんでした。
そこで、dnsmasqを使って社内と社外の両方の名前を解決してくれるdnsサーバを立てました。なお、以下ではうまくいきませんでした。
- 単純にネットワーク設定に複数のDNSサーバを設定する
これは、単純に優先DNSサーバが応答しない時に代替DNSサーバが利用されるというだけなので、この目的では使えません。 -
/etc/resolver
配下に社内用ドメインに対するDNSサーバの設定を記載する
/etc/resolver
配下に、example.com
(社内ドメイン)という名前のファイルに社内用のDNSサーバを設定しておき、全体の設定には社外用のDNSサーバを設定してみました。これで問題ないように見えるのですが、なぜかうまく動作しませんでした。
dnsmasqのインストール
Homebrewでインストールします。
$ brew install dnsmasq
==> Downloading https://homebrew.bintray.com/bottles/dnsmasq-2.75.el_capitan.bottle.tar.gz
Already downloaded: /Library/Caches/Homebrew/dnsmasq-2.75.el_capitan.bottle.tar.gz
==> Pouring dnsmasq-2.75.el_capitan.bottle.tar.gz
==> Caveats
To configure dnsmasq, copy the example configuration to /usr/local/etc/dnsmasq.conf
and edit to taste.
cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
To have launchd start dnsmasq at startup:
sudo cp -fv /usr/local/opt/dnsmasq/*.plist /Library/LaunchDaemons
sudo chown root /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
Then to load dnsmasq now:
sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
==> Summary
🍺 /usr/local/Cellar/dnsmasq/2.75: 7 files, 492.3K
dnsmasq.confの編集
サンプルをベースに設定を変更します。
$ cp $(brew --prefix dnsmasq)/dnsmasq.conf.example $(brew --prefix)/etc/dnsmasq.conf
以下のような設定をしました。
# Never forward plain names (without a dot or domain part)
domain-needed
# Never forward addresses in the non-routed address spaces.
bogus-priv
# Change this line if you want dns to get its upstream servers from
# somewhere other that /etc/resolv.conf
resolv-file=/etc/dnsmasq.resolv.conf
# Add other name servers here, with domain specs if they are for
# non-public domains.
#server=/localnet/192.168.0.1
server=/<社内ドメイン>/<社内用DNSサーバIP>
dnsmasqの起動設定
起動のための設定ファイルをシステムにコピーします。
$ sudo cp $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons/
LaunchDaemonにdnsmasqをデーモンとして登録します。
$ sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
システムのDNS設定
システム環境設定のネットワークのDNSの設定にlocalhost
を指定すれば、dnsmasqを使って社内外両方の名前を解決できるようになります。
システムのプロキシ設定
以上の設定により、システム環境設定のネットワークのHTTPプロキシの設定に何も設定しなくても、認証情報を入力することなくプロキシを利用できるようになります。
なお、HTTPS通信についても、証明書を適切に設定すれば透過プロキシを利用できるようなのですが、現状は以下のような理由により設定していません。
- クライアント側に証明書を設定するのが手間である
- Mac上に立てたVMやコンテナから社外に対してHTTPS通信をしなければならないケースはあまりない気がする
4/14追記:
上記のように思ったのですが、実際使っていると結構HTTPS通信が必要なので、いまいち透過プロキシを立てた意味がないです。また余裕があるときにHTTPS通信を透過型にして嬉しいかどうかを実験してみたいと思います。