LoginSignup
13
4

More than 5 years have passed since last update.

OpenResty で証明書の動的読み込み

Posted at

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 で確認してみます。

$ 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
$ 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 で管理した方が楽かもしれません。

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4