227 lines
6.5 KiB
Go
227 lines
6.5 KiB
Go
/*
|
|
Copyright 2019 The Jetstack cert-manager contributors.
|
|
|
|
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 tls
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-logr/logr"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
crlog "sigs.k8s.io/controller-runtime/pkg/log"
|
|
|
|
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
|
"github.com/jetstack/cert-manager/pkg/util/pki"
|
|
"github.com/jetstack/cert-manager/pkg/webhook/authority"
|
|
)
|
|
|
|
// DynamicSource provides certificate data for a golang HTTP server by
|
|
// automatically generating certificates using an authority.SignFunc.
|
|
type DynamicSource struct {
|
|
// DNSNames that will be set on certificates this source produces.
|
|
DNSNames []string
|
|
|
|
// The authority used to sign certificate templates.
|
|
Authority *authority.DynamicAuthority
|
|
|
|
// Log is an optional logger to write informational and error messages to.
|
|
// If not specified, no messages will be logged.
|
|
Log logr.Logger
|
|
|
|
cachedCertificate *tls.Certificate
|
|
requiresRotation bool
|
|
nextRenew time.Time
|
|
lock sync.Mutex
|
|
}
|
|
|
|
var _ CertificateSource = &DynamicSource{}
|
|
|
|
func (f *DynamicSource) Run(stopCh <-chan struct{}) error {
|
|
if f.Log == nil {
|
|
f.Log = crlog.NullLogger{}
|
|
}
|
|
|
|
// Run the authority in a separate goroutine
|
|
authorityErrChan := make(chan error)
|
|
go func() {
|
|
defer close(authorityErrChan)
|
|
authorityErrChan <- f.Authority.Run(stopCh)
|
|
}()
|
|
|
|
// initially fetch a certificate from the signing CA
|
|
interval := time.Second
|
|
if err := wait.PollUntil(interval, func() (done bool, err error) {
|
|
// check for errors from the authority here too, to prevent retrying
|
|
// if the authority has failed to start
|
|
select {
|
|
case err, ok := <-authorityErrChan:
|
|
if err != nil {
|
|
return true, fmt.Errorf("failed to run certificate authority: %w", err)
|
|
}
|
|
if !ok {
|
|
return true, fmt.Errorf("certificate authority stopped")
|
|
}
|
|
default:
|
|
// this case avoids blocking if the authority is still running
|
|
}
|
|
|
|
if err := f.regenerateCertificate(); err != nil {
|
|
f.Log.Error(err, "Failed to generate initial serving certificate, retrying...", "interval", interval)
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}, stopCh); err != nil {
|
|
return err
|
|
}
|
|
|
|
// watch for changes to the root CA
|
|
rotationChan := f.Authority.WatchRotation(stopCh)
|
|
renewalChan := func() <-chan struct{} {
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
defer close(ch)
|
|
for {
|
|
// exit if stopCh closes
|
|
select {
|
|
case <-stopCh:
|
|
return
|
|
default:
|
|
}
|
|
// regenerate the certificate if we have gone past the 'nextRenew' time
|
|
if time.Now().After(f.nextRenew) {
|
|
ch <- struct{}{}
|
|
}
|
|
time.Sleep(time.Second * 5)
|
|
}
|
|
}()
|
|
return ch
|
|
}()
|
|
// check the current certificate every 10s in case it needs updating
|
|
return wait.PollImmediateUntil(time.Second*10, func() (done bool, err error) {
|
|
// regenerate the serving certificate if the root CA has been rotated
|
|
select {
|
|
// if the authority has stopped for whatever reason, exit and return the error
|
|
case err, ok := <-authorityErrChan:
|
|
if err != nil {
|
|
return true, fmt.Errorf("failed to run certificate authority: %w", err)
|
|
}
|
|
if !ok {
|
|
return true, fmt.Errorf("certificate authority stopped")
|
|
}
|
|
// trigger regeneration if the root CA has been rotated
|
|
case _, ok := <-rotationChan:
|
|
if !ok {
|
|
return true, fmt.Errorf("channel closed")
|
|
}
|
|
f.Log.Info("Detected root CA rotation - regenerating serving certificates")
|
|
if err := f.regenerateCertificate(); err != nil {
|
|
f.Log.Error(err, "Failed to regenerate serving certificate")
|
|
// Return an error here and stop the source running - this case should never
|
|
// occur, and if it does, indicates some form of internal error.
|
|
return false, err
|
|
}
|
|
// trigger regeneration if a renewal is required
|
|
case <-renewalChan:
|
|
f.Log.Info("Serving certificate requires renewal, regenerating")
|
|
if err := f.regenerateCertificate(); err != nil {
|
|
f.Log.Error(err, "Failed to regenerate serving certificate")
|
|
// Return an error here and stop the source running - this case should never
|
|
// occur, and if it does, indicates some form of internal error.
|
|
return false, err
|
|
}
|
|
}
|
|
return false, nil
|
|
}, stopCh)
|
|
}
|
|
|
|
func (f *DynamicSource) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
if f.cachedCertificate == nil {
|
|
return nil, ErrNotAvailable
|
|
}
|
|
return f.cachedCertificate, nil
|
|
}
|
|
|
|
func (f *DynamicSource) Healthy() bool {
|
|
return f.cachedCertificate != nil
|
|
}
|
|
|
|
// regenerateCertificate will trigger the cached certificate and private key to
|
|
// be regenerated by requesting a new certificate from the authority.
|
|
func (f *DynamicSource) regenerateCertificate() error {
|
|
f.Log.Info("Generating new ECDSA private key")
|
|
pk, err := pki.GenerateECPrivateKey(384)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// create the certificate template to be signed
|
|
template := &x509.Certificate{
|
|
PublicKeyAlgorithm: x509.ECDSA,
|
|
PublicKey: pk.Public(),
|
|
DNSNames: f.DNSNames,
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
}
|
|
|
|
f.Log.Info("Signing new serving certificate")
|
|
cert, err := f.Authority.Sign(template)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f.Log.Info("Signed new serving certificate")
|
|
|
|
if err := f.updateCertificate(pk, cert); err != nil {
|
|
return err
|
|
}
|
|
|
|
f.Log.Info("Updated serving TLS certificate")
|
|
return nil
|
|
}
|
|
|
|
func (f *DynamicSource) updateCertificate(pk crypto.Signer, cert *x509.Certificate) error {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
|
|
pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
certData, err := pki.EncodeX509(cert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bundle, err := tls.X509KeyPair(certData, pkData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f.cachedCertificate = &bundle
|
|
certDuration := cert.NotAfter.Sub(cert.NotBefore)
|
|
// renew the certificate 1/3 of the time before its expiry
|
|
f.nextRenew = cert.NotAfter.Add(certDuration / -3)
|
|
return nil
|
|
}
|