diff --git a/cert/cert.go b/cert/cert.go index 2ef70da..0b26918 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -45,16 +45,15 @@ const ( duration365d = time.Hour * 24 * 365 ) -var ( - ErrStaticCert = errors.New("cannot renew static certificate") -) +var ErrStaticCert = errors.New("cannot renew static certificate") -// Config contains the basic fields required for creating a certificate +// Config contains the basic fields required for creating a certificate. type Config struct { CommonName string Organization []string AltNames AltNames Usages []x509.ExtKeyUsage + ExpiresAt time.Duration } // AltNames contains the domain names and IP addresses that will be added @@ -97,7 +96,8 @@ func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, erro return x509.ParseCertificate(certDERBytes) } -// NewSignedCert creates a signed certificate using the given CA certificate and key +// NewSignedCert creates a signed certificate using the given CA certificate and key based +// on the given configuration. func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) { serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) if err != nil { @@ -109,6 +109,12 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe if len(cfg.Usages) == 0 { return nil, errors.New("must specify at least one ExtKeyUsage") } + var expiresAt time.Duration + if cfg.ExpiresAt > 0 { + expiresAt = time.Duration(cfg.ExpiresAt) + } else { + expiresAt = duration365d + } certTmpl := x509.Certificate{ Subject: pkix.Name{ @@ -119,7 +125,7 @@ func NewSignedCert(cfg Config, key crypto.Signer, caCert *x509.Certificate, caKe IPAddresses: cfg.AltNames.IPs, SerialNumber: serial, NotBefore: caCert.NotBefore, - NotAfter: time.Now().Add(duration365d).UTC(), + NotAfter: time.Now().Add(expiresAt).UTC(), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: cfg.Usages, } diff --git a/factory/gen.go b/factory/gen.go index d677b01..435c45c 100644 --- a/factory/gen.go +++ b/factory/gen.go @@ -6,7 +6,9 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/sha1" + "crypto/sha256" "crypto/x509" + "encoding/hex" "encoding/pem" "fmt" "net" @@ -195,7 +197,7 @@ func populateCN(secret *v1.Secret, cn ...string) *v1.Secret { } for _, cn := range cn { if cnRegexp.MatchString(cn) { - secret.Annotations[cnPrefix+cn] = cn + secret.Annotations[getAnnotationKey(cn)] = cn } else { logrus.Errorf("dropping invalid CN: %s", cn) } @@ -219,7 +221,7 @@ func NeedsUpdate(maxSANs int, secret *v1.Secret, cn ...string) bool { } for _, cn := range cn { - if secret.Annotations[cnPrefix+cn] == "" { + if secret.Annotations[getAnnotationKey(cn)] == "" { if maxSANs > 0 && len(cns(secret)) >= maxSANs { return false } @@ -263,3 +265,22 @@ func Marshal(x509Cert *x509.Certificate, privateKey crypto.Signer) ([]byte, []by func NewPrivateKey() (crypto.Signer, error) { return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) } + +// getAnnotationKey return the key to use for a given CN. IPv4 addresses and short hostnames +// are safe to store as-is, but longer hostnames and IPv6 address must be truncated and/or undergo +// character replacement in order to be used as an annotation key. If the CN requires modification, +// a portion of the SHA256 sum of the original value is used as the suffix, to reduce the likelihood +// of collisions in modified values. +func getAnnotationKey(cn string) string { + cn = cnPrefix + cn + cnLen := len(cn) + if cnLen < 64 && !strings.ContainsRune(cn, ':') { + return cn + } + digest := sha256.Sum256([]byte(cn)) + cn = strings.ReplaceAll(cn, ":", "_") + if cnLen > 56 { + cnLen = 56 + } + return cn[0:cnLen] + "-" + hex.EncodeToString(digest[0:])[0:6] +}