libvault/tls.go
Simon Marsh e817179911
Some checks failed
continuous-integration/drone/push Build is failing
add backoff timer on errors
2022-08-15 14:02:56 +01:00

237 lines
5.4 KiB
Go

//////////////////////////////////////////////////////////////////////////
package libvault
//////////////////////////////////////////////////////////////////////////
import (
"crypto/tls"
"crypto/x509"
log "github.com/sirupsen/logrus"
"math/rand"
"sync"
"time"
)
//////////////////////////////////////////////////////////////////////////
type TLSRequest struct {
CommonName string `json:"common_name"`
AltNames string `json:"alt_names"`
IPSANs string `json:"ip_sans"`
URISANs string `json:"uri_sans"`
OtherSANs string `json:"other_sans"`
TTL time.Duration `json:"ttl"`
RenewPeriod time.Duration `json:"-"`
stop chan bool `json:"-"`
done sync.WaitGroup `json:"-"`
}
type TLSKeyCert struct {
Certificate string `json:"certificate"`
IssuingCA string `json:"issuing_ca"`
CAChain []string `json:"ca_chain"`
PrivateKey string `json:"private_key"`
}
//////////////////////////////////////////////////////////////////////////
// issue a new certificate based on the request
func (req *TLSRequest) Issue(t *Token) (*TLSKeyCert, error) {
log.WithFields(log.Fields{
"CommonName": req.CommonName,
}).Debug("libvault: Issuing certificate")
// default the TTL if required
if req.TTL == 0 {
req.TTL = VAULT_TTL
}
response := &struct {
Data *TLSKeyCert `json:"data"`
}{Data: &TLSKeyCert{}}
if err := vault.POST(t, "/burble.dn42/pki/sites/issue/tls",
req, response); err != nil {
log.WithFields(log.Fields{
"request": req,
"error": err,
}).Error("libvault: vault failed to issue certificate")
return nil, err
}
return response.Data, nil
}
//////////////////////////////////////////////////////////////////////////
// check if a certificate needs renewing
func (req *TLSRequest) CheckRenew(cdata []byte) (bool, error) {
// default the renew period
if req.RenewPeriod == 0 {
req.RenewPeriod = VAULT_RENEW_PERIOD
}
// parse the certificate
cert, err := x509.ParseCertificate(cdata)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("libvault: failed to parse tls certificate")
return false, err
}
// and check the ttl
ttl := cert.NotAfter.Sub(time.Now())
return (ttl.Seconds() < req.RenewPeriod.Seconds()), nil
}
//////////////////////////////////////////////////////////////////////////
func (req *TLSRequest) Renew(t *Token, config *tls.Config) (bool, error) {
// if there is an existing certificate, check if it needs renewing
if len(config.Certificates) > 0 {
renew, err := req.CheckRenew(config.Certificates[0].Certificate[0])
if err != nil {
return false, err
}
if !renew {
// nothing to see here, move along
log.WithFields(log.Fields{
"CommonName": req.CommonName,
"ttl": req.TTL.String(),
}).Info("libvault: TLS certificate renewal not required")
return false, nil
}
}
// issue a new cert
kc, err := req.Issue(t)
if err != nil {
log.Error("libvault: certificate renewal failed")
return false, err
}
config.RootCAs = x509.NewCertPool()
config.RootCAs.AppendCertsFromPEM([]byte(kc.IssuingCA))
for _, ca := range kc.CAChain {
config.RootCAs.AppendCertsFromPEM([]byte(ca))
}
cert, err := tls.X509KeyPair(
[]byte(kc.Certificate),
[]byte(kc.PrivateKey),
)
if err != nil {
log.WithFields(log.Fields{
"cert": kc.Certificate,
"key": kc.PrivateKey,
"error": err,
}).Error("libvault: unable to load x509 cert pair")
return false, err
}
config.Certificates = []tls.Certificate{cert}
log.WithFields(log.Fields{
"CommonName": req.CommonName,
}).Debug("libvault: issued TLS certificate")
return true, nil
}
//////////////////////////////////////////////////////////////////////////
// auto renew
func (req *TLSRequest) AutoRenew(
t *Token,
config *tls.Config,
callback func(config *tls.Config),
) {
log.Info("Starting TLS auto renew")
req.stop = make(chan bool)
req.done.Add(1)
rgen := rand.New(rand.NewSource(time.Now().UnixNano()))
// every day
ticker := time.NewTicker(24 * time.Hour)
go func() {
defer req.done.Done()
var backoff int = 5
for {
for i := 0; i < 3; i++ {
var err error
var updated bool
// attempt to renew token first
err = t.Renew(VAULT_TTL)
if err == nil {
updated, err = req.Renew(t, config)
}
// was there an error in either renewal ?
if err != nil {
// if renew fails then sleep for a while and try again
sleep := time.Duration(backoff+rgen.Intn(backoff/4)) * time.Second
log.WithFields(log.Fields{
"attempt": i,
"sleep": sleep,
"error": err,
}).Error("libvault: auto renew failed")
time.Sleep(sleep)
if backoff < 300 {
backoff *= 2
}
} else {
// no error
if updated {
if callback != nil {
callback(config)
}
}
break
}
}
// wait for the timer expiry
select {
case <-req.stop:
return
case <-ticker.C:
// sleep for a random period before going round loop
// to prevent stampeeding herds
sleep := time.Duration(rgen.Intn(300))
log.WithFields(log.Fields{
"sleep": sleep,
}).Debug("libvault: TLS auto renew")
time.Sleep(sleep * time.Second)
}
}
}()
}
func (req *TLSRequest) Shutdown() {
log.Info("Stopping TLS AutoRenew")
req.stop <- true
req.done.Wait()
log.Info("TLS AutoRenew Stopped")
}
//////////////////////////////////////////////////////////////////////////
// end of file