Skip to content

Commit

Permalink
Certificate Rotatation in APIServer
Browse files Browse the repository at this point in the history
fixes: #1152
  • Loading branch information
MatthewHinton56 committed Aug 26, 2020
1 parent 23d5c33 commit c69be3d
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 29 deletions.
12 changes: 0 additions & 12 deletions pkg/apiserver/certificate/cacert_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,6 @@ func newCACertController(caContentProvider dynamiccertificates.CAContentProvider
return c
}

// UpdateCertificate updates the certificate to a new one. Used to rotate statically signed certificates before they
// expire.
func (c *CACertController) UpdateCertificate(caContentProvider dynamiccertificates.CAContentProvider) {
c.mutex.Lock()
defer c.mutex.Unlock()

c.caContentProvider = caContentProvider

// Need to trigger a sync.
c.Enqueue()
}

// getCertificate exposes the certificate for testing.
func (c *CACertController) getCertificate() []byte {
c.mutex.RLock()
Expand Down
45 changes: 31 additions & 14 deletions pkg/apiserver/certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package certificate

import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"os"
Expand All @@ -27,6 +25,8 @@ import (
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/client-go/kubernetes"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
"k8s.io/klog"
"k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"

Expand All @@ -36,6 +36,9 @@ import (
var (
// certDir is the directory that the TLS Secret should be mounted to. Declaring it as a variable for testing.
certDir = "/var/run/antrea/antrea-controller-tls"

// selfSignedCertDir is the dir Antrea self signed certificates are created in.
selfSignedCertDir = "/var/run/antrea/antrea-controller-self-signed"
// certReadyTimeout is the timeout we will wait for the TLS Secret being ready. Declaring it as a variable for testing.
certReadyTimeout = 2 * time.Minute

Expand Down Expand Up @@ -124,15 +127,14 @@ func generateSelfSignedCertificate(secureServing *options.SecureServingOptionsWi
var caContentProvider dynamiccertificates.CAContentProvider

// Set the PairName but leave certificate directory blank to generate in-memory by default.
secureServing.ServerCert.CertDirectory = ""
secureServing.ServerCert.CertDirectory = selfSignedCertDir
secureServing.ServerCert.PairName = "antrea-controller"

if err := secureServing.MaybeDefaultWithSelfSignedCerts("antrea", GetAntreaServerNames(), []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
}

certPEMBlock, _ := secureServing.ServerCert.GeneratedCert.CurrentCertKeyContent()
caContentProvider, err = dynamiccertificates.NewStaticCAContent("self-signed cert", certPEMBlock)
caContentProvider, err = dynamiccertificates.NewDynamicCAContentFromFile("self-signed cert", secureServing.ServerCert.CertKey.CertFile)
if err != nil {
return nil, fmt.Errorf("error reading self-signed CA certificate: %v", err)
}
Expand All @@ -145,20 +147,15 @@ func generateSelfSignedCertificate(secureServing *options.SecureServingOptionsWi
// Also can be used to pass a user provided rotation window.
func nextRotationDuration(secureServing *options.SecureServingOptionsWithLoopback,
maxRotateDuration time.Duration) (time.Duration, error) {
pemCert, pemKey := secureServing.ServerCert.GeneratedCert.CurrentCertKeyContent()
cert, err := tls.X509KeyPair(pemCert, pemKey)
if err != nil {
return time.Duration(0), fmt.Errorf("error parsing generated certificate: %v", err)
}

x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
x509Cert, err := certutil.CertsFromFile(secureServing.ServerCert.CertKey.CertFile)
if err != nil {
return time.Duration(0), fmt.Errorf("error parsing generated certificate: %v", err)
}

// Attempt to rotate the certificate at the half-way point of expiration.
// Unless the halfway point is longer than maxRotateDuration
duration := x509Cert.NotAfter.Sub(time.Now()) / 2
duration := x509Cert[0].NotAfter.Sub(time.Now()) / 2

waitDuration := duration
if maxRotateDuration < waitDuration {
Expand All @@ -178,15 +175,35 @@ func rotateSelfSignedCertificates(c *CACertController, secureServing *options.Se
klog.Errorf("error reading expiration date of cert: %v", err)
return
}

klog.Infof("Certificate will be rotated at %v", time.Now().Add(rotationDuration))

time.Sleep(rotationDuration)

klog.Infof("Rotating self signed certificate")

caContentProvider, err := generateSelfSignedCertificate(secureServing)
err = generateNewServingCertificate(secureServing)
if err != nil {
klog.Errorf("error generating new cert: %v", err)
return
}
c.UpdateCertificate(caContentProvider)
c.RunOnce()
}
}

func generateNewServingCertificate(secureServing *options.SecureServingOptionsWithLoopback) error {
cert, key, err := certutil.GenerateSelfSignedCertKeyWithFixtures("antrea", []net.IP{net.ParseIP("127.0.0.1")}, GetAntreaServerNames(), secureServing.ServerCert.FixtureDirectory)
if err != nil {
return fmt.Errorf("unable to generate self signed cert: %v", err)
}

if err := certutil.WriteCert(secureServing.ServerCert.CertKey.CertFile, cert); err != nil {
return err
}
if err := keyutil.WriteKey(secureServing.ServerCert.CertKey.KeyFile, key); err != nil {
return err
}
klog.Infof("Generated self-signed cert (%s, %s)", secureServing.ServerCert.CertKey.CertFile, secureServing.ServerCert.CertKey.KeyFile)

return nil
}
11 changes: 8 additions & 3 deletions pkg/apiserver/certificate/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,16 @@ func TestApplyServerCert(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var err error
certDir, err = ioutil.TempDir("", "antrea-tls-test")
certReadyTimeout = 100 * time.Millisecond
if err != nil {
t.Fatalf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(certDir)
selfSignedCertDir, err = ioutil.TempDir("", "antrea-self-signed")
if err != nil {
t.Fatalf("Unable to create temporary directory: %v", err)
}
defer os.RemoveAll(selfSignedCertDir)
certReadyTimeout = 100 * time.Millisecond
secureServing := genericoptions.NewSecureServingOptions().WithLoopback()
if tt.tlsCert != nil {
certutil.WriteCert(path.Join(certDir, TLSCertFile), tt.tlsCert)
Expand Down Expand Up @@ -203,9 +208,9 @@ func TestApplyServerCert(t *testing.T) {
assert.Equal(t, genericoptions.CertKey{CertFile: certDir + "/tls.crt", KeyFile: certDir + "/tls.key"}, secureServing.ServerCert.CertKey, "CertKey doesn't match")
}
if tt.wantGeneratedCert {
assert.NotNil(t, secureServing.ServerCert.GeneratedCert)
assert.Equal(t, genericoptions.CertKey{CertFile: selfSignedCertDir + "/antrea-controller.crt", KeyFile: selfSignedCertDir + "/antrea-controller.key"}, secureServing.ServerCert.CertKey, "CertKey doesn't match")
} else {
assert.Nil(t, secureServing.ServerCert.GeneratedCert)
assert.NotEqual(t, genericoptions.CertKey{CertFile: selfSignedCertDir + "/antrea-controller.crt", KeyFile: selfSignedCertDir + "/antrea-controller.key"}, secureServing.ServerCert.CertKey, "CertKey doesn't match")
}
if tt.wantCACert != nil {
assert.Equal(t, tt.wantCACert, got.caContentProvider.CurrentCABundleContent(), "CA cert doesn't match")
Expand Down

0 comments on commit c69be3d

Please sign in to comment.