All checks were successful
continuous-integration/drone/push Build is passing
236 lines
5.4 KiB
Go
236 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
|
|
}
|
|
|
|
// update the tls.Config structure
|
|
config.ServerName = req.CommonName
|
|
|
|
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()
|
|
|
|
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(rgen.Intn(300) + 600)
|
|
|
|
log.WithFields(log.Fields{
|
|
"attempt": i,
|
|
"sleep": sleep,
|
|
"error": err,
|
|
}).Error("libvault: auto renew failed")
|
|
|
|
time.Sleep(sleep * time.Second)
|
|
|
|
} 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
|