Subversion のバーチャルホストを VPN アドレスに向けても変わるのは、どのサイトがリクエストに応答するかであり、誰が送信を許されるかではありません。アクセス検査は後段で、URL に一致したセクションの規則に従って行われ、ホスト名がその判断に入ることはありません。ServerName だけを変更したのなら、リポジトリの到達性は昨日とまったく同じです。設定では、どのソケットが接続を受け付けたのか、リクエストがどの識別情報を運んでいたのかという二つの別個の問いに答える必要があります。

ServerName が実際に決めていること

名前ベースのバーチャルホスティングは Host ヘッダーを ServerName と ServerAlias のエントリと比較し、一つのホストを選び、そのホストにリクエストを処理させます。接続そのものは、ホストではなくサーバーに属する Listen ソケットが以前に受け付けています。私的に見える名前が何の保護にもならないのは、ソケットに到達できるクライアントなら誰でもその Host ヘッダーを送れるからです。名前はルーティングの鍵であり、ルーティングの鍵を選ぶのは呼び出し側です。

当て推量なしで対応関係を確認できます。apachectl -S は各ホスト、そのポート、名前を出力し、アドレスに対してどれが既定かを示します。

RequireAll は両方の検査を必須にするコンテナーです

Apache 2.4 は一つのセクションの Require ディレクティブを暗黙の RequireAny で包むため、裸の二つのディレクティブはどちらか一方で足りるという意味になり、コンテナーを加えると意味が反転します。

<Location /svn/repo>
    AuthType Basic
    AuthName "Subversion"
    AuthBasicProvider file
    AuthUserFile /etc/apache2/svn.htpasswd
    <RequireAll>
        Require ip 10.8.0.0/24
        Require valid-user
    </RequireAll>
</Location>

RequireAll が要求するのは、トンネル範囲内の送信元アドレスとリポジトリが受け入れる利用者という二つの条件です。Subversion の URL にとって VPN 限定とはそういう意味であるべきです。mod_authz_core のリファレンス にコンテナーの規則が記されています。ブロックはリポジトリの既存の Location の内側に置き、すでに機能している DAV ディレクティブは、この断片が AuthType もパスワードファイルもパス単位の認可も置き換えないため、そのままにしてください。

送信元の検査は mod_authz_host によるものなので、他の何かをデバッグする前に読み込まれていることを確認してください。apachectl -M | grep authz_host

Apache が評価するアドレスはソケットのアドレスです

Require ip は Apache が接続のために記録したアドレスと比較しますが、それはクライアントが自分にあると思っているアドレスとは限りません。

VPN がアドレスを変換せずにルーティングする場合、Apache はトンネルのアドレスを見て 10.8.0.0/24 が一致します。ゲートウェイが変換する場合、Apache はゲートウェイを見て規則は静かに何にも一致しません。ホストの手前で TLS を終端するロードバランサーの背後でも同じことが起こります。その場合のために mod_remoteip があり、RemoteIPHeader X-Forwarded-For をバランサー用の RemoteIPTrustedProxy と組み合わせると記録されたアドレスが置き換わり、そのとき初めて範囲に比較対象ができます。

任意の転送ヘッダーを信頼するのは、規則をまったく持たないより悪い選択です。どのクライアントも 10.8.0.9 を含む X-Forwarded-For を送れば、信頼できるホップに固定されていない範囲検査を満たせます。

リスナーとリポジトリのポリシーを階層化する

バインドは別の制御であり、多くの場合より単純です。Listen 10.8.0.1:443 はどのソケットが存在するかを決め、一度も開かれなかったソケット上でリクエストがホストを選ぶことはできません。代償は起動順序で、Apache の起動時にトンネルのインターフェースが落ちていれば、バインドは失敗しサービスは立ち上がりません。ユニットの順序と組み合わせるか、トンネルとは独立に存在するアドレスにバインドしてください。

パスの権限は AuthzSVNAccessFile に残ります。サイトに対する有効なアカウントは、プロジェクトのパスへの書き込み権限と同じ許可ではなく、ネットワークの規則はそれを変えません。

ブロックがインクルードされたファイルにある場合はセクションのマージに注意してください。Apache はネストしたセクションをまたいで認可コンテナーをマージし、最も具体的に一致するセクションは要件を追加するのではなく置き換えることがあるため、より狭いセクションが独自のものを定義すると /svn の規則が /svn/repo/trunk に届かない可能性があります。

終わったと言う前に両方の拒否を証明する

一つではなく四つのリクエストを試してください。VPN の内側で有効なアカウントならリポジトリの一覧が得られるはずです。VPN の内側で誤ったパスワードなら一覧ではなく 401 が返るはずです。VPN の外から有効な資格情報なら 403 が返るはずで、別のホスト名に対する同じ公開リクエストも同じように失敗しなければなりません。

ケースごとにコマンド一つで十分で、curl -sI -u user:pass https://svn.example.com/svn/repo/ を実行してステータス行を読むだけです。動作するケースは、svn info がコミットや更新の使う DAV メソッドを行使するため、実際のクライアントでも繰り返してください。

四つの結果のどれかが意図したポリシーと食い違うなら、設定は未完成であり、ホストの名前を変えてもそれは変わりません。

関連記事