べにやまぶろぐ

技術寄りの話を書くつもり

Nginx で http も https も IP 直打ちのアクセスを弾きたい

XXX.XXX.XXX.XXX mysite.com という A レコードを持つサイトを運用していて、http と https でのアクセスを受け付けているとします。

Nginx では下記のようなサーバブロックがあるイメージです。

server {
    listen 80;
    server_name mysite.com;
    return 301 https://$server_name$request_uri; #https にリダイレクト
}

server {
    listen 443 ssl;
    server_name mysite.com;
…
}

そして IP 直指定で飛んでくる不審なリクエストには応答したくないので Nginx 内の公式ドキュメント サーバ名 を参考に下記のようなサーバブロックを追加してみます。

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

このとき、当然 http://XXX.XXX.XXX.XXX へのリクエストには 444 が返るのですが、https://XXX.XXX.XXX.XXX は SSL 証明書の警告が出るもののアクセスできてしまいます。

nginx はどのようにリクエストを処理するか によれば

もし “Host” ヘッダがどのサーバ名ともマッチしない場合、またはリクエストにこのフィールドがまったく含まれていない場合は、nginxはこのリクエストをデフォルトサーバに振り向けます。上記の設定ではデフォルトサーバは最初のもので、これは nginx の標準的なデフォルトの挙動です。

ということで 443 に対するディレクティブが mysite.com のものしかない場合、https へのリクエストは全て mysite.com のサーバブロックで処理してしまうようです。

では、80 に加えて 443 に対する default_server も足してみると良さそうです。

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

server {
    listen       443  ssl default_server;
    server_name  _;
    ssl_certificate /path/to/mysite.crt
    ssl_certificate_key /path/to/mysite.key
    return       444;
}

最初、https://mysite.com へのリクエストまでもが 444 でドロップしてしまっていたのですが ssl_certificate と ssl_certificate_key を記載していなくて

no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: x.x.x.x, server: 0.0.0.0:443

とエラーになっていただけでした。このデフォルトサーバの証明書はオレオレでも構わないと思います。

あるいは

stackoverflow.com

の回答にあるような

server {
    listen 443;
    server_name mysite.com;

    if ($host != "mysite.com") {
        return 444;
    }
    ...
}

でも IP 直のアクセスを落とすことができますが、server_name を指定しているのにもう一度チェックしていて若干冗長な感じがあります。

あとこちらでは前段に HAproxy を置いてそこで落としているようです。

security.stackexchange.com

今回のサイトは IP とドメインが1対1なので SNI 云々については気にする必要がありませんでしたが、複数ドメインをホストするようなサービスだと default_server での直 IP リクエストドロップが良さそうです。