tech memo

調べたこと、試したことの覚書です

nginx 1.15.9 の ssl_certificate, ssl_certificate_key の挙動確認

nginx ngx_http_ssl_module 関連記事

 

この記事の概要

nginx 1.15.9 で ssl_certificate, ssl_certificate_key に変数が使えるようになりました。

この変更により証明書・秘密鍵の動的な読み込みが可能になったのでは?と話題になっていました。

この件について、ちょうど挙動を確認したので、書き留めておきます。
深追いはできてないので、参考までに。

 

nginx.orgのドキュメントに記述が追記されていました。

nginx.org

そのなかで下記の忠告が書かれていました。

Note that using variables implies that a certificate will be loaded for each SSL handshake, and this may have a negative impact on performance.
It should be kept in mind that due to the HTTPS protocol limitations for maximum interoperability virtual servers should listen on different IP addresses.

変数を使用すると、SSLハンドシェイク毎に 都度証明書(と鍵)がロードされ、パフォーマンスにはネガティブな影響を与えるようです。

パフォーマンスについては未だ計測できてないのですが、この都度ロードされるという挙動について確認してみました。

 

検証の準備

■serverディレクティブの記述

server {
listen 443 ssl;
server_name _ default;

access_log /var/log/nginx/default_access.log ltsv;
error_log /var/log/nginx/default_error.log notice;

set $ssl_server_name $http_host;

ssl_certificate /etc/nginx/cert/$ssl_server_name.crt;
ssl_certificate_key /etc/nginx/cert/$ssl_server_name.key;

~略~
}

・[set $(任意の変数名) $(http_hostなどの識別名としてつかう組込変数)]を定義
・定義した任意の変数名を ssl_certificate や ssl_certificate_key にパスの一部として渡します

※ssl_certificate や ssl_certificate_keyに直接$(http_hostなどの識別名としてつかう組込変数)を書くのはNGでした。(file open対象がnil値になります)

 

■Let's Encryptで証明書&鍵を準備(2セット)

# ls -l /etc/nginx/cert/
total 20
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key
-rw-r--r-- 1 root root 1907 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.key

片系を退避 (動作確認を分かりやすくするため)

# mv yyy.* old/

# ls -l /etc/nginx/cert/
total 12
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key

 

検証の内容と結果

workerを1つにしそのworkerに対して straceで挙動を確認しつつ、ログを確認しつつ、接続確認を行いました。

この間、 nginx の reload, restart は行わず、動的更新に対しての動作を確認しています。

 

① https://xxx.1773.work/ への初回アクセス

挙動:https://xxx.1773.work/が表示され 適切な証明書が表示される

■straceの抜粋

# strace -tt -s 1024 -p 21462
strace: Process 22157 attached
20:04:04.693471 epoll_wait(9,

[{EPOLLIN, {u32=2378772744, u64=139893758570760}}], 512, -1) = 1
20:04:08.085823 accept4(7, {sa_family=AF_INET, sin_port=htons(60941), sin_addr=inet_addr("<接続元アドレス>")}, [16], SOCK_NONBLOCK) = 3
20:04:08.085989 epoll_ctl(9, EPOLL_CTL_ADD, 3, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2378773240, u64=139893758571256}}) = 0
20:04:08.086055 epoll_wait(9, [{EPOLLIN, {u32=2378772744, u64=139893758570760}}, {EPOLLIN, {u32=2378773240, u64=139893758571256}}], 512, 60000) = 2
20:04:08.086118 accept4(7, {sa_family=AF_INET, sin_port=htons(60942), sin_addr=inet_addr("<接続元アドレス>")}, [16], SOCK_NONBLOCK) = 12
20:04:08.086197 epoll_ctl(9, EPOLL_CTL_ADD, 12, {EPOLLIN|EPOLLRDHUP|EPOLLET, {u32=2378773488, u64=139893758571504}}) = 0
20:04:08.086266 recvfrom(3, "\26", 1, MSG_PEEK, NULL, NULL) = 1
20:04:08.086334 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
20:04:08.086609 brk(NULL) = 0x5621533bf000
20:04:08.086673 brk(0x5621533e4000) = 0x5621533e4000
20:04:08.086754 read(3, "\26\3\1\2\0\1\0\1\374\3\3", 11) = 11
20:04:08.086869 read(3, "\366~~~略~~~", 506) = 506

<!ここでOPEN!>
20:04:08.087083 open("/etc/nginx/cert/xxx.1773.work.crt", O_RDONLY) = 13
20:04:08.087182 fstat(13, {st_mode=S_IFREG|0666, st_size=1907, ...}) = 0
20:04:08.087263 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.087334 read(13, "-----BEGIN CERTIFICATE-----\n<証明書の内容>"..., 4096) = 1907
20:04:08.087553 read(13, "", 4096) = 0
20:04:08.087622 close(13) = 0
20:04:08.087783 munmap(0x7f3b8dce4000, 4096) = 0

<!ここでOPEN!>
20:04:08.087887 open("/etc/nginx/cert/xxx.1773.work.key", O_RDONLY) = 13
20:04:08.088032 fstat(13, {st_mode=S_IFREG|0666, st_size=1704, ...}) = 0
20:04:08.088189 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.088298 read(13, "-----BEGIN PRIVATE KEY-----\n<秘密鍵の内容>"..., 4096) = 1704
20:04:08.088525 close(13) = 0
20:04:08.088587 munmap(0x7f3b8dce4000, 4096) = 0
20:04:08.090924 write(3, "~~~略~~~","..., 1809) = 1809
20:04:08.091136 read(3, 0x5621533bbc83, 33093) = -1 EAGAIN (Resource temporarily unavailable)
20:04:08.091276 epoll_wait(9, [{EPOLLIN, {u32=2378773488, u64=139893758571504}}], 512, 59999) = 1
20:04:08.091407 recvfrom(12, "\26", 1, MSG_PEEK, NULL, NULL) = 1
20:04:08.091525 setsockopt(12, SOL_TCP, TCP_NODELAY, [1], 4) = 0
20:04:08.091667 read(12, "\26\3\1\2\0\1\0\1\374\3\3", 11) = 11
20:04:08.091799 read(12, "~~~略~~~", 506) = 506

<!ここでOPEN!>
20:04:08.091990 open("/etc/nginx/cert/xxx.1773.work.crt", O_RDONLY) = 13
20:04:08.092114 fstat(13, {st_mode=S_IFREG|0666, st_size=1907, ...}) = 0
20:04:08.092231 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3b8dce4000
20:04:08.092348 read(13, "-----BEGIN CERTIFICATE-----\n<証明書の内容>"..., 4096) = 1907
20:04:08.092557 read(13, "", 4096) = 0
20:04:08.092676 close(13) = 0
20:04:08.092787 munmap(0x7f3b8dce4000, 4096) = 0

<!ここでOPEN!>
20:04:08.092913 open("/etc/nginx/cert/xxx.1773.work.key", O_RDONLY) = 13
20:04:08.093055 fstat(13, {st_mode=S_IFREG|0666, st_size=1704, ...}) = 0
20:04:08.093288 read(13, "-----BEGIN PRIVATE KEY-----\n<秘密鍵の内容>"..., 4096) = 1704
20:04:08.093582 munmap(0x7f3b8dce4000, 4096) = 0
20:04:08.095721 write(12, "~~~略~~~"..., 1809) = 1809
20:04:08.096013 read(12, 0x5621533bbc83, 33093) = -1 EAGAIN (Resource temporarily unavailable)
20:04:08.096170 epoll_wait(9, [{EPOLLIN, {u32=2378773240, u64=139893758571256}}], 512, 59994) = 1
20:04:08.117737 read(3, "~~~略~~~", 33093) = 126
20:04:08.118427 write(3, "~~~略~~~", 258) = 258

~~~略~~~

20:04:08.130079 write(5, "time:2019-03-04T20:04:08+09:00\thost:xxx.1773.work\thttp_host:xxx.1773.work\tscheme:https\tremote_addr:<接続元アドレス>\tremote_user:-\ttime_local:04/Mar/2019:20:04:08 +0900\trequest:GET / HTTP/1.1\tstatus:200\tbody_bytes_sent:0\thttp_referer:-\thttp_user_agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36\thttp_x_forwarded_for:-\trequest_time:0.000\tupstream_response_time:\tmsec:1551697448.128\tupstream_http_content_type:-\tupstream_status:-\tupstream_cache_status:-\n", 524) = 524

~~~略~~~

 

② https://xxx.1773.work/ への2度目のアクセス(再ハンドシェイク)

挙動:https://xxx.1773.work/が表示され 適切な証明書が表示される

straceのログを見ていると①とフローが一致。

ドキュメント記述の通り、SSLハンドシェイク毎に 都度証明書(と鍵)がロードされる という動作を確認しました。

 

③ https://yyy.1773.work/ へのアクセス (証明書未配置のパス)

 挙動:SSLハンドシェイク失敗

■straceの抜粋

~~~略~~~

20:12:51.337382 open("/etc/nginx/cert/yyy.1773.work.crt", O_RDONLY) = -1 ENOENT (No such file or directory)
20:12:51.337677 gettid() = 22157
20:12:51.337840 write(4, "2019/03/04 20:12:51 [error] 22157#22157: *7 cannot load certificate \"/etc/nginx/cert/yyy.1773.work.crt\": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443\n", 367) = 367
20:12:51.338061 write(13, "\25\3\3\0\2\2P", 7) = 7
20:12:51.338266 close(13) = 0

~~~略~~~
■error_logへの出力

2019/03/04 20:12:51 [error] 22157#22157: *7 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443
2019/03/04 20:12:51 [error] 22157#22157: *6 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443
2019/03/04 20:12:51 [error] 22157#22157: *8 cannot load certificate "/etc/nginx/cert/yyy.1773.work.crt": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/etc/nginx/cert/yyy.1773.work.crt','r') error:2006D080:BIO routines:BIO_new_file:no such file) while SSL handshaking, client: <接続元アドレス>, server: 0.0.0.0:443

誘導先の証明書(鍵)パスが存在していないとエラーに。 

 

④ https://yyy.1773.work/ へのアクセス (証明書の動的追加)

証明書追加を行います。

# mv old/yyy* ./

# ls -l /etc/nginx/cert/
total 20
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key
-rw-r--r-- 1 root root 1907 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:51 /etc/nginx/cert/yyy.1773.work.key

 挙動:https://yyy.1773.work/ が表示され 適切な証明書が表示される

straceのログを見ていると①②とフローが一致。

reloadを伴わない動的追加が可能そうです。

 

⑤ https://yyy.1773.work/ へのアクセス (証明書の動的削除)

今度は、証明書を削除します。

# mv yyy.* old/

# ls -l /etc/nginx/cert/
total 12
drwxr-xr-x 2 root root 4096 Mar 4 12:18 old
-rw-r--r-- 1 root root 1907 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.crt
-rw-r----- 1 root root 1704 Mar 4 12:58 /etc/nginx/cert/xxx.1773.work.key

挙動:

(1) keepaliveが切れるまではhttps://yyy.1773.work/ が表示され 適切な証明書が表示される

(2) 再SSLハンドシェイク時より ハンドシェイク失敗する

 

今回は、これらのざっくりとした挙動を確認してみました。