mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
Actually renew certificates (using on-disk CAs)
This commit is contained in:
parent
ab28409da3
commit
76be5ca581
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user