特殊なケースすぎてもう使うことはない設定だと思うけど、全然思い通りにいかなくて悩んでしまったのでメモ。
検証に使ったnginxのバージョンは1.18.0です。
達成したいこと
- https://example.com 全体にクライアント認証を適用したいが、 https://example.com/ignore 以下はクライアント認証を除外したい。
条件
/
全体 および/ignore
内では、php-fpmによりphpが実行できるようにしたい。対象の拡張子は.php
とする。/
全体 および/ignore
は設定でtry_files $uri $uri/ /index.php?$args;
による内部リダイレクトをしているので、https://example.com/index.php は状況に応じてクライアント認証を適用・除外する必要がある。
例
- https://example.com/example.php
クライアント認証が必要 - https://example.com/abc?example (
/abc
は存在しない)
https://example.com/index.php?example に内部リダイレクトする。
クライアント認証が必要 - https://example.com/ignore/example.php
クライアント認証が不要 - https://example.com/ignore/abc?example (
/abc
は存在しない)
https://example.com/index.php?example に内部リダイレクトする。
クライアント認証が不要
ダメだった方法
server {
listen 443 ssl http2;
### ...いろいろ省略...
### クライアント認証設定
ssl_client_certificate /etc/nginx/ssl/xxx.pem;
ssl_verify_client optional;
location / {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
try_files $uri $uri/ /index.php?$args;
}
location /ignore {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
fastcgi_pass unix:/run/php-fpm/www.sock;
### いろいろ省略
}
}
locationディレクティブでのifの使用は推奨されていませんが、ifを使わずに特定ディレクトリを除外する方法はおそらく存在しないため、ifを使いました。
If is Evil… when used in location context | NGINX
ぱっと見は動いてるように見えたのですが https://example.com/ignore/example.php にアクセスするとクライアント認証を要求されます。location ~ \.php$
が先に評価されるからですね。
今回採用した方法
server {
listen 443 ssl http2;
### ...いろいろ省略...
### クライアント認証設定
ssl_client_certificate /etc/nginx/ssl/xxx.pem;
ssl_verify_client optional;
location / {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
set $verified T;
try_files $uri $uri/ /index.php?$args;
}
location /ignore {
set $verified T;
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
set $check NG;
if ($ssl_client_verify = SUCCESS) {
set $check OK;
}
if ($verified = T) {
set $check OK;
}
if ($check != OK) {
return 403;
}
fastcgi_pass unix:/run/php-fpm/www.sock;
### いろいろ省略
}
}
認証する必要がない・認証済みの場合を表すフラグを設定して、php実行時にそのフラグを評価するようにしました。ifを使わずに実現する方法がわかったらそちらを使いたいですね…。