tls: automatic certificate renewal
This commit is contained in:
parent
5e667295ac
commit
748a028161
@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
from mcluseau/golang-builder:1.14.0 as build
|
from mcluseau/golang-builder:1.14.1 as build
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
from debian:stretch
|
from debian:stretch
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@ -12,8 +14,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cespare/xxhash"
|
"github.com/cespare/xxhash"
|
||||||
|
"github.com/cloudflare/cfssl/certinfo"
|
||||||
"github.com/cloudflare/cfssl/config"
|
"github.com/cloudflare/cfssl/config"
|
||||||
"github.com/cloudflare/cfssl/csr"
|
"github.com/cloudflare/cfssl/csr"
|
||||||
"github.com/cloudflare/cfssl/helpers"
|
"github.com/cloudflare/cfssl/helpers"
|
||||||
@ -212,11 +216,49 @@ func (sd *SecretData) Token(cluster, name string) (token string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sd *SecretData) RenewCACert(cluster, name string) (err error) {
|
||||||
|
cs := sd.cluster(cluster)
|
||||||
|
|
||||||
|
ca := cs.CAs[name]
|
||||||
|
|
||||||
|
var cert *x509.Certificate
|
||||||
|
cert, err = helpers.ParseCertificatePEM(ca.Cert)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var signer crypto.Signer
|
||||||
|
signer, err = helpers.ParsePrivateKeyPEM(ca.Key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newCert, err := initca.RenewFromSigner(cert, signer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sd.l.Lock()
|
||||||
|
defer sd.l.Unlock()
|
||||||
|
|
||||||
|
cs.CAs[name].Cert = newCert
|
||||||
|
sd.changed = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (sd *SecretData) CA(cluster, name string) (ca *CA, err error) {
|
func (sd *SecretData) CA(cluster, name string) (ca *CA, err error) {
|
||||||
cs := sd.cluster(cluster)
|
cs := sd.cluster(cluster)
|
||||||
|
|
||||||
ca, ok := cs.CAs[name]
|
ca, ok := cs.CAs[name]
|
||||||
if ok {
|
if ok {
|
||||||
|
checkErr := checkCertUsable(ca.Cert)
|
||||||
|
if checkErr == nil {
|
||||||
|
log.Infof("secret-data cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr)
|
||||||
|
|
||||||
|
err = sd.RenewCACert(cluster, name)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +298,22 @@ func (sd *SecretData) CA(cluster, name string) (ca *CA, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkCertUsable(certPEM []byte) error {
|
||||||
|
cert, err := certinfo.ParseCertificatePEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certDuration := cert.NotAfter.Sub(cert.NotBefore)
|
||||||
|
delayBeforeRegen := certDuration / 3 // TODO allow configuration
|
||||||
|
|
||||||
|
if cert.NotAfter.Sub(time.Now()) < delayBeforeRegen {
|
||||||
|
return errors.New("too old")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sd *SecretData) KeyCert(cluster, caName, name, profile, label string, req *csr.CertificateRequest) (kc *KeyCert, err error) {
|
func (sd *SecretData) KeyCert(cluster, caName, name, profile, label string, req *csr.CertificateRequest) (kc *KeyCert, err error) {
|
||||||
for idx, host := range req.Hosts {
|
for idx, host := range req.Hosts {
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
@ -288,15 +346,21 @@ func (sd *SecretData) KeyCert(cluster, caName, name, profile, label string, req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logPrefix := fmt.Sprintf("secret-data: cluster %s: CA %s:", cluster, caName)
|
||||||
|
|
||||||
rh := hash(req)
|
rh := hash(req)
|
||||||
kc, ok := ca.Signed[name]
|
kc, ok := ca.Signed[name]
|
||||||
if ok && rh == kc.ReqHash {
|
if ok && rh == kc.ReqHash {
|
||||||
|
err = checkCertUsable(kc.Cert)
|
||||||
|
if err == nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
log.Infof("%s regenerating certificate: ", err)
|
||||||
|
|
||||||
} else if ok {
|
} else if ok {
|
||||||
log.Infof("secret-data: cluster %s: CA %s: CSR changed for %s: hash=%q previous=%q",
|
log.Infof("%s CSR changed for %s: hash=%q previous=%q", name, rh, kc.ReqHash)
|
||||||
cluster, caName, name, rh, kc.ReqHash)
|
|
||||||
} else {
|
} else {
|
||||||
log.Infof("secret-data: cluster %s: CA %s: new CSR for %s", cluster, caName, name)
|
log.Infof("%s new CSR for %s", logPrefix, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
sd.l.Lock()
|
sd.l.Lock()
|
||||||
|
@ -135,3 +135,39 @@ func wsClusterBootstrapPods(req *restful.Request, resp *restful.Response) {
|
|||||||
|
|
||||||
wsRender(resp, cluster.BootstrapPods, cluster)
|
wsRender(resp, cluster.BootstrapPods, cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wsClusterCACert(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := secretData.CA(req.PathParameter("cluster"), req.PathParameter("ca-name"))
|
||||||
|
if err != nil {
|
||||||
|
wsError(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(ca.Cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsClusterSignedCert(req *restful.Request, resp *restful.Response) {
|
||||||
|
cluster := wsReadCluster(req, resp)
|
||||||
|
if cluster == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := secretData.CA(req.PathParameter("cluster"), req.PathParameter("ca-name"))
|
||||||
|
if err != nil {
|
||||||
|
wsError(resp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kc := ca.Signed[req.QueryParameter("name")]
|
||||||
|
if kc == nil {
|
||||||
|
wsNotFound(req, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(kc.Cert)
|
||||||
|
}
|
||||||
|
@ -49,6 +49,14 @@ func registerWS(rest *restful.Container) {
|
|||||||
ws.Route(ws.PUT("/clusters/{cluster-name}/passwords/{password-name}").To(wsClusterSetPassword).
|
ws.Route(ws.PUT("/clusters/{cluster-name}/passwords/{password-name}").To(wsClusterSetPassword).
|
||||||
Doc("Set cluster's password"))
|
Doc("Set cluster's password"))
|
||||||
|
|
||||||
|
ws.Route(ws.GET("/clusters/{cluster-name}/ca/{ca-name}/certificate").To(wsClusterCACert).
|
||||||
|
Produces(mime.CACERT).
|
||||||
|
Doc("Get cluster CA's certificate"))
|
||||||
|
ws.Route(ws.GET("/clusters/{cluster-name}/ca/{ca-name}/signed").To(wsClusterSignedCert).
|
||||||
|
Produces(mime.CERT).
|
||||||
|
Param(ws.QueryParameter("name", "signed reference name").Required(true)).
|
||||||
|
Doc("Get cluster's certificate signed by the CA"))
|
||||||
|
|
||||||
ws.Route(ws.GET("/clusters/{cluster-name}/tokens/{token-name}").To(wsClusterToken).
|
ws.Route(ws.GET("/clusters/{cluster-name}/tokens/{token-name}").To(wsClusterToken).
|
||||||
Doc("Get cluster's token"))
|
Doc("Get cluster's token"))
|
||||||
|
|
||||||
|
@ -7,4 +7,6 @@ const (
|
|||||||
ISO = "application/x-iso9660-image"
|
ISO = "application/x-iso9660-image"
|
||||||
IPXE = "text/x-ipxe"
|
IPXE = "text/x-ipxe"
|
||||||
OCTET = "application/octet-stream"
|
OCTET = "application/octet-stream"
|
||||||
|
CERT = "application/x-x509-user-cert"
|
||||||
|
CACERT = "application/x-x509-ca-cert"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user