-- using configuration for specific "kind" of HTTP server
local function add_socket(fd, kind, addr_str)
assert(M.servers[fd] == nil, 'socket is already served by an HTTP instance')
- local conf, certs, key
- conf = mergeconf(M.configs._builtin._all, M.configs._builtin[kind], M.configs._all, M.configs[kind])
+ local conf = mergeconf(M.configs._builtin._all, M.configs._builtin[kind],
+ M.configs._all, M.configs[kind])
conf.socket = cqueues.socket.fdopen(fd)
- if conf.tls ~= false then
- -- Check if a cert file was specified
- -- Read or create self-signed x509 certificate
+ if conf.tls ~= false then -- Create a TLS context, either from files or new.
if conf.ephemeral then
- certs, key = tls_cert.new_ephemeral_files(conf.cert, conf.key)
+ if not M.ephem_state then
+ M.ephem_state = { servers = M.servers }
+ tls_cert.ephemeral_state_maintain(M.ephem_state, conf.cert, conf.key)
+ end
+ conf.ctx = M.ephem_state.ctx
else
- certs, key = tls_cert.load(conf.cert, conf.key)
- end
- -- Check loaded certificate
- if not certs or not key then
- panic('failed to load certificate "%s"', conf.cert)
+ local certs, key = tls_cert.load(conf.cert, conf.key)
+ conf.ctx = tls_cert.new_tls_context(certs, key)
end
+ assert(conf.ctx)
end
-- Compose server handler
local routes = route(conf.endpoints)
- conf.ctx = certs and tls_cert.new_tls_context(certs, key)
conf.onstream = routes
-- Create TLS context and start listening
local s, err = http_server.new(conf)
panic('failed to listen on %s: %s', addr_str, err)
end
M.servers[fd] = {kind = kind, server = s, config = conf}
- -- Create certificate renewal timer if ephemeral; FIXME: renew more than once, not per-socket? etc.
- if certs and conf.ephemeral then
- local _, expiry = certs[1]:getLifetime()
- expiry = 1000 * math.max(0, expiry - (os.time() - 3 * 24 * 3600))
- event.after(expiry, function ()
- log('[http] refreshed ephemeral certificate')
- certs, key = tls_cert.new_ephemeral_files(conf.cert, conf.key)
- -- TODO servers sharing cert?!
- s.ctx = tls_cert.new_tls_context(certs, key)
- end)
- end
end
-- @function Stop listening on given socket
instance.server:close()
M.servers[fd] = nil
- -- TODO stop refresh timer
end
-- @function Listen for config changes from net.listen()/net.close()
remove_socket(fd)
end
prometheus.deinit()
+ tls_cert.ephemeral_state_destroy(M.ephem_state)
net.register_endpoint_kind('doh')
net.register_endpoint_kind('webmgmt')
end
return { crt }, key
end
--- @function Create new self-signed certificate, write to files; return certs, key
--- Well, we actually never read from these files anyway (in case of ephemeral).
-function tls_cert.new_ephemeral_files(certfile, keyfile)
- local certs, key = new_ephemeral()
+-- @function Write certs and key to files
+local function write_cert_files(certs, key, certfile, keyfile)
-- Write certs
local f = assert(io.open(certfile, 'w'), string.format('cannot open "%s" for writing', certfile))
for _, cert in ipairs(certs) do
local pub, priv = key:toPEM('public', 'private')
assert(f:write(pub .. priv))
f:close()
- return certs, key
+end
+
+-- @function Start maintenance of a self-signed TLS context (at ephem_state.ctx).
+-- Keep updating the ephem_state.servers table. Stop updating by calling _destroy().
+-- TODO: each process maintains its own ephemeral cert ATM, and the files aren't ever read from.
+function tls_cert.ephemeral_state_maintain(ephem_state, certfile, keyfile)
+ local certs, key = new_ephemeral()
+ write_cert_files(certs, key, certfile, keyfile)
+ ephem_state.ctx = tls_cert.new_tls_context(certs, key)
+ -- Each server needs to have its ctx updated.
+ for _, s in pairs(ephem_state.servers) do
+ s.server.ctx = ephem_state.ctx
+ s.config.ctx = ephem_state.ctx -- not required, but let's keep it synchonized
+ end
+ log('[http] created new ephemeral TLS certificate')
+ local _, lifetime_sec = certs[1]:getLifetime()
+ local wait_msec = 1000 * math.max(1, lifetime_sec - (os.time() - 3 * 24 * 3600))
+ if not ephem_state.timer_id then
+ ephem_state.timer_id = event.after(wait_msec, function ()
+ tls_cert.ephemeral_state_maintain(ephem_state, certfile, keyfile)
+ end)
+ else
+ event.reschedule(ephem_state.timer_id, wait_msec)
+ end
+end
+function tls_cert.ephemeral_state_destroy(ephem_state)
+ if ephem_state and ephem_state.timer_id then
+ event.cancel(ephem_state.timer_id)
+ end
end
-- @function Read a certificate chain and a key from files; return certs, key