Ubuntu

フォワードプロキシーあれこれ

インターネットに公開するフォワードプロキシーが欲しいという話があった。
安全にサービスを公開する方法はあるのか、色々と試してみることにした。



広告


結論からすると、インターネットに向けてフォワードプロキシーを安全・お手軽に公開することはできず、どこかで割り切りが必要。
KDCを外に向けて開けて、全端末にその情報を配っておくというのが一番良さそうだが、この記事の対象外。
とはいえ、イントラネットで使う分には十分な程度の設定メモにはなっている(実装しないので手順の再確認までは行っていないけれど)。

Squidについていえば、SAMLやOAuthなどの色々な外部サービスと認証の連携ができる、としているサイトもあるのだけれど、やり方は書いていなくて分からない。
本家ではBasic, NTLM, Digest, Negotiateと書いているので、フォワードプロキシーではなく、リバースプロキシーでの話なのかなと思う。

色々試す際に登場するホストは、すべて仮想環境でUbuntu 22.04を使用している。
hogeserver.hogeddns.jpはホームラボだけで使える架空のドメインで、各ホストの名前解決ができるようになっている。

ホスト名機能
squid.hogeserver.hogeddns.jpForward Proxy。当初、Squidだけを試すつもりだったが、Apacheも試している。
IPアドレスは192.168.110.172/24。
addc.hogeserver.hogeddns.jpSamba AD DC。Acitive DirectoryでDHCP、DNS、プライベートCA等。
keycloak.hogeserver.hogeddns.jpIdP。バックエンドはaddcのLDAP。
desktop.hogeserver.hogeddns.jpUbuntu desktop。FirefoxやChromeでForward Proxyの動作を試す。

Squid

プロキシーサーバーといえばSquidのようで、昔からあって有名とのこと。

CanonicalがDockerのイメージを提供してくれているので、これを動かしてみることにした。
Docker Hub / ubuntu/squid

とりあえず動かす

作業のベースとなるディレクトリを作成する。

$ sudo mkdir /opt/squid
$ cd /opt/squid

docker-compose.ymlを作成する。

/opt/squid/docker-compose.yml

services:
squid:
image: ubuntu/squid:latest
container_name: squid
restart: unless-stopped
# volumes:
# - /opt/squid/logs:/var/log/squid
# - /opt/squid/data:/var/spool/squid
# - /opt/squid/squid.conf:/etc/squid/squid.conf
ports:
- 3128:3128
environment:
TZ: Asia/Tokyo

起動して、squid.confを取り出し、ボリュームごとコンテナを削除する。

$ sudo docker compose up -d
$ sudo docker cp squid-container:/etc/squid/squid.conf ./
$ sudo docker compose down -v

ログとデーター用のディレクトリを作成し、オーナーを変えておく。(proxy = UID:13)

$ sudo mkdir /opt/squid/{logs,data}
$ sudo chown 13:13 /opt/squid/{logs,data}

先程作成したdocker-compose.ymlで、コメント化していた4行の先頭#を削除して有効化する。

/opt/squid/docker-compose.yml

...
volumes:
- /opt/squid/logs:/var/log/squid
- /opt/squid/data:/var/spool/squid
- /opt/squid/squid.conf:/etc/squid/squid.conf
...

再度コンテナを起動してみる。

$ sudo docker compose up -d

これで、とりあえずSquidは動作する。

Ubuntu 22.04 desktopをインストールした仮想PCで、Firefoxを使ってProxyの動作を試してみる。

問題なく動いているようだ。

Basic認証(LDAP)

間違いなく認証は必要ですよね…ということで、Basic認証ができるようにする。
バックエンドはLDAPで、ホームラボで稼働しているSamba AD DCを使用する。

SquidにBasic認証の設定をする。ファイルのどこに書いても良いと思うが、赤文字を追加。

/opt/squid/squid.conf

...
#Default:
# authenticate_ip_ttl 1 second

auth_param basic program /usr/lib/squid/basic_ldap_auth -v 3 -b CN=users,dc=hogeserver,dc=hogeddns,dc=jp -s sub -D ssoauth@hogeserver.hogeddns.jp -w "p@ssword123" -f sAMAccountName=%s -H ldaps://addc.hogeserver.hogeddns.jp
auth_param basic children 10
auth_param basic realm Squid proxy-caching web server
auth_param basic casesensitive off
auth_param basic credentialsttl 1 minute
acl authenticated_user proxy_auth REQUIRED
http_access deny !authenticated_user


# ACCESS CONTROLS
...

Samba AD DCのユーザーはCN=users,dc=hogeserver,dc=hogeddns,dc=jpにいる。
ユーザー名としてsAMAccountNameを使用する。
バインドするユーザーはssoauth、パスワードはp@assword123。

Samba AD DCはStrong Authを要求しているので、LDAPSのシンプルバインドを選択。
LDAPSの暗号化に使用する証明書はプライベート認証局が署名しているので、以下を実施。

  • /usr/local/share/ca-certificates/hoge-ca.crtとして、プライベート認証局の証明書を配置。
  • /opt/squid/ldap.confを作成して、この証明書を参照するように設定。

具体的には…

/usr/local/share/ca-certificates/hoge-ca.crt ※認証局発行のもの

-----BEGIN CERTIFICATE-----
MIICertCertCertCertCertCertCertCertCertCertCertCertCertCertCertC
...
CertCertCertCertCe==
-----END CERTIFICATE-----

/opt/squid/ldap.conf ※新規作成

TLS_CACERT /usr/local/share/ca-certificates/hoge-ca.crt

この2つのファイルがコンテナの中でしかるべき場所に入るように設定。

/opt/squid/docker-compose.yml

services:
squid:
image: ubuntu/squid:latest
container_name: squid
restart: unless-stopped
volumes:
- /opt/squid/logs:/var/log/squid
- /opt/squid/data:/var/spool/squid
- /opt/squid/squid.conf:/etc/squid/squid.conf
- /usr/local/share/ca-certificates/hoge-ca.crt:/usr/local/share/ca-certificates/hoge-ca.crt
- /opt/squid/ldap.conf:/etc/ldap/ldap.conf
ports:
- 3128:3128
environment:
TZ: Asia/Tokyo

コンテナを一旦破棄して、改めて作り直す。

$ sudo docker compose down -v
$ sudo docker compose up -d

Firefoxを一旦閉じて起動すると、直後に認証を求められるようになった。

Samba AD DCに登録してある rohhie というユーザーでログインし、様々なサイトにアクセスできることが確認できた。

Digest認証(LDAP)

Basic認証の場合、アクセスする度に平文でパスワードが送られるとのこと。
それはちょっと嫌だなと思って、Digest認証の設定をしてみた。

試行錯誤して動作するようになったが、LDAPバックエンドのDigest認証は、

  • Samba AD DCで、ユーザー属性の市区町村(l:エル)に、パスワードをmd5sumで計算して設定。
  • ヘルパーとしてdigest_ldap_authを利用し、ブラウザから送られてくるパスワードと、市区町村に設定された値と照合。
    (ブラウザから送られてくるパスワードはハッシュ計算されていると思うけれど、確かめてはいない)

で行われているようだった。

つまり、ファイルの代わりにLDAPにパスワードデーターを書き込んでおく、というもののようだ。

以下に情報があって、途中までを実行している。
Squid Web Cache wiki / Using the digest LDAP authentication

まず、ユーザー属性の市区町村(l:エル)に、パスワードをハッシュ計算して設定する。
どんな方法を使っても良いけれど、コンテナの中でldap-utilsを使えるようにしてみた。

$ sudo docker exec -it squid bash --login
# apt update
# apt install ldap-utils

ユーザーrohhieの市区町村にパスワードをハッシュ計算して設定する。
更新には、rohhieを使用した。ssoauthは参照しかできないためで、administratorを使った更新でもいいかもしれない。

# REALM="Squid proxy-caching web server"
# HASH=`echo -n "rohhie:$REALM:rohhie-password" | md5sum | cut -f1 -d' '`
# ldapmodify -H ldaps://addc.hogeserver.hogeddns.jp -D rohhie@hogeserver.hogeddns.jp -w "p@ssword123" << EOF
dn: CN=Rohhie Puyopuyo,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp
changetype: modify
replace: l
l: $REALM:$HASH
EOF

設定した値を確かめる。

# ldapsearch -H ldaps://addc.hogeserver.hogeddns.jp -b "cn=Users,dc=hogeserver,dc=hogeddns,dc=jp" -s sub -D ssoauth@hogeserver.hogeddns.jp -w "p@ssword123" "sAMAccountName=rohhie" l
# extended LDIF
#
# LDAPv3
# base <cn=Users,dc=hogeserver,dc=hogeddns,dc=jp> with scope subtree
# filter: sAMAccountName=rohhie
# requesting: l
#

# Rohhie Puyopuyo, Users, hogeserver.hogeddns.jp
dn: CN=Rohhie Puyopuyo,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp
l: Squid proxy-caching web server:89324bbb7dae59bf4bd14fffb323ba4e

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

それっぽい値が設定されているので、digest_ldap_authでテストする。

# echo '"rohhie":"Squid proxy-caching web server"' | /usr/lib/squid/digest_ldap_auth -b CN=users,dc=hogeserver,dc=hogeddns,dc=jp -u sAMAccountName -A "l" -D ssoauth@hogeserver.hogeddns.jp -w "p@ssword123" -e -v 3 -H ldaps://addc.hogeserver.hogeddns.jp -d
ldap_backend.cc(395): pid=291 :Connected OK
ldap_backend.cc(253): pid=291 :searchbase 'CN=users,dc=hogeserver,dc=hogeddns,dc=jp'
ldap_backend.cc(283): pid=291 :password: 89324bbb7dae59bf4bd14fffb323ba4e
OK ha1="89324bbb7dae59bf4bd14fffb323ba4e"

どうやら上手くいきそうな予感。
コンテナから抜ける。

# logout

先程のBasic認証の設定を、Digest認証の設定に置き換える。

/opt/squid/squid.conf

...
#Default:
# authenticate_ip_ttl 1 second

#auth_param basic program /usr/lib/squid/basic_ldap_auth -v 3 -b CN=users,dc=hogeserver,dc=hogeddns,dc=jp -s sub -D ssoauth@hogeserver.hogeddns.jp -w "p@ssword123" -f sAMAccountName=%s -H ldaps://addc.hogeserver.hogeddns.jp
auth_param digest program /usr/lib/squid/digest_ldap_auth -b CN=users,dc=hogeserver,dc=hogeddns,dc=jp -u sAMAccountName -A "l" -D ssoauth@hogeserver.hogeddns.jp -w "p@ssword123" -e -v 3 -H ldaps://addc.hogeserver.hogeddns.jp
auth_param digest children 10
auth_param digest realm Squid proxy-caching web server
auth_param digest casesensitive off
auth_param digest credentialsttl 1 minute
acl authenticated_user proxy_auth REQUIRED
http_access deny !authenticated_user

# ACCESS CONTROLS
...

コンテナを再起動する。

$ sudo docker compose restart

Forefoxを再起動したところ、認証を求められるようになる。
ここで、今回設定したユーザーrohhie、パスワードrohhie-passwordを入力してログインしたところ、様々なサイトにアクセスができた。

ちなみにこのパスワードは、Samba AD DCのパスワードとは違う。
Digest認証情報の解析はできるかもしれないけれど、時間は掛かるだろうから随分良いのだろうけれど、パスワードを別に作って保管しておかなければならないことを考えると、LDAPではなくファイルに保管しておいた方がお手軽な気もする。

Apache

ここに設定方法が書かれている。
Apache Module mod_proxy

とりあえず動かす

上でSquidを試しているので、一旦落としておく。

$ cd /opt/squid
$ sudo docker compose down
$ cd

Apacheをインストールする。

$ sudo apt install apache2

設定ファイルを作成する。
ProxyRequestsがForward Proxyとしての動作をさせるための指定。

/etc/apache2/sites-available/myproxy.conf

LISTEN 3128

ProxyRequests On
ProxyVia On

<Proxy "*">

</Proxy>

設定を有効化する。

$ sudo a2enmod proxy_http proxy_connect proxy_wstunnel
$ sudo a2ensite myproxy
$ sudo systemctl restart apache2

これでForward Proxyとして動作しており、Firefoxで動作を確認できた。

Basic認証(LDAP)

Basic認証ができるようにする。
バックエンドはLDAPで、ホームラボで稼働しているSamba AD DCを使用する。

設定ファイルを全面的に書き換えて反映させる。
ホームラボにあるSamba AD DCを使用し、プライベート認証局を中心とした信頼関係を構築していて、LDAPはstrong authを要求しているので、LDAPSを使用する。
そのため、プライベート認証局のCA証明書を信頼できるものとして指定している。

/etc/apache2/sites-available/myproxy.conf

LISTEN 3128

ProxyRequests On
ProxyVia On

LDAPTrustedGlobalCert CA_BASE64 /usr/local/share/ca-certificates/hoge-ca.crt

<Proxy "*">
AuthName "My server authentication"
AuthType Basic
AuthBasicProvider ldap
AuthLDAPURL ldaps://addc.hogeserver.hogeddns.jp/CN=users,dc=hogeserver,dc=hogeddns,dc=jp?sAMAccountName
AuthLDAPBindDN ssoauth@hogeserver.hogeddns.jp
AuthLDAPBindPassword "p@ssword123"
# AuthBasicProvider file
# AuthUserFile /etc/apache2/.proxypassword
Require valid-user
</Proxy>

AuthLDAPURLは”?”で区切って、BaseDN、対象となる項目名、フィルターが指定できるようだった。
今回、フィルターは設定せずに試している。

ちなみに、LDAPではなくファイルでユーザー認証をすることもできる。
この場合は、AuthBasicProvider~AuthLDAPBindPassword迄を削除し、コメントアウトしている行を有効にする。

パスワードファイルは、以下で作成できる。

$ sudo htpasswd -c /etc/apache2/.proxypassword hoge
New password:
Re-type new password:
Adding password for user hoge

※ ユーザーを追加する場合には、-cを付けずにhtpasswdを実行すれば良いみたい。

LDAP認証に必要なモジュールを有効にして、設定を反映。

$ sudo a2enmod ldap authnz_ldap
$ sudo systemctl restart apache2

これで、Samba AD DCのユーザー情報でユーザー認証ができるようになった。

ログファイルを指定していないので、/var/log/apache2/other_vhosts_access.logにログが出力されている。
認証を受けてWebアクセスをすると、認証を受けたユーザーの名前が出力されることも確認できた。
ただ、ちょっとしたページにアクセスするだけで物凄い量のログが出るので、これへの対策は考えておく必要がありそうだ。

Digest認証(LDAP/失敗)

ApacheにはLDAPをバックエンドにしたDigest認証を設定する方法がなかった。
Squidで試した結果を考えると、存在しないのは当然のように思われた。

今回は試していない。

クライアント証明書でアクセス制限(失敗)

クライアント証明書で他所からのアクセスを排除できるのか試してみた。
結果としては、接続ができなかった。

/etc/apache2/sites-available/myproxy.conf

LISTEN 3128

ProxyRequests On
ProxyVia On

SSLCACertificateFile /usr/local/share/ca-certificates/hoge-ca.crt
SSLCARevocationFile /etc/ssl/private/hoge.crl
SSLCARevocationCheck leaf
SSLVerifyClient require
SSLVerifyDepth 1

<Proxy "*">
Require expr (%{SSL_CLIENT_VERIFY} == "SUCCESS")
</Proxy>

※CAはプライベート認証局、クライアント証明書はこのプライベート認証局が署名したpfxファイル。

SSLモジュールを有効にして、設定を反映。

$ sudo a2enmod ssl
$ sudo systemctl reload apache2

Firefoxでアクセスしてみたところ、プロキシサーバーから接続を拒否される。
クライアント証明書をインポートしてあっても、それを提示することができない。
Chromeも同様だったので、プロキシサーバーそのものに対してクライアント証明書を提示する仕組みは存在しないものと思われる。

SAML認証(失敗)

ApacheにはSAML認証のためのプラグインが提供されているようなので試してみる。
IdPとしてKeycloakを動作させた(後述)。

Apacheのプラグインをインストール。
インストールしただけでモジュールは有効になっていた。

$ sudo apt install libapache2-mod-auth-mellon

証明書、秘密鍵、メタ情報を生成するスクリプトを動かす。
クライアントID、エンドポイントURLの順に指定する。

$ mellon_create_metadata https://squid.hogeserver.hogeddns.jp/mellon/metadata https://squid.hogeserver.hogeddns.jp/mellon
$ ls -l
total 8
-rw------- 1 rohhie rohhie 1391 Apr 13 13:20 https_squid.hogeserver.hogeddns.jp_mellon_metadata.cert
-rw------- 1 rohhie rohhie 2484 Apr 13 13:20 https_squid.hogeserver.hogeddns.jp_mellon_metadata.key

実行したところ、xmlファイルが生成されない。
スクリプトをデバッグしてみたところ、RANDFILEとして指定している/dev/urandomというファイルに書き込みができないことが原因で異常終了していたため、書き込みができるランダムな値が入ったファイルを作成して、それを使うように変更した。
※上手くファイルが生成されるなら、やる必要はない。

/usr/sbin/mellon_create_metadata

...
# No files should not be readable by the rest of the world.
umask 0077

RANDOMFILE="$(mktemp -t mellon_create_random.XXXXXXXXXX)"
dd if=/dev/urandom of=$RANDOMFILE bs=256 count=1 2>/dev/null

TEMPLATEFILE="$(mktemp -t mellon_create_sp.XXXXXXXXXX)"

#RANDFILE = /dev/urandom ・・・待避したけれど使わないかも
cat >"$TEMPLATEFILE" <<EOF
RANDFILE = $RANDOMFILE
[req]
default_bits = 3072
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
prompt = no
policy = policy_anything
[req_distinguished_name]
commonName = $HOST
EOF
...

修正したスクリプトを実行して、ファイルを生成する。

$ mellon_create_metadata https://squid.hogeserver.hogeddns.jp/mellon/metadata https://squid.hogeserver.hogeddns.jp/mellon
Output files:
Private key: https_squid.hogeserver.hogeddns.jp_mellon_metadata.key
Certificate: https_squid.hogeserver.hogeddns.jp_mellon_metadata.cert
Metadata: https_squid.hogeserver.hogeddns.jp_mellon_metadata.xml

Host: squid.hogeserver.hogeddns.jp

Endpoints:
SingleLogoutService: https://squid.hogeserver.hogeddns.jp/mellon/logout
AssertionConsumerService: https://squid.hogeserver.hogeddns.jp/mellon/postResponse

$ ls -l
total 12
-rw-r--r-- 1 rohhie rohhie 1391 Apr 13 13:28 https_squid.hogeserver.hogeddns.jp_mellon_metadata.cert
-rw------- 1 rohhie rohhie 2484 Apr 13 13:28 https_squid.hogeserver.hogeddns.jp_mellon_metadata.key
-rw-r--r-- 1 rohhie rohhie 2218 Apr 13 13:28 https_squid.hogeserver.hogeddns.jp_mellon_metadata.xml

生成したxmlファイルは、Keycloakのクライアントとしてインポートすることができた。
さきにKeycloakでクライアントを設定して、それをApacheで設定することもあるかもしれないので、どんな値が設定されたかメモしておく。

項目設定値
Client IDhttps://squid.hogeserver.hogeddns.jp/mellon/metadata
Valid redirect URLshttps://squid.hogeserver.hogeddns.jp/mellon/postResponse
KeysタブのCertificatehttps_squid.hogeserver.hogeddns.jp_mellon_metadata.certの中身

続いて、KeycloakからIdPのメタデーターをダウンロードする。
画面では、レルムを選択した後、ConfiguireのRealm settingsを開くと、Endpoints項目に、IdPのリンクが表示される。
このURLの中身を保管する。テキストを表示させて、コピペで作ってもOK。

$ curl --insecure -o ./idp_metadata.xml https://keycloak.hogeserver.hogeddns.jp/realms/MyHome/protocol/saml/descriptor

ファイルの準備ができたので、/etc/apache/saml2ディレクトリを作って、そこにファイルを持っていく。

$ sudo mkdir /etc/apache2/saml2
$ sudo chown root:root ./*
$ sudo mv ./* /etc/apache2/saml2/

※コピー元のファイルは消してしまうのが良さそう。

Apacheの方では、Proxyの他に通常のページも用意して、SAML認証ができるか試している。

/etc/apache2/sites-available/myproxy.conf

LISTEN 3128

<VirtualHost *:3128>
ServerName squid.hogeserver.hogeddns.jp
ServerAdmin webmaster@hogeserver.hogeddns.jp
DocumentRoot /var/www/html

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

ProxyRequests On
ProxyVia On

<ProxyMatch "^(?!.*((squid|keycloak)\.hogeserver\.hogeddns\.jp)).*">
AuthName "My server authentication"
MellonEndpointPath /mellon/
MellonSPMetadataFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.xml
MellonSPPrivateKeyFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.key
MellonSPCertFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.cert
MellonIdPMetadataFile /etc/apache2/saml2/idp_metadata.xml
MellonSecureCookie On
AuthType Mellon
MellonEnable auth
Require valid-user
</ProxyMatch>
</VirtualHost>

<VirtualHost *:443>
ServerName squid.hogeserver.hogeddns.jp
ServerAdmin webmaster@hogeserver.hogeddns.jp
DocumentRoot /var/www/html

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

SSLEngine on
SSLCertificateFile /opt/keycloak/x509/https/hoge.crt
SSLCertificateKeyFile /opt/keycloak/x509/https/hoge.key

<Location />
AuthName "My server authentication"
MellonEndpointPath /mellon/
MellonSPMetadataFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.xml
MellonSPPrivateKeyFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.key
MellonSPCertFile /etc/apache2/saml2/https_squid.hogeserver.hogeddns.jp_mellon_metadata.cert
MellonIdPMetadataFile /etc/apache2/saml2/idp_metadata.xml
MellonSecureCookie On
AuthType Mellon
MellonEnable auth
Require valid-user
</Location>
</VirtualHost>

設定を反映。

$ sudo systemctl restart apache2

Firefoxで試してみる。

まず、squid.hogeserver.hogeddns.jpとkeycloak.hogeserver.hogeddns.jpには、Proxyの認証なしでアクセスができる。
ページを見に行くと、クライアント証明書の提示を求められ(後述)、SAML認証のためのサインイン画面が表示され、認証されるとページにアクセスできた。

続いて他のページを見に行く。
しかし、一度SAML認証を受けていてもProxyの要求には応えられず、他のページにアクセスすることはできなかった。
結局、Forward Proxyとして動作させることができない。

この結果を見ると、Forward Proxyでユーザー認証する方法はBasicかDigestしかなく、セッション管理のような仕組みはないと分かる。
結局、通信をする度にユーザー・パスワードを送信して、Proxy側に認証されているということになる。
セッション管理のような機能はなく、クライアント証明書を提示することもできないのだった。

Dante

2024/04/28追記

SOCKS5サーバーを立ててみる。

とりあえず動かす

Dante Serverをインストール。

$ sudo apt install dante-server

とりあえず動くように設定する。

/etc/danted.conf

errorlog: /var/log/sockd.errlog
logoutput: /var/log/sockd.log
#debug: 2

internal: 192.168.110.172 port = 1080
external: 192.168.110.172

socksmethod: none

user.privileged: proxy
user.unprivileged: nobody

client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect disconnect error
}

socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: connect disconnect error
}

ログファイルが出力できるように設定を変更。

$ sudo systemctl edit danted.service
### Editing /etc/systemd/system/danted.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

[Service]
ReadWriteDirectories=/var/log

### Lines below this comment will be discarded

### /lib/systemd/system/danted.service
# [Unit]
...

※赤文字部分を追加。

サービスを再起動。

$ sudo systemctl restart danted

Firefoxで手動でProxyを設定する。
SOCKS v5、192.168.110.172:1080、SOCKS v5を使用するときはDNSのプロキシーを使用するにチェックを入れた。

これで色々なところにプロキシーを経由してアクセスができた。

ユーザー認証

確認してみたところ、RFC931、Username、PAM、BSDAUTH、GSSAPIが使えるようだった。
ただ、多くの場合安全ではないとか、認証情報が平文(cleartext)で送られるとされていて、安全なのはGSSAPIだけのようだった。

安全にインターネットに晒して使うなら、KDCをどうにかして浸透させるしかなさそうだ。

Basic認証のパケットを見てみる

今回は、素の状態でForward Proxyを晒そうという話なのだが、Basic認証は危険という話もある。
一方で、HTTPSならパケットは暗号化されるし、Man-in-the-middle的なことが考えられない状態なら、Forward Proxyの認証にそれ程頑張らなくても…という書き込みがあったりしたので、パケットの中身を見てみることにした。

実際のところ、

[ユーザーの端末] – [ローカルネットワーク] – [ISP] <- internet -> [Forward Proxy] <-> internet <-> [接続先]

という経路を考えると、太字部分でBasic認証情報が流れていて、その中でも「ローカルネットワーク」が危ないと想定。
ユーザーが接続するネットワークの安全が確保できれば問題なさそうだけれども、何かのミスで危険にさらされるかもしれない。

ついでに、HTTPSプロキシというものがあるらしいので、それを設定したらどうなるのかを見てみる。

PAC

HTTPSプロキシはOSやブラウザでは設定ができないとのことなので、PACファイルを使うことにする。
https://squid.hogeserver.hogddns.jp/myproxy.pacとして、以下をダウンロードができるようにした。

/var/www/html/myproxy.pac

function FindProxyForURL(url, host) {
return "PROXY 192.168.110.172:3128";
}

これをベースにして、PROXYのところを書き換えて試してみる。

ブラウザを動かすデスクトップで、tcpdumpを使ってパケットをキャプチャーし、Wiresharkで見てみることにする。

$ sudo tcpdump dst host 192.168.110.172 and dst port 3128 -w ./https.cap
$ sudo tcpdump dst host 192.168.110.172 and dst port 3128 -w ./http.cap

といった形で、アクセス先のでファイルを分けて確認した。

PROXY HOST:PORT

HTTPへのアクセスでは、GETのヘッダーでProxy-Authorization: Basicとして<user>:<password>が渡されていた。
base64でデコードすれば簡単に見られる。

では、HTTPSへのアクセスはどうかというと、CONNECTのタイミングで、同様にBasic認証情報が渡されていた。
1つのページを見るにしても、そのページの中身によって色々なサイトにアクセスをしていて、そのたびに認証情報が送られる。
つまり、アクセスしているサイトと、ProxyのBasic認証情報がセットで見えるということのようだ。

この感じからすると、どちらも安全とはいえないように思われる。
なんとなく予想していた通りだった…

HTTPS HOST:PORT

Apacheでは、HTTPのサイトも、HTTPSのサイトも見られない。
そのため、パケットを見るのは諦めた。

具体的には、

http://test-onw.hogeserver.hogeddns.jp
https://test-two.hogeserver.hogeddns.jp
https://<他所のサイト>.org

のすべてでSSL_ERROR_RX_RECORD_TOO_LONGが発生し、解決策は見つからなかった。

ApacheにはワイルドカードのSSL証明書(*.hogeserver.hogeddns.jp)を持たせているので、test-twoへのアクセスはできそうなものだがエラーが発生するので、Apacheをトンネル動作させていることに原因があるのかもしれないが、動作確認はこれで打ち切り。

また、ChromeではHTTPS指定が上手く動かなかった。
UbuntuのOS自体にProxy設定をするのだけれど、ネットワークに接続する時点ではこれを見に行かない。
Chromeは起動するとOSのProxy設定を見に行って、PACファイルをダウンロードしてくるが、HTTPSの指定は受け付けないようだ。

なお、UbuntuではProxy設定はできるものの、その動作はアプリケーションに任されているようで、curlやaptはProxy設定を使っていないし、Chromeでもplayやaccounts等はProxy設定を使っていないようだった。
WindowsでもアプリケーションごとにOSのProxy設定を使うかどうか選択できたような気がするから、Proxyを利用するかどうかはアプリケーションに委ねられているのだろう。

ついでに…PACファイルの配布を保護

結論からすると、PACファイルの配布を保護する運用は難しそうだ。

SAML認証を設定した状態だと、PACファイルにアクセスした際にログインページにリダイレクトされる。
そのためファイルがダウンロードされず、Proxy設定はできない。

PACファイルへのアクセスにクライアント証明書の提示を求めたところ、Firefoxは証明書を提示できてPACファイルが取り込まれた。
しかし、Chromeでは証明書を提示できずPACファイルは取り込めなかったので、ユーザーが使用するブラウザによって上手くいかないことが分かった。

Keycloak

認証のバックエンドにSAMLを試すため、別の仮想サーバーでKeycloakを動作させることにした。
keycloak.hogeserver.hogeddns.jpでアクセスできるように設定した。

まずは動かす

SAML認証のためのIdPとして、プロダクションモードでKeycloakを動かす。
開発モードでも全然問題ないと思うけれども、プロダクションモードに興味があったのでやってみたもの。

ベースとなるディレクトリを作成。

$ sudo mkdir -p /opt/keycloak
$ cd /opt/keycloak

Dockerfileを作成し、プライベート認証局の証明書をコンテナに入れて、信頼されたCAとして登録するように仕掛ける。

/opt/keycloak/Dockerfile

FROM quay.io/keycloak/keycloak:latest as builder
COPY ./hoge-ca.crt /etc/pki/ca-trust/source/anchors/hoge-ca.crt
USER root
RUN keytool -storepass changeit -noprompt -import -trustcacerts -alias hoge-ca -cacerts -file /etc/pki/ca-trust/source/anchors/hoge-ca.crt
USER keycloak

Dockerfileで作られたイメージを起動するdocker composeファイルを作成する。
ボリュームごと消してもデーターが残るように、/opt/keycloak/dataをバインドマウントする。
また、プロダクションモードで起動できるように、サーバー証明書と秘密鍵を設定する。

/opt/keycloak/docker-compose.yml

services:
keycloak:
build: ./
# image: quay.io/keycloak/keycloak:latest
container_name: keycloak
restart: unless-stopped
volumes:
- /opt/keycloak/data:/opt/keycloak/data
- /opt/keycloak/x509:/etc/x509
ports:
- 8443:8443
command:
- start
- --proxy-headers=xforwarded
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
KC_HOSTNAME: keycloak.hogeserver.hogeddns.jp
KC_HOSTNAME_ADMIN: keycloak.hogeserver.hogeddns.jp
KC_HTTPS_CERTIFICATE_FILE: /etc/x509/https/hoge.crt
KC_HTTPS_CERTIFICATE_KEY_FILE: /etc/x509/https/hoge.key
TZ: Asia/Tokyo

使用する証明書と秘密鍵を用意する。

ディレクトリ内容備考
/opt/keycloak/hoge-ca.crtプライベート認証局のCA証明書LDAPSで接続する先がhoge-caで署名したSSL証明書を使用しているため、hoge-caを信頼する必要がある。
/opt/keycloak/x509/https/hoge.crtSSL証明書hoge-caが署名している。
/opt/keycloak/x509/https/hoge.keySSL証明書の秘密鍵パスワードなしにしている。

プライベート認証局で署名した証明書と秘密鍵を/opt/keycloak/x509/httpsに保管した。

$ sudo mkdir -p /opt/keycloak/x509/https

プライベート認証局のCA証明書と、証明書・秘密鍵をどうにかして持ってくる。

$ ls -l /opt/keycloak/ /opt/keycloak/x509/https/
/opt/keycloak/:
total 12
-rw-r--r-- 1 root root 671 Apr 13 09:23 docker-compose.yml
-rw-r--r-- 1 root root 1375 Apr 13 06:50 hoge-ca.crt
drwxr-xr-x 3 root root 4096 Apr 12 08:45 x509

/opt/keycloak/x509/https/:
total 8
-rw-r--r-- 1 root root 1509 Apr 12 08:45 hoge.crt
-rw-r----- 1 root root 1679 Apr 12 08:46 hoge.key

dataディレクトリをバインドマウントしているので、そのディレクトリを作成する。

$ sudo mkdir /opt/keycloak/data
$ sudo chown 1000:1000 /opt/keycloak/data

Keycloakを起動。

$ sudo docker compose up -d

プロダクションモードで動き出した模様。
ブラウザで、https://keycloak.hogeserver.hogeddns.jp:8443/admin にアクセスする。
ユーザーadmin、パスワードadminでログインすると、ウェルカムページが表示される。

ポート443でアクセスできるようにする

Keycloakはユーザーモードで動いているので、443ポートでサービスを始められないらしい(試していない)。
ポートのマッピングを443:8443とすれば動くかというと、上手く動かなかった。

仕方がないので、ApacheでReverse Proxyさせることにした。
ついでに、クライアント証明書でアクセスを制限してみよう。

$ sudo apt install apache2

設定ファイルを作成。
証明書はKeycloakのために用意したものをそのまま使っている。

/etc/apache2/sites-available/keycloak.conf

<VirtualHost *:443>
ServerName keycloak.hogeserver.hogeddns.jp
ServerAdmin webmaster@hogeserver.hogeddns.jp
DocumentRoot /var/www/html

ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined

# プライベート認証局が署名したCAを信頼しつつProxyする
SSLProxyEngine on
SSLProxyCACertificateFIle /opt/keycloak/hoge-ca.crt
ProxyPreserveHost on
RemoteIPHeader X-Forwarded-For
# RequestHeader set X-Forwarded-Proto "https"
ProxyPass / https://localhost:8443/
ProxyPassReverse / https://localhost:8443/

# 接続してきたクライアントとの通信を暗号化する
SSLEngine on
SSLCertificateFile /opt/keycloak/x509/https/hoge.crt
SSLCertificateKeyFile /opt/keycloak/x509/https/hoge.key

# アクセスするためにクライアント証明書を要求する
# SSLCARevocationFile /opt/keycloak/x509/https/hoge.crl
# SSLCARevocationCheck leaf
SSLVerifyClient require
SSLVerifyDepth 1
<Location />
Require expr (%{SSL_CLIENT_VERIFY} == "SUCCESS")
</Location>
</VirtualHost>

※本来はCRLもちゃんとチェックしておくべきだけれども、ここでは省略。

設定を反映させる。

$ sudo a2ensite keycloak.conf
$ sudo a2enmod ssl proxy_http remoteip
$ sudo systemctl restart apache2

ブラウザで、https://keycloak.hogeserver.hogeddns.jp/admin にアクセスする。
クライアント証明書の提示を求められ、その後にログインができるようならOK。

Samba AD DCのLDAPで認証

MyHomeというレルムを作成。

ユーザーフェデレーションで、LDAPプロバイダーを追加。
多くはデフォルトで行けるけれど、今回の設定で気になるところだけメモ。

分類項目設定値
General optionsUI display nameldap
VendorActive Directory
Connection and authentication settingsConnection URLldaps://addc.hogeserver.hogeddns.jp
Enable StartTLSOff
Use Truststore SPIAlways
Bind typesimple
Bind DNssoauth@hogeserver.hogeddns.jp
Bind credentialsp@ssword123
LDAP searching and updatingEdit modeREAD_ONLY
User DNCN=users,dc=hogeserver,dc=hogeddns,dc=jp
Username LDAP attributesAMAccountName
Search scopeSubtree

Edit modeはREAD_ONLYにしている。
過去にうっかりユーザーを消してしまったことがあって、この選択は常に読み取り専用。便利に使うのは、もう少し理解できてから。

追加できたら、認証を試す。
Clients → Clients list の一覧で account の行の一番右、この設定だと
https://keycloak.hogeserver.hogeddns.jp/realms/master/account/
にアクセスし、Samba AD DCのユーザーでサインインできればOK。

クライアントの設定は本編で

さいごに

世の中的にはプロキシーサービスを提供しているケースがたくさんあるので、OSSで実装できるかもと思っていたが、探してみたら全然そんなことはなかった。リバースプロキシーで入ってくる人を制限することはできるが、フォワードプロキシーの制限は難しい。

セッションを確立してから行う認証ならば、色々なものと連携できるのだけれど、セッションを確立するかどうかを決めるところなので、複雑な認証方式は使えないということなのだと思う。

やっぱりVPNで接続して、フルトンネルの状態でプロキシーを使うのが安全なようだ。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他