はじめまして、バックエンドエンジニアのまさです。
昨今ではセキュリティの強化に伴い、特定のPCのみアクセス可能とするためクライアント認証を導入しているサーバーが存在します。
これらのサーバーへのアクセスにはクライアント証明書が事前にインストールされている必要がありますが、SaaSアプリケーションなど、クライアント証明書がインストールできない制約がある環境からはアクセスすることができません。
このようなケースにProxyを利用した代理クライアント認証を利用してアクセスを実現する方法をご紹介します。
Proxyによる代理クライアント認証手法
プロキシサーバーによる代理クライアント認証を適用した通信のイメージとしては下記のようになります。
クライアントアプリケーションからの通信をproxyサーバーが受け取り、一度SSL終端してproxy内でクライアント証明書を付与してSSL再構築しサーバーにリクエストする形となります。
プロキシサーバーについて
プロキシサーバーにはOSSのsquidを使用します。
基本的には透過プロキシとして機能させ、対象とするサーバーへのアクセスに関してのみ代理クライアント認証を行うように設定していきます。
元々squidはHTTPS通信に関しては、トンネルとしてしか機能しません。その状態ではクライアント認証を代理で行うことはできないので、クライアント認証を行うためにSSLを一旦終端させ、クライアント認証を埋め込んで再度SSLに戻し実サイトにリクエストさせるようにする必要があります。
環境構築
事前準備
Dockerコンテナ上でsquidを動作させますが、デフォルトでインストールされるsquidはSSL通信の終端を行うことができません。
SSL通信の終端を行うためのssl_bump機能を有効にするためコンテナ上でsquidのソースビルドを行っていきます。
事前に以下のファイルを準備しておく必要があります。client-cert.pem
: クライアント証明書client-key.pem
: クライアント秘密鍵
サーバー証明書はsquidに自動生成させることが可能ですが、クライアント側で証明書の検証エラーになる可能性があります。
証明書の検証をスキップできる場合には、以下のコマンドを実行し自己署名の証明書を作成します。
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout server_key.pem -out server_cert.pem
# 各種応答に答えることで証明書が生成される
証明書の検証がスキップできない場合にはサーバーの実証明書を用意することで対応することが可能です。
パッケージのインストール~ビルド
まずはビルドに必要なパッケージをインストールします。
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y devscripts build-essential fakeroot debhelper dh-autoreconf cdbs libssl-dev
RUN sed -e s/"^# deb-src"/"deb-src"/g /etc/apt/sources.list > /tmp/sources.list && \
mv /tmp/sources.list /etc/apt/sources.list && \
apt-get update && \
apt-get -y build-dep squid
その後、ソースビルドを行います。この際、sslを有効にするオプション等をビルドオプションとして指定できるように設定します。
※展開されるsquidのバージョンは最新版になると思うので、適宜修正してください。
RUN cd /usr/local/src && \\
apt-get source squid && \\
sed -e s/"--with-gnutls"/"--with-gnutls \\\\\\\\\\n --with-openssl \\\\\\\\\\n --enable-ssl \\\\\\\\\\n --enable-ssl-crtd "/g /usr/local/src/squid-4.10/debian/rules > /tmp/rules && \\
mv /tmp/rules /usr/local/src/squid-4.10/debian/rules && \\
cd /usr/local/src/squid-4.10 && \\
./configure && \\
debuild -us -uc -j`nproc`
ビルドした成果物をインストールしていきます。
RUN cd /usr/local/src && \\
apt-get -y install apache2 squid-langpack logrotate libdbi-perl && \\
apt-get autoclean && \\
apt-get -f install && \\
dpkg --configure -a && \\
apt-get -f install && \\
dpkg -i squid*.deb
SSL関連の初期設定を行います。
RUN mkdir -p /var/lib/squid && \\
rm -rf /var/lib/squid/ssl_db && \\
/usr/lib/squid/security_file_certgen -c -s /var/lib/squid/ssl_db -M 20MB && \\
chown -R proxy:proxy /var/lib/squid
一度この時点でコンテナをビルドして起動させ、デフォルトの設定ファイルをコンテナからコピーします。
# コンテナのビルド
docker build -t squid-ssl:ubuntu .
# コンテナの起動
docker run -d -it squid-ssl:ubuntu /bin/bash
# コンテナから設定ファイルのコピー
docker cp ${container_id}:/etc/squid/squid.conf ./squid.conf
# コンテナの停止
docker stop ${container_id}
# コンテナの削除
docker rm ${container_id}
設定ファイルの編集
コピーした設定ファイルsquid.conf
を編集していきます。
サーバーへの通信時に適用するクライアント証明書を指定します。
# tls_outgoing_options min-version=1.0
↓
tls_outgoing_options cert=/etc/squid/ssl_cert/client-cert.pem key=/etc/squid/ssl_cert/client-key.pem
クライアント証明書にパスワードが必要な場合は、下記のようにパスワードを出力するプログラムを指定することで対応可能です。
sslpassword_program /etc/squid/ssl_cert/certpass.sh
ここで指定するプログラムは下記のようなシンプルなスクリプトです。
#!/bin/bash
# 証明書のパスワードを出力
echo "passpass"
proxyへの接続を許可するネットワークの設定を行います。
ローカルマシン上に構築して他者がアクセスしない環境であれば緩めの設定でも問題無いでしょう。
http_access deny all
↓
#http_access deny all
または
# localnetに許可リストを定義
acl localnet src ~
:
:
:
#http_access deny all
http_access allow localnet
サーバー証明書の設定を行います。通常、待ち受けポートのみ設定するhttp_port句がありますがここに追加オプションを指定していきます。
http_port 3128
↓
# 自己署名証明書を使わない場合
http_port 3128 ssl-bump generate-host-certificates=off dynamic_cert_mem_cache_size=20MB cert=/etc/squid/ssl_cert/fullchain.pem key=/etc/squid/ssl_cert/privkey.pem
# 自己署名証明書を使う場合
http_port 3128 ssl-bump generate-host-certificates=on dynamic_cert_mem_cache_size=20MB cert=/etc/squid/ssl_cert/server_cert.pem key=/etc/squid/ssl_cert/server_key.pem
ssl_bumpを行う対象のドメインを設定します。例では.example.com
というドメインのみssl_bumpするように設定しています。
acl broken_sites dstdomain .example.com
ssl_bump server-first broken_sites
ssl_bump none all
証明書の作成コマンド、検証エラー時の振舞い、ssl_bumpの振舞いを定義します。
sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 20MB
sslproxy_cert_error allow all
ssl_bump stare all
ファイルの配置~起動
先程まで編集を行ったファイルがコンテナに配置されるようにします。
# squidの設定ファイル
COPY ./squid.conf /etc/squid/squid.conf
# クライアント証明書
COPY ./client-cert.pem /etc/squid/ssl_cert/client-cert.pem
COPY ./client-key.pem /etc/squid/ssl_cert/client-key.pem
# サーバー証明書
COPY ./fullchain.pem /etc/squid/ssl_cert/fullchain.pem
COPY ./privkey.pem /etc/squid/ssl_cert/privkey.pem
# 自己署名証明書
COPY ./server_cert.pem /etc/squid/ssl_cert/server_cert.pem
COPY ./server_key.pem /etc/squid/ssl_cert/server_key.pem
# パスワード出力プログラム
COPY ./certpass.sh /etc/squid/ssl_cert/certpass.sh
あとはコンテナ起動時にsquid
が起動するように指定します。
CMD ["/usr/sbin/squid", "-f", "/etc/squid/squid.conf", "-NYC"]
コンテナを起動します。
docker run -d -p 3128:3128 -it squid-ssl:ubuntu /bin/bash
アプリケーションへのプロキシサーバー設定追加
上記まで行うことでプロキシサーバーが起動している状態になります。
あとはアプリケーションのプロキシ設定にこのプロキシサーバーを指定することで通信がプロキシサーバーに送られ、クライアント証明書が付与された状態で通信されるようになります。
所感
今回の手法はクライアント認証が必要なサイトにアプリケーションでアクセスする必要性が発生した場合に有効な対応方法ではないかと思います。
squidはあくまでプロキシサーバーなので、この他にも様々な活用方法があります。通常のプロキシサーバーとして使用する方法や、特殊な通信パターンを再現する場合等、アプリケーションだけでは実現できないアクセスパターンを再現するのに利用することもできると思いますので、通信系に関しての役に立つ手法の一つとして知っておいて損は無いのではないでしょうか。