mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +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",
|
name = "go_default_test",
|
||||||
srcs = ["renewal_test.go"],
|
srcs = ["renewal_test.go"],
|
||||||
embed = [":go_default_library"],
|
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(
|
filegroup(
|
||||||
|
@ -35,18 +35,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewCmdCertsRenewal() *cobra.Command {
|
func NewCmdCertsRenewal() *cobra.Command {
|
||||||
cfg := &renewConfig{
|
|
||||||
kubeconfigPath: constants.GetAdminKubeConfigPath(),
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "renew",
|
Use: "renew",
|
||||||
Short: "Renews all known certificates for kubeadm",
|
Short: "Renews all known certificates for kubeadm",
|
||||||
Long: "", // TODO EKF fill out
|
Long: "", // TODO EKF fill out
|
||||||
}
|
}
|
||||||
addFlags(cmd, cfg)
|
|
||||||
|
|
||||||
cmd.AddCommand(getRenewSubCommands(cfg)...)
|
cmd.AddCommand(getRenewSubCommands()...)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -58,7 +53,10 @@ type renewConfig struct {
|
|||||||
useAPI bool
|
useAPI bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRenewSubCommands(cfg *renewConfig) []*cobra.Command {
|
func getRenewSubCommands() []*cobra.Command {
|
||||||
|
cfg := &renewConfig{
|
||||||
|
kubeconfigPath: constants.GetAdminKubeConfigPath(),
|
||||||
|
}
|
||||||
// Default values for the cobra help text
|
// Default values for the cobra help text
|
||||||
kubeadmscheme.Scheme.Default(&cfg.cfg)
|
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")
|
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{
|
return &cobra.Command{
|
||||||
Use: name,
|
Use: name,
|
||||||
Short: fmt.Sprintf("Generates the %s", longName),
|
Short: fmt.Sprintf("Generates the %s", longName),
|
||||||
Long: "", // TODO EKF fill out
|
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 {
|
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)
|
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
|
return certCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRenewer(cfg *renewConfig, caCertSpec *certsphase.KubeadmCert) (renewal.Interface, error) {
|
func getRenewer(cfg *renewConfig, caCertBaseName string) (renewal.Interface, error) {
|
||||||
if cfg.useAPI {
|
if cfg.useAPI {
|
||||||
kubeConfigPath := cmdutil.FindExistingKubeConfig(cfg.kubeconfigPath)
|
kubeConfigPath := cmdutil.FindExistingKubeConfig(cfg.kubeconfigPath)
|
||||||
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
|
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
|
||||||
@ -115,7 +116,7 @@ func getRenewer(cfg *renewConfig, caCertSpec *certsphase.KubeadmCert) (renewal.I
|
|||||||
return renewal.NewCertsAPIRenawal(client), nil
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,24 @@ limitations under the License.
|
|||||||
package renew
|
package renew
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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) {
|
func TestCommandsGenerated(t *testing.T) {
|
||||||
@ -31,7 +45,8 @@ func TestCommandsGenerated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedCommands := []string{
|
expectedCommands := []string{
|
||||||
"renew",
|
// TODO(EKF): add `renew all`
|
||||||
|
// "renew",
|
||||||
|
|
||||||
"renew apiserver",
|
"renew apiserver",
|
||||||
"renew apiserver-kubelet-client",
|
"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
|
// CertOrKeyExist returns a boolean whether the cert or the key exists
|
||||||
func CertOrKeyExist(pkiPath, name string) bool {
|
func CertOrKeyExist(pkiPath, name string) bool {
|
||||||
certificatePath, privateKeyPath := pathsForCertAndKey(pkiPath, name)
|
certificatePath, privateKeyPath := PathsForCertAndKey(pkiPath, name)
|
||||||
|
|
||||||
_, certErr := os.Stat(certificatePath)
|
_, certErr := os.Stat(certificatePath)
|
||||||
_, keyErr := os.Stat(privateKeyPath)
|
_, keyErr := os.Stat(privateKeyPath)
|
||||||
@ -234,7 +234,8 @@ func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rs
|
|||||||
return k, p, nil
|
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)
|
return pathForCert(pkiPath, name), pathForKey(pkiPath, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,7 +405,7 @@ func TestTryLoadKeyFromDisk(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPathsForCertAndKey(t *testing.T) {
|
func TestPathsForCertAndKey(t *testing.T) {
|
||||||
crtPath, keyPath := pathsForCertAndKey("/foo", "bar")
|
crtPath, keyPath := PathsForCertAndKey("/foo", "bar")
|
||||||
if crtPath != "/foo/bar.crt" {
|
if crtPath != "/foo/bar.crt" {
|
||||||
t.Errorf("unexpected certificate path: %s", crtPath)
|
t.Errorf("unexpected certificate path: %s", crtPath)
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RenewExistingCert(certsDir, baseName string, impl Interface) error {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load existing certificate %s: %v", baseName, err)
|
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)
|
newCert, newKey, err := impl.Renew(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to renew certificate %s: %v", baseName, err)
|
return fmt.Errorf("failed to renew certificate %s: %v", baseName, err)
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
cryptorand "crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"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
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user