diff --git a/build/test-lua.sh b/build/test-lua.sh index ce2cd60048..f9bc77b8d7 100755 --- a/build/test-lua.sh +++ b/build/test-lua.sh @@ -31,4 +31,5 @@ resty \ --shdict "balancer_ewma 1M" \ --shdict "balancer_ewma_last_touched_at 1M" \ --shdict "balancer_ewma_locks 512k" \ + --shdict "ocsp_cache 1M" \ ./rootfs/etc/nginx/lua/test/run.lua ${BUSTED_ARGS} ./rootfs/etc/nginx/lua/test/ diff --git a/internal/ingress/controller/template/configmap.go b/internal/ingress/controller/template/configmap.go index 862754a557..02111a6db3 100644 --- a/internal/ingress/controller/template/configmap.go +++ b/internal/ingress/controller/template/configmap.go @@ -71,6 +71,7 @@ var ( "balancer_ewma_last_touched_at": 10, "balancer_ewma_locks": 1, "certificate_servers": 5, + "ocsp_cache": 5, // 1MB ~100 responses } ) diff --git a/rootfs/etc/nginx/lua/certificate.lua b/rootfs/etc/nginx/lua/certificate.lua index eb7feabd36..a6f2e23c54 100644 --- a/rootfs/etc/nginx/lua/certificate.lua +++ b/rootfs/etc/nginx/lua/certificate.lua @@ -1,6 +1,10 @@ local ssl = require("ngx.ssl") local configuration = require("configuration") local re_sub = ngx.re.sub +local http = require "resty.http" +local ocsp = require "ngx.ocsp" + +local certificate_servers = ngx.shared.certificate_servers local _M = {} @@ -48,6 +52,57 @@ local function get_pem_cert_key(raw_hostname) return pem_cert_key end +local function get_ocsp_response(pem_cert_key) + local der_cert_chain, der_cert_err = ssl.cert_pem_to_der(pem_cert_key) + if not der_cert_chain then + return nil, "failed to convert certificate chain from PEM to DER: " .. der_cert_err + end + + local ocsp_url, ocsp_responder_err = ocsp.get_ocsp_responder_from_der_chain(der_cert_chain) + if not ocsp_url then + return nil, "failed to get OCSP URL: " .. (ocsp_responder_err or "") + end + + local ocsp_req, ocsp_request_err = ocsp.create_ocsp_request(der_cert_chain) + if not ocsp_req then + return nil, "failed to create OCSP request: " .. (ocsp_request_err or "") + end + + local httpc = http.new() + httpc:set_timeout(10000) + + local res, req_err = httpc:request_uri(ocsp_url, { + method = "POST", + body = ocsp_req, + headers = { + ["Content-Type"] = "application/ocsp-request", + } + }) + + -- Perform various checks to ensure we have a valid OCSP response. + if not res then + return nil, "OCSP responder query failed (" .. (ocsp_url or "") .. "): " .. (req_err or "") + end + + if res.status ~= 200 then + return nil, "OCSP responder returns bad HTTP status code (" .. (ocsp_url or "") .. "): " .. (res.status or "") + end + + httpc:set_keepalive() + + local ocsp_resp = res.body + if not ocsp_resp or ocsp_resp == "" then + return nil, "OCSP responder returns bad response body (" .. (ocsp_url or "") .. "): " .. (ocsp_resp or "") + end + + local ok, ocsp_validate_err = ocsp.validate_ocsp_response(ocsp_resp, der_cert_chain) + if not ok then + return nil, "failed to validate OCSP response (" .. (ocsp_url or "") .. "): " .. (ocsp_validate_err or "") + end + + return ocsp_resp, nil +end + function _M.configured_for_current_request() if ngx.ctx.configured_for_current_request ~= nil then return ngx.ctx.configured_for_current_request @@ -89,6 +144,41 @@ function _M.call() ngx.log(ngx.ERR, set_pem_cert_key_err) return ngx.exit(ngx.ERROR) end + + --- fake SSL certificate should not return OCSP responses + if hostname == DEFAULT_CERT_HOSTNAME then + return + end + + --- only server names with a valid certificate can return OCSP responses + local cert_exists = certificate_servers:get(hostname) + if not cert_exists then + return + end + + --- check if the OCSP response is in the cache + local ocsp_resp = ngx.shared.ocsp_cache:get(hostname) + if not ocsp_resp then + local ocsp_response_err + ocsp_resp, ocsp_response_err = get_ocsp_response(pem_cert_key) + if not ocsp_resp then + ngx.log(ngx.ERR, "failed to get OCSP response for hostname " .. hostname .. " : " .. ocsp_response_err) + return + end + + local _, set_ocsp_err, set_ocsp_forcible = ngx.shared.ocsp_cache:set(hostname, ocsp_resp, 3600) + if set_ocsp_err then + ngx.log(ngx.ERR, "failed to set cache of OCSP response for " .. hostname .. ": ", set_ocsp_err) + elseif set_ocsp_forcible then + ngx.log(ngx.ERR, "'lua_shared_dict ocsp_cache' might be too small - consider increasing the size") + end + end + + local ok, ocsp_status_err = ocsp.set_ocsp_status_resp(ocsp_resp) + if not ok then + ngx.log(ngx.ERR, "failed to set OCSP information: " .. ocsp_status_err) + end + end return _M