Actually renew certificates (using on-disk CAs)

This commit is contained in:
liz 2018-08-29 17:57:02 -04:00
parent ab28409da3
commit 76be5ca581
No known key found for this signature in database
GPG Key ID: 42D1F3A8C4A02586
7 changed files with 175 additions and 26 deletions

View File

@ -24,7 +24,14 @@ go_test(
name = "go_default_test",
srcs = ["renewal_test.go"],
embed = [":go_default_library"],
deps = ["//vendor/github.com/spf13/cobra:go_default_library"],
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//cmd/kubeadm/test/certs:go_default_library",
"//cmd/kubeadm/test/cmd:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
],
)
filegroup(

View File

@ -35,18 +35,13 @@ import (
)
func NewCmdCertsRenewal() *cobra.Command {
cfg := &renewConfig{
kubeconfigPath: constants.GetAdminKubeConfigPath(),
}
cmd := &cobra.Command{
Use: "renew",
Short: "Renews all known certificates for kubeadm",
Long: "", // TODO EKF fill out
}
addFlags(cmd, cfg)
cmd.AddCommand(getRenewSubCommands(cfg)...)
cmd.AddCommand(getRenewSubCommands()...)
return cmd
}
@ -58,7 +53,10 @@ type renewConfig struct {
useAPI bool
}
func getRenewSubCommands(cfg *renewConfig) []*cobra.Command {
func getRenewSubCommands() []*cobra.Command {
cfg := &renewConfig{
kubeconfigPath: constants.GetAdminKubeConfigPath(),
}
// Default values for the cobra help text
kubeadmscheme.Scheme.Default(&cfg.cfg)
@ -84,28 +82,31 @@ func addFlags(cmd *cobra.Command, cfg *renewConfig) {
cmd.Flags().BoolVar(&cfg.useAPI, "use-api", cfg.useAPI, "Use the kubernetes certificate API to renew certificates")
}
func generateCertCommand(name, longName string) *cobra.Command {
// generateCertCommand takes mostly strings instead of structs to avoid using structs in a for loop
func generateCertCommand(name, longName, baseName, caCertBaseName string, cfg *renewConfig) *cobra.Command {
return &cobra.Command{
Use: name,
Short: fmt.Sprintf("Generates the %s", longName),
Long: "", // TODO EKF fill out
Run: func(cmd *cobra.Command, args []string) {
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg)
kubeadmutil.CheckErr(err)
renewer, err := getRenewer(cfg, caCertBaseName)
kubeadmutil.CheckErr(err)
err = renewal.RenewExistingCert(internalcfg.CertificatesDir, baseName, renewer)
kubeadmutil.CheckErr(err)
},
}
}
func makeCommandForRenew(cert *certsphase.KubeadmCert, caCert *certsphase.KubeadmCert, cfg *renewConfig) *cobra.Command {
certCmd := generateCertCommand(cert.Name, cert.LongName)
certCmd := generateCertCommand(cert.Name, cert.LongName, cert.BaseName, caCert.BaseName, cfg)
addFlags(certCmd, cfg)
certCmd.Run = func(cmd *cobra.Command, args []string) {
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfg.cfgPath, &cfg.cfg)
kubeadmutil.CheckErr(err)
renewer, err := getRenewer(cfg, caCert)
}
return certCmd
}
func getRenewer(cfg *renewConfig, caCertSpec *certsphase.KubeadmCert) (renewal.Interface, error) {
func getRenewer(cfg *renewConfig, caCertBaseName string) (renewal.Interface, error) {
if cfg.useAPI {
kubeConfigPath := cmdutil.FindExistingKubeConfig(cfg.kubeconfigPath)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
@ -115,7 +116,7 @@ func getRenewer(cfg *renewConfig, caCertSpec *certsphase.KubeadmCert) (renewal.I
return renewal.NewCertsAPIRenawal(client), nil
}
caCert, caKey, err := certsphase.LoadCertificateAuthority(cfg.cfg.CertificatesDir, caCertSpec.BaseName)
caCert, caKey, err := certsphase.LoadCertificateAuthority(cfg.cfg.CertificatesDir, caCertBaseName)
if err != nil {
return nil, err
}

View File

@ -17,10 +17,24 @@ limitations under the License.
package renew
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"os"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
certstestutil "k8s.io/kubernetes/cmd/kubeadm/test/certs"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
)
func TestCommandsGenerated(t *testing.T) {
@ -31,7 +45,8 @@ func TestCommandsGenerated(t *testing.T) {
}
expectedCommands := []string{
"renew",
// TODO(EKF): add `renew all`
// "renew",
"renew apiserver",
"renew apiserver-kubelet-client",
@ -64,3 +79,122 @@ func TestCommandsGenerated(t *testing.T) {
})
}
}
func TestRunRenewCommands(t *testing.T) {
tests := []struct {
command string
baseName string
caBaseName string
}{
{
command: "apiserver",
baseName: kubeadmconstants.APIServerCertAndKeyBaseName,
caBaseName: kubeadmconstants.CACertAndKeyBaseName,
},
{
command: "apiserver-kubelet-client",
baseName: kubeadmconstants.APIServerKubeletClientCertAndKeyBaseName,
caBaseName: kubeadmconstants.CACertAndKeyBaseName,
},
{
command: "apiserver-etcd-client",
baseName: kubeadmconstants.APIServerEtcdClientCertAndKeyBaseName,
caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName,
},
{
command: "front-proxy-client",
baseName: kubeadmconstants.FrontProxyClientCertAndKeyBaseName,
caBaseName: kubeadmconstants.FrontProxyCACertAndKeyBaseName,
},
{
command: "etcd-server",
baseName: kubeadmconstants.EtcdServerCertAndKeyBaseName,
caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName,
},
{
command: "etcd-peer",
baseName: kubeadmconstants.EtcdPeerCertAndKeyBaseName,
caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName,
},
{
command: "etcd-healthcheck-client",
baseName: kubeadmconstants.EtcdHealthcheckClientCertAndKeyBaseName,
caBaseName: kubeadmconstants.EtcdCACertAndKeyBaseName,
},
}
renewCmds := getRenewSubCommands()
for _, test := range tests {
t.Run(test.command, func(t *testing.T) {
tmpDir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpDir)
caCert, caKey := certstestutil.SetupCertificateAuthorithy(t)
if err := pkiutil.WriteCertAndKey(tmpDir, test.caBaseName, caCert, caKey); err != nil {
t.Fatalf("couldn't write out CA: %v", err)
}
certTmpl := x509.Certificate{
Subject: pkix.Name{
CommonName: "test-cert",
Organization: []string{"sig-cluster-lifecycle"},
},
DNSNames: []string{"test-domain.space"},
SerialNumber: new(big.Int).SetInt64(0),
NotBefore: time.Now().Add(-time.Hour * 24 * 365),
NotAfter: time.Now().Add(-time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatalf("couldn't generate private key: %v", err)
}
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
if err != nil {
t.Fatalf("couldn't generate private key: %v", err)
}
cert, err := x509.ParseCertificate(certDERBytes)
if err != nil {
t.Fatalf("couldn't generate private key: %v", err)
}
if err := pkiutil.WriteCertAndKey(tmpDir, test.baseName, cert, key); err != nil {
t.Fatalf("couldn't write out initial certificate")
}
cmdtestutil.RunSubCommand(t, renewCmds, test.command, fmt.Sprintf("--cert-dir=%s", tmpDir))
newCert, newKey, err := pkiutil.TryLoadCertAndKeyFromDisk(tmpDir, test.baseName)
if err != nil {
t.Fatalf("couldn't load renewed certificate: %v", err)
}
certstestutil.AssertCertificateIsSignedByCa(t, newCert, caCert)
pool := x509.NewCertPool()
pool.AddCert(caCert)
_, err = newCert.Verify(x509.VerifyOptions{
DNSName: "test-domain.space",
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
})
if err != nil {
t.Errorf("couldn't verify renewed cert: %v", err)
}
pubKey, ok := newCert.PublicKey.(*rsa.PublicKey)
if !ok {
t.Errorf("unknown public key type %T", newCert.PublicKey)
} else if pubKey.N.Cmp(newKey.N) != 0 {
t.Error("private key does not match public key")
}
})
}
}

View File

@ -130,7 +130,7 @@ func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error {
// CertOrKeyExist returns a boolean whether the cert or the key exists
func CertOrKeyExist(pkiPath, name string) bool {
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
certificatePath, privateKeyPath := PathsForCertAndKey(pkiPath, name)
_, certErr := os.Stat(certificatePath)
_, keyErr := os.Stat(privateKeyPath)
@ -234,7 +234,8 @@ func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rs
return k, p, nil
}
func pathsForCertAndKey(pkiPath, name string) (string, string) {
// PathsForCertAndKey returns the paths for the certificate and key given the path and basename.
func PathsForCertAndKey(pkiPath, name string) (string, string) {
return pathForCert(pkiPath, name), pathForKey(pkiPath, name)
}

View File

@ -405,7 +405,7 @@ func TestTryLoadKeyFromDisk(t *testing.T) {
}
func TestPathsForCertAndKey(t *testing.T) {
crtPath, keyPath := pathsForCertAndKey("/foo", "bar")
crtPath, keyPath := PathsForCertAndKey("/foo", "bar")
if crtPath != "/foo/bar.crt" {
t.Errorf("unexpected certificate path: %s", crtPath)
}

View File

@ -25,12 +25,17 @@ import (
)
func RenewExistingCert(certsDir, baseName string, impl Interface) error {
cert, err := pkiutil.TryLoadCertFromDisk(certsDir, baseName)
certificatePath, _ := pkiutil.PathsForCertAndKey(certsDir, baseName)
certs, err := certutil.CertsFromFile(certificatePath)
if err != nil {
return fmt.Errorf("failed to load existing certificate %s: %v", baseName, err)
}
cfg := certToConfig(cert)
if len(certs) != 1 {
return fmt.Errorf("wanted exactly one certificate, got %d", len(certs))
}
cfg := certToConfig(certs[0])
newCert, newKey, err := impl.Renew(cfg)
if err != nil {
return fmt.Errorf("failed to renew certificate %s: %v", baseName, err)

View File

@ -20,6 +20,7 @@ import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
@ -87,7 +88,7 @@ func NewSelfSignedCACert(cfg Config, key *rsa.PrivateKey) (*x509.Certificate, er
// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg Config, key *rsa.PrivateKey, caCert *x509.Certificate, caKey *rsa.PrivateKey) (*x509.Certificate, error) {
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64))
serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
if err != nil {
return nil, err
}