Mechanism for renewing a certificate based on an existing certificate

This commit is contained in:
liz
2018-08-28 17:49:56 -04:00
parent 7e3340361a
commit ab28409da3
11 changed files with 427 additions and 19 deletions

View File

@@ -137,7 +137,7 @@ func CreateCertAndKeyFilesWithCA(certSpec *KubeadmCert, caCertSpec *KubeadmCert,
return fmt.Errorf("Expected CAname for %s to be %q, but was %s", certSpec.Name, certSpec.CAName, caCertSpec.Name)
}
caCert, caKey, err := loadCertificateAuthority(cfg.CertificatesDir, caCertSpec.BaseName)
caCert, caKey, err := LoadCertificateAuthority(cfg.CertificatesDir, caCertSpec.BaseName)
if err != nil {
return fmt.Errorf("Couldn't load CA certificate %s: %v", caCertSpec.Name, err)
}
@@ -158,7 +158,8 @@ func newCertAndKeyFromSpec(certSpec *KubeadmCert, cfg *kubeadmapi.InitConfigurat
return cert, key, err
}
func loadCertificateAuthority(pkiDir string, baseName string) (*x509.Certificate, *rsa.PrivateKey, error) {
// LoadCertificateAuthority tries to load a CA in the given directory with the given name.
func LoadCertificateAuthority(pkiDir string, baseName string) (*x509.Certificate, *rsa.PrivateKey, error) {
// Checks if certificate authority exists in the PKI directory
if !pkiutil.CertOrKeyExist(pkiDir, baseName) {
return nil, nil, fmt.Errorf("couldn't load %s certificate authority from %s", baseName, pkiDir)

View File

@@ -17,8 +17,8 @@ limitations under the License.
package renewal
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"errors"
@@ -55,7 +55,7 @@ func NewCertsAPIRenawal(client kubernetes.Interface) Interface {
}
// Renew takes a certificate using the cert and key.
func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, crypto.PrivateKey, error) {
func (r *CertsAPIRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
reqTmp := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: cfg.CommonName,

View File

@@ -17,10 +17,8 @@ limitations under the License.
package renewal
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"fmt"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
@@ -29,11 +27,11 @@ import (
// FileRenewal renews a certificate using local certs
type FileRenewal struct {
caCert *x509.Certificate
caKey crypto.PrivateKey
caKey *rsa.PrivateKey
}
// NewFileRenewal takes a certificate pair to construct the Interface.
func NewFileRenewal(caCert *x509.Certificate, caKey crypto.PrivateKey) Interface {
func NewFileRenewal(caCert *x509.Certificate, caKey *rsa.PrivateKey) Interface {
return &FileRenewal{
caCert: caCert,
caKey: caKey,
@@ -41,11 +39,6 @@ func NewFileRenewal(caCert *x509.Certificate, caKey crypto.PrivateKey) Interface
}
// Renew takes a certificate using the cert and key
func (r *FileRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, crypto.PrivateKey, error) {
caKey, ok := r.caKey.(*rsa.PrivateKey)
if !ok {
return nil, nil, fmt.Errorf("unsupported private key type %t", r.caKey)
}
return pkiutil.NewCertAndKey(r.caCert, caKey, cfg)
func (r *FileRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
return pkiutil.NewCertAndKey(r.caCert, r.caKey, cfg)
}

View File

@@ -17,7 +17,7 @@ limitations under the License.
package renewal
import (
"crypto"
"crypto/rsa"
"crypto/x509"
certutil "k8s.io/client-go/util/cert"
@@ -25,5 +25,5 @@ import (
// Interface represents a standard way to renew a certificate.
type Interface interface {
Renew(*certutil.Config) (*x509.Certificate, crypto.PrivateKey, error)
Renew(*certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error)
}

View File

@@ -0,0 +1,55 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package renewal
import (
"crypto/x509"
"fmt"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
)
func RenewExistingCert(certsDir, baseName string, impl Interface) error {
cert, err := pkiutil.TryLoadCertFromDisk(certsDir, baseName)
if err != nil {
return fmt.Errorf("failed to load existing certificate %s: %v", baseName, err)
}
cfg := certToConfig(cert)
newCert, newKey, err := impl.Renew(cfg)
if err != nil {
return fmt.Errorf("failed to renew certificate %s: %v", baseName, err)
}
if err := pkiutil.WriteCertAndKey(certsDir, baseName, newCert, newKey); err != nil {
return fmt.Errorf("failed to write new certificate %s: %v", baseName, err)
}
return nil
}
func certToConfig(cert *x509.Certificate) *certutil.Config {
return &certutil.Config{
CommonName: cert.Subject.CommonName,
Organization: cert.Subject.Organization,
AltNames: certutil.AltNames{
IPs: cert.IPAddresses,
DNSNames: cert.DNSNames,
},
Usages: cert.ExtKeyUsage,
}
}

View File

@@ -19,6 +19,9 @@ package renewal
import (
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"net"
"os"
"testing"
"time"
@@ -31,6 +34,8 @@ import (
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
certtestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
)
func TestRenewImplementations(t *testing.T) {
@@ -131,3 +136,100 @@ func getCertReq(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey) *
},
}
}
func TestCertToConfig(t *testing.T) {
expectedConfig := &certutil.Config{
CommonName: "test-common-name",
Organization: []string{"sig-cluster-lifecycle"},
AltNames: certutil.AltNames{
IPs: []net.IP{net.ParseIP("10.100.0.1")},
DNSNames: []string{"test-domain.space"},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
cert := &x509.Certificate{
Subject: pkix.Name{
CommonName: "test-common-name",
Organization: []string{"sig-cluster-lifecycle"},
},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
DNSNames: []string{"test-domain.space"},
IPAddresses: []net.IP{net.ParseIP("10.100.0.1")},
}
cfg := certToConfig(cert)
if cfg.CommonName != expectedConfig.CommonName {
t.Errorf("expected common name %q, got %q", expectedConfig.CommonName, cfg.CommonName)
}
if len(cfg.Organization) != 1 || cfg.Organization[0] != expectedConfig.Organization[0] {
t.Errorf("expected organization %v, got %v", expectedConfig.Organization, cfg.Organization)
}
if len(cfg.Usages) != 1 || cfg.Usages[0] != expectedConfig.Usages[0] {
t.Errorf("expected ext key usage %v, got %v", expectedConfig.Usages, cfg.Usages)
}
if len(cfg.AltNames.IPs) != 1 || cfg.AltNames.IPs[0].String() != expectedConfig.AltNames.IPs[0].String() {
t.Errorf("expected SAN IPs %v, got %v", expectedConfig.AltNames.IPs, cfg.AltNames.IPs)
}
if len(cfg.AltNames.DNSNames) != 1 || cfg.AltNames.DNSNames[0] != expectedConfig.AltNames.DNSNames[0] {
t.Errorf("expected SAN DNSNames %v, got %v", expectedConfig.AltNames.DNSNames, cfg.AltNames.DNSNames)
}
}
func TestRenewExistingCert(t *testing.T) {
cfg := &certutil.Config{
CommonName: "test-common-name",
Organization: []string{"sig-cluster-lifecycle"},
AltNames: certutil.AltNames{
IPs: []net.IP{net.ParseIP("10.100.0.1")},
DNSNames: []string{"test-domain.space"},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
caCertCfg := &certutil.Config{CommonName: "kubernetes"}
caCert, caKey, err := certs.NewCACertAndKey(caCertCfg)
if err != nil {
t.Fatalf("couldn't create CA: %v", err)
}
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg)
if err != nil {
t.Fatalf("couldn't generate certificate: %v", err)
}
dir := testutil.SetupTempDir(t)
defer os.RemoveAll(dir)
if err := pkiutil.WriteCertAndKey(dir, "server", cert, key); err != nil {
t.Fatalf("couldn't write out certificate")
}
renewer := NewFileRenewal(caCert, caKey)
if err := RenewExistingCert(dir, "server", renewer); err != nil {
t.Fatalf("couldn't renew certificate: %v", err)
}
newCert, err := pkiutil.TryLoadCertFromDisk(dir, "server")
if err != nil {
t.Fatalf("couldn't load created certificate: %v", err)
}
if newCert.SerialNumber.Cmp(cert.SerialNumber) == 0 {
t.Fatal("expected new certificate, but renewed certificate has same serial number")
}
certtestutil.AssertCertificateIsSignedByCa(t, newCert, caCert)
certtestutil.AssertCertificateHasClientAuthUsage(t, newCert)
certtestutil.AssertCertificateHasOrganizations(t, newCert, cfg.Organization...)
certtestutil.AssertCertificateHasCommonName(t, newCert, cfg.CommonName)
certtestutil.AssertCertificateHasDNSNames(t, newCert, cfg.AltNames.DNSNames...)
certtestutil.AssertCertificateHasIPAddresses(t, newCert, cfg.AltNames.IPs...)
}