Linux/squidのインストール(リバースプロキシ)

squid でリバースプロキシ(2017-09-09)


余談:
だいぶ前にトライしてみた作業ですが、ブログにメモし損ねてました。
本件、すこし内容が長いのは、色々と試したことが多かったからかもしれません。

さて、
squid はプロキシ-キャッシュサーバで、
通信の間に入って、通信内容に応じて転送したり止めたりする機能やら
保持した情報を本体の代わりに応答することで、本体の負荷を減らすやら
色々できます。

今回は squid でWebサーバのリバースプロキシにしてみます。

Webブラウザ --- Squid --- Webサーバ

のように、通信の間でSquidが転送 or 切断してあげるイメージです。
Squidで不正アクセスに 404 応答あるいは即切断*1を実行します。
将来的には、
https を Squid で平文に戻すことで snort など IPS が通信を読みやすくしたり、
一部の通信はWebサーバ以外に転送させることも視野に。
(むかし導入したWAFはSquidの転送機能を利用していました)
追々色々やってみたいです。

ソースの取得とビルド

squid-cache.org (英語)で
ダウンロードから設定例まで探せます。
(同サイトから 日本語訳しているリンク(j-one様)にも行けます)

まず、ダウンロードページから、
ソースと、 sig ファイルを拾ってきてから、ハッシュ値を確認します。

# cat squid-3.5.24.tar.gz.asc
(確認方法について情報を得る。今回は「SHA1」行を参照してみます)
# sha1sum squid-3.5.24.tar.gz
(上記、ascファイルの中の SHA1 行と一致すること)

あとは、INSTALL テキストの指示通りです。

# tar zxvf squid-3.5.24.tar.gz
# cd squid-3.5.24
# ./configure --prefix=/usr/local/squid
# make
# make install

configure の オプションは --help を参照。
etc や var の置き場を /usr/local/squid 以外へにするなら configure する時に指定しましょう。


ですが、私の場合は、OpenSSLをソースからビルドしているので念のために。

# ./configure --prefix=/usr/local/squid  --with-openssl=OpenSSL関連のPATH
(中略)
checking for SSL_library_init in -lssl... no

3.5.24版時点では OpenSSL 1.1.0 だと通りませんね。
SSL_library_init は 1.1.0 には存在しない関数です。
SSL_library_init それ自体をsquidで使っている訳ではなさそうですが、*2、全体として3.5.24版では 1.1.0 には未対応なんでしょうね。

1.0.1ならば、ビルドできました。
参考まで、
OpenSSLの1.1 と 1.0 を分けるための考察はこちら「Linux/ビルドとyumの混在


無事、インストール出来たら、試しに(初期値のまま)起動。

# /usr/local/squid/sbin/squid -z
WARNING: Cannot write log file: /usr/local/squid/var/logs/cache.log
/usr/local/squid/var/logs/cache.log: Permission denied

ぬ!?
デフォルトだと、logfile-daemon がnobady起動でビルドされたためです。

# chown -R nobody:nobody /usr/local/squid/var/logs
(あるいは起動ユーザを nobody 以外に変える)
# /usr/local/squid/sbin/squid -z
# /usr/local/squid/sbin/squid
# ps -ef | grep squid

とりあえず、起動するところまで行きました。
一旦「/usr/local/squid/sbin/squid -k kill」か kill コマンドで
squid を停止して、コンフィグを設定します。

squid.conf の設定(リバースプロキシ)

今回は前述の通り 3.5版ですが、
squid もバージョンで結構パラメータが変わっている印象があって、
むかし(2.x版?)にsquid を構築した時と使用するパラメータが違う気がしました。

www.squid-cache.org から最新版に対応した設定例が探せます。リバプロが欲しいので、
[Examples] - [Reverse Proxy (Acceleration)] - [BasicAccelerator] と探しました。

以下、squid.conf への追記内容。
(たぶん、バージョンに依存するので参考程度に)

http_port 80 accel defaultsite=localhost
https_port 443 accel cert=サーバ証明書 key=証明書の秘密鍵 defaultsite=localhost protocol=http
cache_peer localhost  parent 8080 0 no-query originserver login=PASS name=my_accel

acl web_client dstdomain null-i.net www.null-i.net subdomain.null-i.net
http_access allow web_client
cache_peer_access my_accel allow web_client
http_access deny all

前半、
まず、前提として、
デフォルトの http_port はコメントアウト済みです。(# http_port 3128 )
今回は、squidとWebサーバは同じサーバ上にあって、
Webサーバ側は localhost:8080 で受信し、httpsは受けません(squidが受けます)。
それらを踏まえて
http_port で 80(http) と 443(https)をそれぞれ設定しています。 cache_peer でWebサーバ 8080 への接続を設定します。
「cach_peer login=~」はWebサーバ側でdigest認証等を行う際に必要になります。*3

後半はアクセス許可設定です。
事前に、前の行にデフォルトで記載されていた 「deny all」行 はコメントアウト済みです。
(・・・という具合に、アクセス許可設定は記述順も忘れずに)
自分のサーバ(null-i.netと、そのサブドメイン*4)へのアクセスのみを許可して、
それ以外の squid へのアクセスは拒否します。

以上が基本的な設定でしょうか、
で、
ここからは、
私は諸事情で以下のように追加

cache deny all

まだキャッシュサーバとして使う予定がないので無効化しました。
性能面で必要になったら変更しますが、それまで毎回WebサーバへWebコンテンツを取りに行かせます。
もし cgi 等が上手く動かない場合は一旦キャッシュ無効化して実験すると、問題切り分けに役立つかもしれません。

error_directory /usr/local/squid/etc/err_page

squidのエラーメッセージは丁寧&各国対応で素敵ですが、
私的なリバースプロキシには、いささか豪華というか親切すぎます。
(今回は外部からの不正接続っぽい 404 Not Found を止めるのが目的なので、
サーバ名、メールアドレス、squidバージョンなどの情報は応答する必要がありません)
一通り刷新して、error_directory でカスタマイズします。

# cd /usr/local/squid
# mkdir etc/err_page
# for ff in share/errors/en/*; do
    ee=`basename $ff`
    echo "<html><body>Sorry, you can't access. ($ee)</body></html>" > etc/err_page/$ee
  done

・・・エラーメッセージがいささか不親切すぎ?ですが(いやいや、あくまで例ですよ?)
for 文で、既存のerr_page を参考に
用意しなければならないファイル(≒エラーの種類ですね)を既存設定からピックアップしながら、
echo で 仮 HTMLファイルを作成しています。

httpd_suppress_version_string on

エラーメッセージに書き込まれる squid のバージョンを隠します、が、
今回エラーメッセージファイルは刷新しているので、不要かも。

logformat httplog %tl %>a [%>Hs] %Ss %>st %<st [%>rm %>ru ] [%{User-Agent}>h] [%{Referer}>h]
access_log daemon:/usr/local/squid/var/logs/access.log httplog
logfile_rotate 30

ログの書式です。(まだ更新中)
ソースファイルに同梱の etc/squid.conf.documented にも書いてありますが、
デフォルトは logformat squid が指定されているようです。
少なくとも、その日時「%ts.%03tu」(エポックタイム。unixの秒数)を %tl 書式に変えたかったのです。
また、ログのローテーション(後述)も30日を想定して、保持する数を増やしました。

acl bad_url urlpath_regex ^/[a-zA-Z0-9]+$ (login|setup|admin|bbs|waw|block|proxy).(cgi|php|pl|sh) .action$
http_access deny bad_url
deny_info TCP_RESET bad_url

存在しないセットアップファイルを漁るアクセスを切断します。
フィルタの内容は regex・・・つまり正規表現で記載できます。
(正規表現をどうこね回すかについては 「fail2banのフィルタどうしよう」 の件も参照ください)
記述が順不同になってしまいましたが、
これらは前述のルール「acl ~ dstdomain」よりも「前」に書きます。前、です。
acl urlpath_regex で拒否するPATHの正規表現を設定
(実際はもう少し色々と書いています、記述では雰囲気が伝わればうれしいです)、
その次の http_access deny で拒否します。
deny_info は直前の指定ルールについて(この場合 bad_url)拒否メッセージを指定しますが
この場合はTCP接続をRESETする、つまりエラー画面すら返さず切断(TCP RESET)という素敵な動作を指定できます。
(これがやりたくてsquidを導入しました)

そして、おまじない

udp_incoming_address localhost

これを「やらない」場合、私の手元のsquid バージョンでは、
グローバルアドレス向けのUDPポートが2つも開いているじゃありませんか・・・*5

(netstat -na の結果より、身に覚えのない 33841 と 58930 が・・・)
udp        0      0 0.0.0.0:33841           0.0.0.0:*
udp6       0      0 :::58930                :::*
# lsof -i:33841,58930
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
squid   12663 nobody    6u  IPv6 384082      0t0  UDP *:58930
squid   12663 nobody    7u  IPv4 384083      0t0  UDP *:33841

ポートはランダムに変わるようです、とはいえ、
(うちの場合は外向けのUDP通信なんぞ想定していないので)
勝手に外へのポートを開かれては困ります、ということで先ほどの
udp_incoming_address localhost で内部通信へ切り替えておきます。

(ふたたび netstat -na の結果より、localな通信になっていることを確認)
udp        0      0 127.0.0.1:39731         0.0.0.0:*

設定出来たら、squidを再起動します。
私の環境の場合は、Webサーバも同じサーバなのでそちらの設定更新&再起動が先ですが。

Webサーバ側の変更(apache)

前述の通り、同一サーバ上にある apache の設定を変えます。
まず、受信ポート 80 はsquid に譲って、squidから8080で受けるので。

# Listen 80            # こちらは削除
Listen localhost:8080  # こちらへ変更。localhost にすることも忘れずに。

それから今回は https を無効にしました。
私の環境の場合は「httpd-ssl.conf」の読込をコメントアウト。

そして、LogFormat の 「&h」を「%{X-Forwarded-For}i」に変更します。*6
そのまま(%h)では全部 squidサーバのアドレスになってしまうので。

また、
vhostも使っている場合は、httpd-vhosts.conf の変更もお忘れなく。
私の場合は サブドメイン subdomain.null-i.net 用の変更が必要でした。

一通りできたら Webサーバを再開します。
localhostにした場合は外からはアクセスできないので、wget での確認例はこんな感じでしょうか。

# wget localhost:8080      # あるいは localhost:8080/任意のページ.html とか

squid の起動、停止、ログローテーション

とりあえず、起動。

/usr/local/squid/sbin/squid -f /usr/local/squid/etc/squid.conf

WebサーバとSquidの両方を起動して、Webアクセスができることを確認します。
起動&停止オプション(-k shutdownなど)については「squid -h 」で確認できます。

また、「-k rotate」でログをローテーションできます。
これもsquid-cache.org の情報を参考に、cron で回すことにしました。

# crontab -e
(日付変更時にローテーションさせる例)
0 0 * * * /usr/local/squid/sbin/squid -k rotate

あとは、起動スクリプトですね。 「/etc/init.d/squid 」を他の起動スクリプトを参考に、 まずは start、stop だけでも実装すれば良いかと。

(ほんとうに start、stop だけの例)
#!/bin/sh
#chkconfig: 2345 90 40
SQUID_BIN=/usr/local/squid/sbin/squid
SQUID_CONF=/usr/local/squid/etc/squid.conf
case $1 in
start)
   $SQUID_BIN -f $SQUID_CONF
   ;;
stop)
   $SQUID_BIN -k kill
   ;;
esac

まず、「stop」 は shutdown にするのがベターかも。*7
chkconfig は、apache など Webサーバの後に起動(90)、前に停止(40)するように数字を調整します。
(それを踏まえて、もう少し上記をまじめに作ったものを、)
「chkconfig --add squid 」で /etc/rc.d へ登録します。する予定です。するといいな。


これでひとまず動かしてみます。
あとは fail2ban のログ監視も apache とすみ分けるように変える予定です。
前述の deny_info TCP_RESET するログなんかは、百年くらいbanしてやれば良いんじゃないかな。

余談~URLを日本語へ戻す

ログ出力される URLの日本語変換が apache と違うんですね。squidは「%」もエスケープ?なんでだろ?
ブラウザ上でURLへ「index.html?ぬるいねっと」と入力すると、
apacheのログでは「index.html?%E3%81%AC%E3%82%8B~」に、
squidのログでは「index.html?%25E3%2581%25AC%25E3%2582%258B~」に更にエスケープされます。

apacheの %E3%81%AC は「ぬ」に変換できるのですが
(2行とも、「ぬ」へ変換するコマンドの例です)

echo %E3%81%AC | perl -MURI::Escape -pe 'print uri_unescape $_'
echo %E3%81%AC | perl -e 'while(<STDIN>){ s/%([0-9a-fA-F]{2})/pack("H2",$1)/ge; printf("%s\n", $_); }'

squidの %25E3%2581%25AC は「%25」を一旦「%」に戻してからでないと変換できませんね。

echo %25E3%2581%25AC |  perl -MURI::Escape -pe 's/%25/%/g; print uri_unescape $_'
echo %E3%81%AC  |  perl -e 'while(<STDIN>){ s/%25/%/g; s/%([0-9a-fA-F]{2})/pack("H2",$1)/ge; printf("%s\n", $_); }'

wikiなのでURLを日本語に戻さないとさっぱり分からないのですが、
squidのアクセスログが日本語に戻らなかったので少し焦りました。まさか%が変換されるとは・・・


*1 後述していますが、今回は「deny_info TCP_RESET」を利用して、念願の「即切断」を実現できました。
*2 squid のソースを置いた場所で、ざっくり探してみたのですが、見当たらないっぽいので。 find . -type f -exec grep -l SSL_library_init {} \;
*3 他の注意点についても squid-cache.org の Reverse Proxy 「Common Problems」 の項目を見た方が良さそうです。cgiの場合の注意とか。
*4 www. がWebブラウザで自動で付与される場合も考慮しなければならないのが盲点でした・・・
*5 あとで気づきましたが、これは ICPや DNS等に利用するポートで、デフォルト値は all interface だと squid.conf.documented に書いてありました。幸いうちの firewall は外との通信を許可していませんでしたが。
*6 X-Forwarded-Forはプロキシなどで転送した時に転送元を記録するHTTPヘッダ。今回は(Squid 3.5 で)自動で付けてもらえます。
*7 shutdown を使った init.d の実装例は squid-cache.org を参照、停止完了まで while ループさせています。あとは 引数チェックやら コマンドの戻り値($?)の確認やらがあれば良いかも?

  最終更新のRSS