1. Hexa

    Posted

    Hexa
Changes in title
+OpenResty で証明書の動的読み込み
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,253 @@
+ssl_certificate_by_lua* ディレクティブを使用することで、証明書を動的に読み込ませることが可能です。
+
+
+## 例
+
+クライアントから送られてきた ClientHello の SNI 拡張に含まれているサーバ名に対応する証明書を読み込んでいます。
+対応する証明書が見つからない場合は ServerHello を返さずに、Internal Error (80) のアラートが返ります。
+
+```
+http {
+ lua_shared_dict certs 10m;
+
+ server {
+ listen 10443 ssl;
+
+ ssl_protocols TLSv1.2;
+ ssl_certificate certs/www.example.com.pem;
+ ssl_certificate_key certs/www.example.com.key;
+ ssl_session_tickets on;
+ ssl_session_ticket_key certs/ticket.key;
+ ssl_session_cache shared:SSL:1m;
+ ssl_session_timeout 5m;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+ ssl_prefer_server_ciphers on;
+
+ ssl_certificate_by_lua_block {
+ local ssl = require "ngx.ssl"
+
+ local ok, err = ssl.clear_certs()
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local name, err = ssl.server_name()
+ if not name then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+
+ local ssl_certificate = string.format("conf/certs/%s.pem", name)
+ local ssl_certificate_key = string.format("conf/certs/%s.key", name)
+
+ local file = io.open(ssl_certificate, "r")
+ if not file then
+ return ngx.exit(ngx.ERROR)
+ end
+ local pem_cert = file:read("*all")
+ io.close(file)
+
+ local cert, err = ssl.cert_pem_to_der(pem_cert)
+ if not cert then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local ok, err = ssl.set_der_cert(cert)
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local certs = ngx.shared.certs
+ certs:set(name, pem_cert)
+
+
+ local file = io.open(ssl_certificate_key, "r")
+ if not file then
+ return ngx.exit(ngx.ERROR)
+ end
+ local pem_priv_key = file:read("*all")
+ io.close(file)
+
+ local priv_key, err = ssl.priv_key_pem_to_der(pem_priv_key)
+ if not priv_key then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local ok, err = ssl.set_der_priv_key(priv_key)
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+ }
+
+ location / {
+ add_header Content-Type text/html;
+ content_by_lua_block {
+ local headers = ngx.req.get_headers()
+ local host = headers["Host"]
+ local m, err = ngx.re.match(host, "(?<hostname>[^:]+)")
+ if err then
+ ngx.log(ngx.ERR, err)
+ return
+ end
+ if m then
+ local certs = ngx.shared.certs
+ local cert = certs:get(m[0])
+ ngx.print(cert)
+ ngx.exit(ngx.HTTP_OK)
+ end
+ }
+ }
+ }
+}
+```
+
+
+### 確認
+
+curl で確認してみます。
+
+```bash
+$ curl -v -s -cacert ca.pem -X GET https://www1.example.com:10443/ 1>/dev/null
+* Rebuilt URL to: ca.pem/
+* Could not resolve host: ca.pem
+* Closing connection 0
+* Trying 127.0.0.1...
+* Connected to www1.example.com (127.0.0.1) port 10443 (#1)
+* TLS 1.2 connection using TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+* Server certificate: www1.example.com
+* Server certificate: CA
+> GET / HTTP/1.1
+> Host: www1.example.com:10443
+> User-Agent: curl/7.43.0
+> Accept: */*
+>
+< HTTP/1.1 200 OK
+< Server: openresty/1.11.2.1
+< Date: Sat, 03 Dec 2016 13:52:05 GMT
+< Content-Type: text/plain
+< Transfer-Encoding: chunked
+< Connection: keep-alive
+< Content-Type: text/html
+<
+{ [2342 bytes data]
+* Connection #1 to host www1.example.com left intact
+```
+
+```bash
+$ curl -v -s -cacert ca.pem -X GET https://www2.example.com:10443/ 1>/dev/null
+* Rebuilt URL to: ca.pem/
+* Could not resolve host: ca.pem
+* Closing connection 0
+* Trying 127.0.0.1...
+* Connected to www2.example.com (127.0.0.1) port 10443 (#1)
+* TLS 1.2 connection using TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
+* Server certificate: www2.example.com
+* Server certificate: CA
+> GET / HTTP/1.1
+> Host: www2.example.com:10443
+> User-Agent: curl/7.43.0
+> Accept: */*
+>
+< HTTP/1.1 200 OK
+< Server: openresty/1.11.2.1
+< Date: Sat, 03 Dec 2016 13:53:24 GMT
+< Content-Type: text/plain
+< Transfer-Encoding: chunked
+< Connection: keep-alive
+< Content-Type: text/html
+<
+{ [2345 bytes data]
+* Connection #1 to host www2.example.com left intact
+```
+
+
+
+## Redis から読み込む
+
+あまりファイルで管理したくないので、Redis に入れておいた証明書と秘密鍵を使用します。
+
+```
+ ssl_certificate_by_lua_block {
+ local ssl = require "ngx.ssl"
+ local ok, err = ssl.clear_certs()
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local name, err = ssl.server_name()
+ if not name then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+
+ local redis = require "resty.redis"
+ local red = redis:new()
+
+ red:set_timeout(1000)
+ local ok, err = red:connect("127.0.0.1", 6379)
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local pem_cert, err = red:hget(name, "cert")
+ if not pem_cert then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ if pem_cert == ngx.null then
+ return ngx.exit(ngx.ERROR)
+ end
+
+
+ local pem_priv_key, err = red:hget(name, "priv_key")
+ if not pem_priv_key then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ if pem_priv_key == ngx.null then
+ return ngx.exit(ngx.ERROR)
+ end
+
+
+ local cert, err = ssl.cert_pem_to_der(pem_cert)
+ if not cert then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local ok, err = ssl.set_der_cert(cert)
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local certs = ngx.shared.certs
+ certs:set(name, pem_cert)
+
+
+ local priv_key, err = ssl.priv_key_pem_to_der(pem_priv_key)
+ if not priv_key then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+
+ local ok, err = ssl.set_der_priv_key(priv_key)
+ if not ok then
+ ngx.log(ngx.ERR, err)
+ return ngx.exit(ngx.ERROR)
+ end
+ }
+```
+
+更新や削除を考えると、管理用の HTTP API を用意して Redis で管理した方が楽かもしれません。