From 5a19db5dc2db7c02d0f99630a07a64cacb7f7b44 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Fri, 13 Mar 2020 11:06:08 -0600 Subject: v2: Implement 'pki' app powered by Smallstep for localhost certificates (#3125) * pki: Initial commit of PKI app (WIP) (see #2502 and #3021) * pki: Ability to use root/intermediates, and sign with root * pki: Fix benign misnamings left over from copy+paste * pki: Only install root if not already trusted * Make HTTPS port the default; all names use auto-HTTPS; bug fixes * Fix build - what happened to our CI tests?? * Fix go.mod --- modules/caddypki/maintain.go | 99 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 modules/caddypki/maintain.go (limited to 'modules/caddypki/maintain.go') diff --git a/modules/caddypki/maintain.go b/modules/caddypki/maintain.go new file mode 100644 index 0000000..2fce0d9 --- /dev/null +++ b/modules/caddypki/maintain.go @@ -0,0 +1,99 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package caddypki + +import ( + "crypto/x509" + "fmt" + "time" + + "go.uber.org/zap" +) + +func (p *PKI) maintenance() { + ticker := time.NewTicker(10 * time.Minute) // TODO: make configurable + defer ticker.Stop() + + for { + select { + case <-ticker.C: + p.renewCerts() + case <-p.ctx.Done(): + return + } + } +} + +func (p *PKI) renewCerts() { + for _, ca := range p.CAs { + err := p.renewCertsForCA(ca) + if err != nil { + p.log.Error("renewing intermediate certificates", + zap.Error(err), + zap.String("ca", ca.id)) + } + } +} + +func (p *PKI) renewCertsForCA(ca *CA) error { + ca.mu.Lock() + defer ca.mu.Unlock() + + log := p.log.With(zap.String("ca", ca.id)) + + // only maintain the root if it's not manually provided in the config + if ca.Root == nil { + if needsRenewal(ca.root) { + // TODO: implement root renewal (use same key) + log.Warn("root certificate expiring soon (FIXME: ROOT RENEWAL NOT YET IMPLEMENTED)", + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)), + ) + } + } + + // only maintain the intermediate if it's not manually provided in the config + if ca.Intermediate == nil { + if needsRenewal(ca.inter) { + log.Info("intermediate expires soon; renewing", + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)), + ) + + rootCert, rootKey, err := ca.loadOrGenRoot() + if err != nil { + return fmt.Errorf("loading root key: %v", err) + } + interCert, interKey, err := ca.genIntermediate(rootCert, rootKey) + if err != nil { + return fmt.Errorf("generating new certificate: %v", err) + } + ca.inter, ca.interKey = interCert, interKey + + log.Info("renewed intermediate", + zap.Time("new_expiration", ca.inter.NotAfter), + ) + } + } + + return nil +} + +func needsRenewal(cert *x509.Certificate) bool { + lifetime := cert.NotAfter.Sub(cert.NotBefore) + renewalWindow := time.Duration(float64(lifetime) * renewalWindowRatio) + renewalWindowStart := cert.NotAfter.Add(-renewalWindow) + return time.Now().After(renewalWindowStart) +} + +const renewalWindowRatio = 0.2 // TODO: make configurable -- cgit v1.2.3