Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
Always renew certificates
Browse files Browse the repository at this point in the history
We raise the minimum required time to 20 years, effectively ensuring
that we won't reuse certificates.

We also now issue them for 2 years, to allow for the longer time
horizons of LTS kubernetes support.

Both these values can be customized through env vars, the defaults
correspond to these env vars:

ETCD_MANAGER_CERT_DURATION=2y
ETCD_MANAGER_CERT_MIN_TIME_LEFT=20y
  • Loading branch information
justinsb committed Apr 27, 2020
1 parent 9f13a11 commit 919362a
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 11 deletions.
8 changes: 7 additions & 1 deletion pkg/pki/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -18,3 +18,9 @@ go_library(
"//vendor/k8s.io/klog:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["parse_test.go"],
embed = [":go_default_library"],
)
66 changes: 61 additions & 5 deletions pkg/pki/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,71 @@ import (
"crypto/rsa"
"crypto/x509"
"fmt"
"os"
"reflect"
"sort"
"strconv"
"strings"
"time"

certutil "k8s.io/client-go/util/cert"
"k8s.io/klog"
)

// Renew certificates with less than 60 days left.
const RENEW_THRESHOLD = 60 * 24 * time.Hour
// CertDuration controls how long we issue certificates for. We set
// it to a longer time period, primarily because we don't have a nice
// means of rotation. This was historically one year, but we now set
// it to two years, with kubernetes LTS proposing one year's support.
var CertDuration = 2 * 365 * 24 * time.Hour

// CertRenewalMinTimeLeft is the minimum amount of validity required on
// a certificate to reuse it. Becasue we set this (much) higher than
// CertDuration, we will now always reissue certificates.
var CertMinTimeLeft = 20 * 365 * 24 * time.Hour

// ParseHumanDuration parses a go-style duration string, but
// recognizes additional suffixes: d means "day" and is interpreted as
// 24 hours; y means "year" and is interpreted as 365 days.
func ParseHumanDuration(s string) (time.Duration, error) {
if strings.HasSuffix(s, "y") {
s = strings.TrimSuffix(s, "y")
n, err := strconv.Atoi(s)
if err != nil {
return time.Duration(0), err
}
return time.Duration(n) * 365 * 24 * time.Hour, nil
}

if strings.HasSuffix(s, "d") {
s = strings.TrimSuffix(s, "d")
n, err := strconv.Atoi(s)
if err != nil {
return time.Duration(0), err
}
return time.Duration(n) * 24 * time.Hour, nil
}

return time.ParseDuration(s)

}

func init() {
if s := os.Getenv("ETCD_MANAGER_CERT_DURATION"); s != "" {
v, err := ParseHumanDuration(s)
if err != nil {
klog.Fatalf("failed to parse ETCD_MANAGER_CERT_DURATION=%q", s)
}
CertDuration = v
}

if s := os.Getenv("ETCD_MANAGER_CERT_MIN_TIME_LEFT"); s != "" {
v, err := ParseHumanDuration(s)
if err != nil {
klog.Fatalf("failed to parse ETCD_MANAGER_CERT_MIN_TIME_LEFT=%q", s)
}
CertMinTimeLeft = v
}
}

type Keypair struct {
Certificate *x509.Certificate
Expand Down Expand Up @@ -47,8 +102,8 @@ func EnsureKeypair(store MutableKeypair, config certutil.Config, signer *Keypair

match := true

if match && time.Until(cert.NotAfter) <= RENEW_THRESHOLD {
klog.Infof("certificate expired or almost expired, not valid after %s; will regenerate", cert.NotAfter.Format(time.RFC3339))
if match && time.Until(cert.NotAfter) <= CertMinTimeLeft {
klog.Infof("existing certificate not valid after %s; will regenerate", cert.NotAfter.Format(time.RFC3339))
match = false
}

Expand Down Expand Up @@ -113,7 +168,8 @@ func EnsureKeypair(store MutableKeypair, config certutil.Config, signer *Keypair
var cert *x509.Certificate
var err error
if signer != nil {
cert, err = NewSignedCert(&config, keypair.PrivateKey, signer.Certificate, signer.PrivateKey)
duration := CertDuration
cert, err = NewSignedCert(&config, keypair.PrivateKey, signer.Certificate, signer.PrivateKey, duration)
} else {
cert, err = certutil.NewSelfSignedCACert(config, keypair.PrivateKey)
}
Expand Down
6 changes: 2 additions & 4 deletions pkg/pki/certutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ const (
RSAPrivateKeyBlockType = "RSA PRIVATE KEY"

rsaKeySize = 2048

duration365d = time.Hour * 24 * 365
)

// EncodeCertPEM returns PEM-endcoded certificate data
Expand All @@ -67,7 +65,7 @@ func NewPrivateKey() (*rsa.PrivateKey, error) {
}

// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, duration time.Duration) (*x509.Certificate, error) {
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
return nil, err
Expand All @@ -88,7 +86,7 @@ func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certifi
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotAfter: time.Now().Add(duration365d).UTC(),
NotAfter: time.Now().Add(duration).UTC(),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: cfg.Usages,
}
Expand Down
34 changes: 34 additions & 0 deletions pkg/pki/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package pki

import (
"testing"
"time"
)

func TestParseHumanDuration(t *testing.T) {
grid := []struct {
Value string
Expected time.Duration
}{
{"10m", 10 * time.Minute},
{"24h", 24 * time.Hour},
{"1d", 24 * time.Hour},
{"10d", 10 * 24 * time.Hour},
{"365d", 365 * 24 * time.Hour},
{"1y", 365 * 24 * time.Hour},
{"10y", 10 * 365 * 24 * time.Hour},
}

for _, g := range grid {
g := g
t.Run(g.Value, func(t *testing.T) {
actual, err := ParseHumanDuration(g.Value)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if actual != g.Expected {
t.Fatalf("unexpected value; expected=%v, actual=%v", g.Expected, actual)
}
})
}
}
2 changes: 1 addition & 1 deletion test/integration/upgradedowngrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestUpgradeDowngrade(t *testing.T) {
t.Fatalf("error reading test key after downgrade: %v", err)
}
if v != "worldv3" {
t.Fatalf("unexpected test key value after upgrade: %q", v)
t.Fatalf("unexpected test key value after downgrade: %q", v)
}

n1.AssertVersion(t, fromVersion)
Expand Down

0 comments on commit 919362a

Please sign in to comment.