ラベル apache の投稿を表示しています。 すべての投稿を表示
ラベル apache の投稿を表示しています。 すべての投稿を表示

ウェブサーバーへのIP直アクセスを制限しつつ、ヘルスチェックを受容する



IPアドレス直のサーバーアクセスを403制限しつつ、ロードバランサのヘルスチェックを通すapacheconfの設定


ポイント


  • 一番初めにローディングされるVirtualHostディレクティブがデフォルトサーバーとなるため、Include順を考慮したconfファイルを命名する
  • ファイル命名規則のInclude順でデフォルトサーバーは自明だが、明示的にServerName anyですべてのホスト名を捕まえている
  • デフォルトサーバーは:80:443ポートごとに設定が必要。 <VirtualHost *:*>だと、うまく動かない
  • デフォルトサーバーはhttpd -Sで確認する

httpd -S
VirtualHost configuration:
*:80                   is a NameVirtualHost
         default server any (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:1)
         port 80 namevhost any (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:1)
         port 80 namevhost webapp.com (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:28)
*:443                  is a NameVirtualHost
         default server any (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:15)
         port 443 namevhost any (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:15)
         port 443 namevhost webapp.com (/usr/local/httpd-2.4/conf/extra/httpd-vhosts.conf:45)

設定


  • :80:443それぞれのポートでデフォルトのバーチャルホストを定義する
  • ドキュメントルートを/var/www/_defaultに設定して、ロードバランサのヘルスチェックファイル/hb.html=>/var/www/_default/hb.htmlを作成
  • ヘルスチェック=hb.htmlはELB=サブネットワークにだけ公開したいので、リモートアドレスをRemoteIPTrustedProxyでX-Forwarded-For偽装をはじく。
  • リモートアドレスがtrustでなければ、X-Forwarded-Forを剥がしてクライアントの本来のリモートアドレスを採用する
  • :443のデフォルトサーバーはアクセス不要なので全拒否



<VirtualHost *:80>
    DocumentRoot "/var/www/_default/" 
    ServerName any
    <Directory "/var/www/_default/">
        Require all granted
    </Directory>
    # リモートアドレスがプロキシーのRemoteIPTrustedProxyアドレス帯ならば、X-Forwarded-Forを採用する。X-Forwarded-Forがなければリモートアドレスを採用する=ロードバランサである
    # リモートアドレスがプロキシーのRemoteIPTrustedProxyアドレス帯でなれけば、X-Forwarded-Forを剥がして、リモートアドレスを採用する=プロキシーを経由しないインターネットアクセス
    RemoteIPHeader X-Forwarded-For
    RemoteIPTrustedProxy 172.21.0.0/16

    # リモートアドレスがプロキシーのRemoteIPTrustedProxyアドレス帯ならば、X-Forwarded-Forを採用する。X-Forwarded-Forがなければリモート
アドレスを採用する=ロードバランサである
    # リモートアドレスがプロキシーのRemoteIPTrustedProxyアドレス帯でなれけば、X-Forwarded-Forを剥がして、リモートアドレスを採用する=プ
ロキシーを経由しないインターネットアクセス
    RemoteIPHeader X-Forwarded-For
    RemoteIPTrustedProxy 172.21.0.0/16

    # サブネットワーク直の侵入のみ許可する
    <Location />
        <RequireAll>
            Require ip 172.21.0.0/16
        </RequireAll>
    </Location>

</VirtualHost>

<VirtualHost *:443>
    ServerName any
    <Location />
        Require all denied
    </Location> 
</VirtualHost>

MVCフレームワークでRequest_URLとRequire envでアクセス制御するときの注意点


apacheconfまたは.htaccessでRequest_URIに基づいてSetEnvIfで環境変数でフラグを立てて、Requireでホワイトリスト判定しているのに拒否される場合の対策。


通常



SetEnvIf Request_URI {ロケーション} Requireでロケーションで判定するシンプルな例


# http://host/path/to/location でリクエスト

# ロケーションにマッチして環境変数をフラグする
SetEnvIf Request_URI /path/to/location allowed_location

# Requireでフラグを判定して許可
Require env allowed_location

MVCフレームワーク下におけるリダイレクト影響



Webフレームワーク+apache環境ではだいたいmod_rewriteを使った次のコードスニペットが公開されている。


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
# => http://host/index.php?_url=/path/to/location にインターナルリダイレクトする

原因


上記インターナルリダイレクトでconfの再評価が入るとロケーションが一致しなくなる。


# http://host/index.php?_url=/path/to/location でインターナルリダイレクト

# ロケーションにマッチしないので環境変数をフラグしない
SetEnvIf Request_URI /path/to/location allowed_location

# Requireでフラグを判定して拒否
Require env allowed_location

対策


リダイレクト後のロケーションもフラグメントするのではさすがに不毛だわ


SetEnvIf Request_URI /path/to/location allowed_location
# リダイレクト後のロケーションもフラグメント
SetEnvIf Request_URI /index.php?_url=/path/to/location allowed_location

# Requireでフラグを判定して拒否
Require env allowed_location

結論


mod_rewriteによるリダイレクトでは、前回評価で定義した環境変数がREDIRECT_プレフィックス付きでREDIRECT_{環境変数}のように引き継がれるので、
RequireREDIRECT_{環境変数}を追加して、本来のユーザリクエストに基づく評価結果をもって通過させる。


# リダイレクト再入ではマッチしない
SetEnvIf Request_URI /path/to/location allowed_location

# が、REDIRECT_allowed_locationを評価して許可
Require env allowed_location REDIRECT_allowed_location

PHP DotEnvのスレッドセーフ対応


 

EC2のマイクロインスタンスでホストしているWEBサーバーに同時並列リクエストでワークロード負荷を与えると、DBやセッションストアのmemcachedへのTCP接続が稀に失敗する事象に遭遇して手間取ったはなし


MySQL・・・No such file or directory
→ TCP/IP接続なんだが、UNIXドメインソケットが見つからない系のエラーなんで?


memcached・・・can not to connect memcached


切り分け調査


  • ワークロード?
    • EC2インスタンスのロードアベレージは余裕で1未満
    • ネットワーク帯域は余裕
    • コネクション数、パケットロストなどRDS, Elasticacheのメトリクスに異常が見られない
  • DNS?
    • 一応Route53のプライベートホストゾーンから、エンドポイント直に変更してみるが状況変わらず
    • VPCのDNSスロットリング制限で蹴られている?いやそこまでいじめてない

Amazon が提供する DNS サーバーは、Elastic Network Interface ごとに 1 秒あたり 1024 パケットの制限を適用します。Amazon が提供する DNS サーバーは、この制限を超えたトラフィックをすべて拒否します。

VPC DNS スロットリングが原因で、Amazon が提供する DNS サーバーへの DNS クエリが失敗しているかどうかを確認するにはどうすればよいですか?


ここまで遠回りして、フレームワークのセッションアダプタからphpネイティブに変更するも失敗するが、きれいに{ホスト}:{ポート}が歯抜けのエラーログが出た


PHP Warning:  session_start(): Failed to parse session.save_path (error at offset 0, url was 'tcp://:')

DotEnv


apache mpm_event_module×スレッドセーフmod_phpな環境で接続コンフィグにDotEnvにImmutableで.envファイルをローディングさせて$_ENVスーパーグローバルで参照していた


$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();

DotEnvをcreateImmutableで起動するとputenv/getenvがスレッドセーフではないため、次の順序で並列リクエストの環境変数がタイミングによってクリーンアップされたようだ。


1 番目のリクエスト: 変数が存在しない → ロード 2 番目のリクエスト: 変数が存在する → ロードしない 1 番目のリクエスト: 終了、変数をクリーンアップする 2 番目のリクエスト: 変数を使用する → 変数はもう存在しない、によってクリーンアップされる1回目のリクエスト

https://github.com/vlucas/phpdotenv/issues/248
When you have two or more sites on one server, the sites's .env will affect each other · Issue #219 · vlucas/phpdotenv


Using getenv() and putenv() is strongly discouraged due to the fact that these functions are not thread safe, however it is still possible to instruct PHP dotenv to use these functions. Instead of calling Dotenv::createImmutable, one can call Dotenv::createUnsafeImmutable, which will add the PutenvAdapter behind the scenes. Your environment variables will now be available using the getenv method, as well as the super-globals:

公式にもスレッドセーフでない旨ため注意書きがあった


Immutableモードは定義済みの環境変数のローディングは無視され(2)、リクエストの終了とともに破棄される(4)
一方でMutableモードはリクエストごとに独立したストアに確保するとみえて、スレッド競合の問題は起きなくなった
createUnsafeImmutableでv5系以降の実装で、v4系にはなかったのでMutableがそれにあたるっぽい


$dotenv = Dotenv::createMutable(__DIR__);
$dotenv->load();