Files
talos/pkg/grpc/tls/provider.go
Andrew Rynhard 6ec5cb02cb refactor: decouple grpc client and userdata code
This detangles the gRPC client code from the userdata code. The
motivation behind this is to make creating clients more simple and not
dependent on our configuration format.

Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
2019-09-26 14:18:53 -07:00

130 lines
2.9 KiB
Go

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"log"
"net"
"sync"
"time"
"github.com/pkg/errors"
"github.com/talos-systems/talos/pkg/constants"
)
// CertificateProvider describes an interface by which TLS certificates may be managed.
type CertificateProvider interface {
// GetCA returns the active root CA.
GetCA() ([]byte, error)
// GetCertificate returns the current certificate matching the given client request.
GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
// UpdateCertificate updates the stored certificate for the given client request.
UpdateCertificates([]byte, *tls.Certificate) error
}
type embeddableCertificateProvider struct {
sync.RWMutex
ca []byte
crt *tls.Certificate
hostname string
ips []net.IP
updateFunc func() ([]byte, tls.Certificate, error)
updateHooks []func(newCert *tls.Certificate)
}
func (p *embeddableCertificateProvider) GetCA() ([]byte, error) {
if p == nil {
return nil, errors.New("no provider")
}
p.RLock()
defer p.RUnlock()
return p.ca, nil
}
func (p *embeddableCertificateProvider) GetCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
if p == nil {
return nil, errors.New("no provider")
}
p.RLock()
defer p.RUnlock()
return p.crt, nil
}
func (p *embeddableCertificateProvider) UpdateCertificates(ca []byte, cert *tls.Certificate) error {
p.Lock()
p.ca = ca
p.crt = cert
p.Unlock()
for _, f := range p.updateHooks {
f(cert)
}
return nil
}
func (p *embeddableCertificateProvider) manageUpdates(ctx context.Context) (err error) {
nextRenewal := constants.DefaultCertificateValidityDuration
for ctx.Err() == nil {
// nolint: errcheck
if c, _ := p.GetCertificate(nil); c != nil {
if len(c.Certificate) > 0 {
var crt *x509.Certificate
crt, err = x509.ParseCertificate(c.Certificate[0])
if err == nil {
nextRenewal = time.Until(crt.NotAfter) / 2
} else {
log.Println("failed to parse current leaf certificate")
}
} else {
log.Println("current leaf certificate not found")
}
} else {
log.Println("certificate not found")
}
log.Println("next renewal in", nextRenewal)
if nextRenewal > constants.DefaultCertificateValidityDuration {
nextRenewal = constants.DefaultCertificateValidityDuration
}
select {
case <-time.After(nextRenewal):
case <-ctx.Done():
return nil
}
var (
ca []byte
cert tls.Certificate
)
if ca, cert, err = p.updateFunc(); err != nil {
log.Println("failed to renew certificate:", err)
continue
}
if err = p.UpdateCertificates(ca, &cert); err != nil {
log.Println("failed to renew certificate:", err)
continue
}
}
return errors.New("certificate update manager exited unexpectedly")
}