Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
NginxでのSSL設定の細かい意味

NginxでのSSL設定(もうちょっと詳しく)

listen 443 ssl
ssl     on;
  • nginxの確認

nginxをmakeするときに「–with-openssl=」オプションを使わずにsslに対応してあることを確認しておく。

$ `which nginx` -V 2>&1 | grep openssl
$ (何も表示されないことを確認)
  • 次にopensslのバージョン確認。
$ rpm -qa openssl
$ openssl-1.0.1i-1.78.xxxxxxxx.x86_64

※ 「xxxxxxx」はPlatform名。 バージョンが1.0.1i以上であることを確認しておく。

もしopensslのバージョンが古いなら、

$ sudo yum update openssl -y

と、実行してバージョンアップしておく。

もし、「–with-openssl=」オプション付きでnginxをmakeしてある場合は、新しいバージョンのopensslのソースを準備してnginxを作成し直すこと。

暗号化スイートの設定

対応する暗号化スイートは、ssl_ciphersディレクティブで設定するが、ネット上の多くの情報はこれらの記載がないものが多い。 つまりは、クライアントサイドが指定した暗号化スイートで暗号化していることになる。 安全なSSL接続を目指すなら、サーバサイドで指定した暗号化スイートで通信を暗号化するほうが望ましい。 ということで、まずは世の中のデ・ファクト・スタンダードはどうなっているのか調べてみたが、MozillaがMozilla Web siteに使用する、HTTPSの推奨設定が一番多くのサイトでの引用元になっているようだ。

Security/Server Side TLS – MozillaWiki
ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK

と、一般的な用途として上記が記載されている。

ちなみに、上記の記載の仕方だが、

  • 優先順位順に記載する
  • 複数指定時のデリミタは「:」(コロン)を利用する
  • プレフィクスに「!」(エクスクラメーション)を指定した場合は除外対象の指定になる

というルールに従って記述されている。

実際には接続される環境(端末含む)を考えて、設定するのが良い。 例えばXPのIE6とか、すでにサポート外の環境を外すならRC4使っている幾つかは切ってOKなのだが、世の中にはそんな環境もまだ残っているようなので、そのあたりも入れておくのが無難とか。 今回は上記の推奨設定を利用しようと思う。

ということで、今回のssl_ciphersディレクティブの指定は以下のようにした。

    ssl_ciphers          'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128:AES256:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK’;

SSL/TLSプロトコルの指定

SSL/TLSプロトコル(暗号化方式)の指定は、ssl_protocolsディレクティブで指定する。 幾つか指定できるプロトコルはあるが、SSLv2プロトコルは古いバージョンで定義されたプロトコルで現在は非推奨なので外すのが鉄板。 またSSLv3も主要な機能が不足していて、事実上すべてのクライアントがTLS1.0のサポートに変わっている。 そのため、余程の理由がない限りはSSLv3もサポートするべきではない。

    ssl_protocols     TLSv1 TLSv1.1 TLSv1.2;

サーバ側指定の暗号化プロトコルの優先設定

ここまで暗号化スイートと暗号化方式の指定を、それぞれssl_ciphers、ssl_protocolsディレクティブで指定したが、これらが優先して機能しなくては意味がない。 nginxではssl_prefer_server_ciphersというディレクティブで、サーバ指定の暗号化スイートが優先されるように設定する。

ということで、以下のように設定した。

    ssl_prefer_server_ciphers     on;

DH鍵交換に使用するパラメータファイル

次に、暗号化スイートの設定内で指定したものにDH(Diffie-Hellman)鍵交換を含む暗号化スイート(DHE-RSA-AES128-GCM-SHA256とか)を指定した場合、鍵交換において使用されるパラメータファイルを作成し、指定しなくてはならない。 今回は幾つか指定しているので、このファイルを作成する必要がある。 ここでも環境を想定しないとならない。 例えば JDK6では鍵長が1024bitまでしかサポートされていないとか。 これら環境から接続がある場合は1024bit長でパラメータファイルを作成する。 作成方法は以下のようにopensslコマンドで作成する。

$ openssl dhparam <鍵長> -out <作成するファイル名>

ということで、今回はセキュリティレベルは

SSLセッションキャッシュ

サーバの負荷を削減するために、SSLのセッションキャッシュを利用する設定を行います。

  • SSLセッションキャッシュの種類とサイズの指定 - ssl_session_cache

ssl_session_cacheにはSSLセッションキャッシュをどのように扱うかとそのキャッシュサイズを指定します。次のような文字列を指定することができます。

  • off セッションの再利用を明示的に禁止
  • none クライアントにはセッションの再利用を明示的には禁止しないが、実際は利用不可
  • builtin OpenSSL組み込みを利用。一つのワーカープロセスのみが利用可能。
  • shared すべてのワーカープロセスで共有。「shared:名前:サイズ」の形式で記述。

デフォルトはnoneです。

サーバの負荷を削減するためにはsharedを使うのが望ましいです。例えば、キャッシュサイズを10MBに設定するには次のように記述します。

ssl_session_cache shared:SSL:10m;

SSLセッションキャッシュのタイムアウトの指定 - ssl_session_timeout

ssl_session_timeoutディレクティブにはSSLセッションキャッシュに保管するSSLセッションの情報のタイムアウト時間を指定します。デフォルトは"5m"(5分)です。例えば、10分に設定するには次のように記述します。

ssl_session_timeout 10m;

設定TIPS

  • HTTPとHTTPSの設定を共有

上述しましたが、listenディレクティブにsslパラメータを付けると、そのポートでSSLを有効にして待ち受けるようになります。この機能を使い、次のようにsslパラメータを付けない80番ポートを指定したlistenディレクティブとsslパラメータを付けた443番ポートを指定したlistenディレクティブを設定すると、同じバーチャルサーバの設定でHTTPとHTTPSの両方を有効にすることができます。

server {
    listen          80;
    listen          443 ssl;
    server_name     example.jp;
    ssl_certificate /etc/nginx/cert.pem;
    ...
}

このときにはsslディレクティブを記述しないでください。TLS/SSLの有効・無効はlistenディレクティブのsslパラメータの有無で判断するようになります。もし、sslディレクティブでonを指定すると80番ポートでもTLS/SSLで待ち受けるようになってしまいます。

  • IPベースのバーチャルサーバ

TLS/SSL対応のIPベースのバーチャルサーバを複数持つためには、次のようにバーチャルサーバ毎に待ち受ける異なるIPアドレスをlistenディレクティブに指定します。

server {
    listen          192.0.2.1:443;
    server_name     example.jp;
    ssl             on;
    ssl_certificate /etc/nginx/cert-example.jp.pem;
    ...
}

server {
    listen          192.0.2.2:443;
    server_name     example.com;
    ssl             on;
    ssl_certificate /etc/nginx/cert-example.com.pem;
    ...
}
  • 名前ベースのバーチャルサーバ

従来のTLS/SSLのプロトコルでは、ハンドシェイクの際にクライアントはURIのホスト名の情報をサーバに渡すことができないため、サーバはどのバーチャルサーバに対するリクエストが判断できませんでした。そのため、名前ベースのバーチャルサーバは利用できませんでした。しかし、Server Name Indication(以降、単にSNIと呼ぶ)というTLSの拡張機能により、ハンドシェイクの際にホスト名の情報を送ることができるようになりました。ただし、このSNIを利用するためには、サーバとクライアントの両方がSNIをサポートしている必要があります。なお、Server Name Indication - Wikipediaにサポート状況が掲載されています。クライアントにWindows XPの環境が残っている限り、実質的にSNIのサポートは難しいと思われます。

nginx自体はSNIに対応していますが、SNIの機能を有効にしてビルドされている必要があります。SNIの機能が有効であるかは次のようなコマンドを実行して、"TLS SNI support enabled"という文字列が出力されるかでわかります。

$ nginx -V 2>&1 | grep SNI
TLS SNI support enabled

SNIが有効であるときには、クライアントがリクエストしたホスト名に対応したバーチャルサーバの設定が適応され、そのサーバ証明書が使われます。

server {
    listen          443;
    server_name     example.jp;
    ssl             on;
    ssl_certificate /etc/nginx/cert-example.jp.pem;
    ...
}

server {
    listen          443;
    server_name     example.com;
    ssl             on;
    ssl_certificate /etc/nginx/cert-example.com.pem;
    ...
}

なお、SNIが無効であるとき、あるいはクライアントがSNIに対応していないときには、デフォルトサーバのバーチャルサーバが適応され、そのサーバ証明書が使われます。

以上でnginxのTLS/SSLの設定についての説明は終わりです。次回はよく使われる設定(認証、アクセス

default_serverは対象のポートに対して有効だから 下記のようにポート毎にデフォルトサーバを指定できる。

server {
    listen       80;
    listen       8080  default_server;
    server_name  example.net;
    ...
}

server {
    listen       80  default_server;
    listen       8080;
    server_name  example.org;
    ...
}
catch-all

どの server_name にも引っかからなかった場合 catch-allとして受け入れるserverをつくっておいた方が安全。 設定は _ で指定。

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}
  • サブドメインを指定しない場合の設定
server {
    listen       80;
    server_name  example.org  www.example.org  *.example.org;
    ...
}

こうやって書かなくても

server {
    listen       80;
    server_name  .example.org;
    ...
}

dotを先頭に書くことで有効になるとのこと。知らなかった。

  • SSL

SSLの設定

server {
    listen               443;
    server_name          www.example.com;
    ssl                  on;
    ssl_certificate      www.example.com.crt;
    ssl_certificate_key  www.example.com.key;
    ssl_protocols        SSLv3 TLSv1;
    ssl_ciphers          HIGH:!ADH:!MD5;
    ...

もしくは

server {
    listen               443 ssl;
    server_name          www.example.com;
    ssl_certificate      www.example.com.crt;
    ssl_certificate_key  www.example.com.key;
    ssl_protocols        SSLv3 TLSv1;
    ssl_ciphers          HIGH:!ADH:!MD5;
    ...

でもOK。

server {
    listen               443;
    server_name          www.example.com;
    ssl                  on;
    ssl_certificate      www.example.com.cert;
    ssl_certificate_key  www.example.com.cert;
    ssl_protocols        SSLv3 TLSv1;
    ssl_ciphers          HIGH:!ADH:!MD5;
    ...

秘密鍵と証明書が同じファイルの場合は、上記のように設定する。 また、 80番ポート でSSLを受け付けることもできる。

server {
    listen               80;
    server_name          www.example.com;
    ssl                  on;
    ssl_certificate      www.example.com.cert;
    ssl_certificate_key  www.example.com.cert;
    ssl_protocols        SSLv3 TLSv1;
    ssl_ciphers          HIGH:!ADH:!MD5;
    ...

SSLのセッションキャッシュを有効化してCPUの負荷を減らす

セッション発生する事にSSLのセッションつくらないで ワーカー間でセッションを使いまわしてCPUの負荷を減らしたり keeepalive を有効にしたりしてなるべく効率的にセッションを回す。

  • ssl_session_timeout はデフォルトは 5m 。
server {
    listen              443 ssl;
    keepalive_timeout   70;

    ssl_protocols       sslv3 tlsv1 tlsv1.1 tlsv1.2;
    ssl_ciphers         aes128-sha:aes256-sha:rc4-sha:des-cbc3-sha:rc4-md5;
    ssl_certificate     /usr/local/nginx/conf/cert.pem;
    ssl_certificate_key /usr/local/nginx/conf/cert.key;
    ssl_session_cache   shared:ssl:10m;
    ssl_session_timeout 10m;

    ...
}

同じIPアドレスで複数のSSLを待ち受けてもデフォルトサーバにしかアクセスが行われない。

server {
    listen           443;
    server_name      www.example.com;
    ssl              on;
    ssl_certificate  www.example.com.crt;
    ...
}

server {
    listen           443;
    server_name      www.example.org;
    ssl              on;
    ssl_certificate  www.example.org.crt;
    ...
}

上記の設定の場合、最初の www.example.com が認識されてしまう。 この場合の対策は

server {
    listen           192.168.2.1:443;
    server_name      www.example.com;
    ssl              on;
    ssl_certificate  www.example.com.crt;
    ...
}

server {
    listen           192.168.2.2:443;
    server_name      www.example.org;
    ssl              on;
    ssl_certificate  www.example.org.crt;
    ...
}

こんな感じでIPアドレスを分けて割り振るのが確実で良いらしい。

@koudaiii

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment