「Nginx」カテゴリーアーカイブ

じゃあ ssl_ciphers (SSLCipherSuite) は何を指定したらいいの!? って話

今やサーバ証明書なんてLet’s Encryptあたりを使用すればフリーだしHTTP/2だとかApp Transport Securityだとかでなにかと必要になってくるSSL/TLS関連の設定。

よく見る注意事項としてSSLv2, SSLv3の無効化、弱いアルゴリズム(RC4など)を使用しないなどがありますが、じゃあ結局Cipher Suiteは何を指定したらいいんだよ!って思うんです。

Nginxだと ssl_ciphers、Apacheだと SSLCipherSuite の項目です。

"HIGH" なんて指定しても「どこがHIGHなの?」という雑な設定になるし、そこから脆弱なものを除外していくのも大変すぎます。

試しにどのCipher Suiteが設定されるかの確認。

$ openssl ciphers -v 'HIGH'

そこで2017年1月現在でのブラウザやその手のクライアントで大体サポートされてるECDH(楕円曲線ディフィー・ヘルマン鍵共有)をベースに指定して簡潔に書こうということです。
ここではECDHにはEphemeral ECDH(動的生成する一時的なECDH)という "EECDH" (※1)を指定します。

※1. openssl 1.0.2以降だと "EECDH" "ECDHE" どちらでも指定できますがCentOS 7でもopenssl 1.0.1eが使われていたので下位互換ということで "EECDH" です。

$ openssl ciphers -v 'EECDH'
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-RC4-SHA              SSLv3    Kx=ECDH  Au=RSA    Enc=RC4(128)     Mac=SHA1
ECDHE-ECDSA-RC4-SHA            SSLv3    Kx=ECDH  Au=ECDSA  Enc=RC4(128)     Mac=SHA1
ECDHE-RSA-DES-CBC3-SHA         SSLv3    Kx=ECDH  Au=RSA    Enc=3DES(168)    Mac=SHA1
ECDHE-ECDSA-DES-CBC3-SHA       SSLv3    Kx=ECDH  Au=ECDSA  Enc=3DES(168)    Mac=SHA1
ECDHE-RSA-NULL-SHA             SSLv3    Kx=ECDH  Au=RSA    Enc=None         Mac=SHA1
ECDHE-ECDSA-NULL-SHA           SSLv3    Kx=ECDH  Au=ECDSA  Enc=None         Mac=SHA1

この一覧で特に使用したいのがEnc=AESGCM、逆に使用するべきでないEnc=RC4, None。これらをまとめて指定するために以下のようにします。

$ openssl ciphers -v 'EECDH+AESGCM:EECDH+AES256'
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2  Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3    Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3    Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA1

すっきりですね。優先順位(※2)もTLSv1.2 Enc=AESGCM(256)が上に来るので強度も申し分ないです。

'EECDH:!RC4:!NULL' ではなく 'EECDH+AESGCM:EECDH+AES256' としたのはAESGCMを先にもってこないと優先順位が変わって一部のブラウザで “接続エラー INADEQUATE_SECURITY” (※3)がでるためです。
また、下位互換のためのTLSv1 ECDHE-RSA(ECDSA)-AES256-SHAがあるので相当ダメなクライアント以外は問題がでません。

ついでに言うなら鍵生成時のRSA・ECDSAどちらでも対応できます。

※2. ssl_prefer_server_ciphers (SSLHonorCipherOrder) on でCipher Suiteの優先順位を指定している場合です。
※3. HTTP/2の場合はTLSv1.2を使用する必要がある (HTTP/2 over TLS/1.2)。

と、ここで終わってもいいのですが今のところAES128+SHA256で十分な強度ですし、パフォーマンスを考慮するならAES128を上に持ってくる方がほんの少し(数%~10%)だけ速いので以下のようにすれば良いと思います。
github.com を見てみるとAES128の優先順位を高くしてますしね。

$ openssl ciphers -v 'EECDH+AESGCM+AES128:EECDH+AESGCM:EECDH+AES128:EECDH+AES256'
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH  Au=RSA    Enc=AESGCM(128)  Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH  Au=ECDSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH  Au=RSA    Enc=AESGCM(256)  Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH  Au=ECDSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2 Kx=ECDH  Au=RSA    Enc=AES(128)     Mac=SHA256
ECDHE-ECDSA-AES128-SHA256      TLSv1.2 Kx=ECDH  Au=ECDSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES128-SHA           SSLv3 Kx=ECDH    Au=RSA    Enc=AES(128)     Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3 Kx=ECDH    Au=ECDSA  Enc=AES(128)     Mac=SHA1
ECDHE-RSA-AES256-SHA384        TLSv1.2 Kx=ECDH  Au=RSA    Enc=AES(256)     Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2 Kx=ECDH  Au=ECDSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3 Kx=ECDH    Au=RSA    Enc=AES(256)     Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3 Kx=ECDH    Au=ECDSA  Enc=AES(256)     Mac=SHA1

最終的にあまり短くなったとは言えませんがNginxの設定は以下のようにしています。

server {
    listen 443 ssl http2;
    ssl                       on;
    ssl_prefer_server_ciphers on;
    ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers               EECDH+AESGCM+AES128:EECDH+AESGCM:EECDH+AES128:EECDH+AES256;
    ...
}

NginxのPHP(FastCGI)をphp-fpmではなくspawn-fcgiで動かす

php-spawn-fcgi

NginxでPHPだけを動かすならphp-fpmを使えば設定も楽なんだけどMuninやらPHP以外のスクリプトをFastCGIで動かしている場合、もうspawn-fcgiで全部やっちゃえばいいのではというわけです。

$ spawn-fcgi -h
Usage: spawn-fcgi [options] [-- <fgiapp> [fcgi app arguments]]

options:
-f <path>      /usr/bin/php-cgi でいいけど非推奨なので -- <fcgiapp> を使う。
-a <address>   127.0.0.1 (Unix domain socketを使わない場合)
-p <port>      9000 (Unix domain socketを使わない場合)
-s <path>      /var/run/spawn-fcgi/php-fcgi.sock (-a, -p を使わない場合)
-C <children>  (PHP only) PHP_FCGI_CHILDRENの値が入るけどデフォルトが0なので数値指定
-F <children>  -C 使ってたらいいか (デフォルト 1)
-u <user>      nginx
-g <group>     nginx (nginx:nginx で実行してる)
-U <user>      Unix domain socketのユーザ
-G <group>     Unix domain socketのグループ

CentOS 6なら /etc/init.d/php-spawn-fcgi みたいな分かりやすい起動スクリプトを作って
start)
daemon /usr/bin/spawn-fcgi -s /var/run/spawn-fcgi/php-fcgi.sock -U nginx -u nginx -g nginx -P /var/run/spawn-fcgi/php-fcgi.pid -C 5 -- /usr/bin/php-cgi

あとは自動起動など
chkconfig php-spawn-fcgi on
/etc/init.d/php-spawn-fcgi start

Nginx側のfastcgi_passは↑で指定したやつにする
fastcgi_pass unix:/var/run/spawn-fcgi/php-fcgi.sock
これで、PHPに関してはServer API : CGI/FastCGIとして動く。

と、ここまで書いたけどphpfpm_status のようなこともできないし、宗教上の理由でもないかぎりphp-fpmを使った方がよいのではという感想。

/etc/init.d/php-spawn-fcgi の中身

#!/bin/sh
#
# php-spawn-fcgi
#
# chkconfig: 2345 86 16
# description: php spawn-fcgi service process
# pidfile: /var/run/spawn-fcgi/php-fcgi.pid

# Source function library.
. /etc/rc.d/init.d/functions

RETVAL=0

spawn_fcgi=/usr/bin/spawn-fcgi
php_cgi=/usr/bin/php-cgi
prog=`basename ${php_cgi}`
pidfile=/var/run/spawn-fcgi/php-fcgi.pid
lockfile=/var/lock/subsys/php-fcgi
sockfile=/var/run/spawn-fcgi/php-fcgi.sock

user=nginx
group=nginx

# PHP_FCGI_CHILDREN
fcgi_children=5

# See how we were called.
case "$1" in
    start)
        echo $"Starting $prog: "
        dir=$(dirname ${pidfile})
        [ -d $dir ] || mkdir $dir
        chown ${user}:${group} $dir
        # spawn-fcgi
        daemon ${spawn_fcgi} -s ${sockfile} -U nginx -u ${user} -g ${group} -P ${pidfile} -C ${fcgi_children} -- ${php_cgi} spawn-fcgi
        RETVAL=$?
        if [ $RETVAL = 0 ]; then
            echo_success
            touch ${lockfile}
        else
            echo_failure
        fi
        echo
        ;;
    stop)
        echo -n $"Stopping $prog: "
        killproc -p ${pidfile} ${prog}
        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
        ;;
    status)
        status -p ${pidfile} ${prog}
        RETVAL=$?
        ;;
    restart|reload)
        $0 stop
        $0 start
        RETVAL=$?
        ;;
    *)
        echo $"Usage: $prog {start|stop|status|restart|reload}"
        RETVAL=2

esac
exit $RETVAL