Add support for cross-signing new certs during ca rotation

We need to send the full chain in order for cross-signing to work
properly during switchover to a new root.

Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
This commit is contained in:
Brad Davidson 2023-03-07 23:05:52 +00:00 committed by Brad Davidson
parent 68fcb48a35
commit 977a85559e
4 changed files with 41 additions and 17 deletions

View File

@ -24,6 +24,7 @@ import (
"github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/passwd" "github.com/k3s-io/k3s/pkg/passwd"
"github.com/k3s-io/k3s/pkg/token" "github.com/k3s-io/k3s/pkg/token"
"github.com/k3s-io/k3s/pkg/util"
"github.com/k3s-io/k3s/pkg/version" "github.com/k3s-io/k3s/pkg/version"
certutil "github.com/rancher/dynamiclistener/cert" certutil "github.com/rancher/dynamiclistener/cert"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -592,7 +593,7 @@ func createClientCertKey(regen bool, commonName string, organization []string, a
return false, err return false, err
} }
caCert, err := certutil.CertsFromFile(caCertFile) caCerts, err := certutil.CertsFromFile(caCertFile)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -615,12 +616,12 @@ func createClientCertKey(regen bool, commonName string, organization []string, a
if altNames != nil { if altNames != nil {
cfg.AltNames = *altNames cfg.AltNames = *altNames
} }
cert, err := certutil.NewSignedCert(cfg, key.(crypto.Signer), caCert[0], caKey.(crypto.Signer)) cert, err := certutil.NewSignedCert(cfg, key.(crypto.Signer), caCerts[0], caKey.(crypto.Signer))
if err != nil { if err != nil {
return false, err return false, err
} }
return true, certutil.WriteCert(certFile, append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...)) return true, certutil.WriteCert(certFile, util.EncodeCertsPEM(cert, caCerts))
} }
func exists(files ...string) bool { func exists(files ...string) bool {

View File

@ -143,10 +143,6 @@ func validateCA(oldCAPath, newCAPath string) error {
return err return err
} }
if len(oldCerts) == 1 {
return errors.New("old CA is self-signed")
}
newCerts, err := certutil.CertsFromFile(newCAPath) newCerts, err := certutil.CertsFromFile(newCAPath)
if err != nil { if err != nil {
return err return err
@ -158,16 +154,26 @@ func validateCA(oldCAPath, newCAPath string) error {
roots := x509.NewCertPool() roots := x509.NewCertPool()
intermediates := x509.NewCertPool() intermediates := x509.NewCertPool()
for i, cert := range oldCerts {
// Load all certs from the old bundle
for _, cert := range oldCerts {
if len(cert.AuthorityKeyId) == 0 || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) {
roots.AddCert(cert)
} else {
intermediates.AddCert(cert)
}
}
// Include any intermediates from the new bundle, in case they're cross-signed by a cert in the old bundle
for i, cert := range newCerts {
if i > 0 { if i > 0 {
if len(cert.AuthorityKeyId) == 0 || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) { if len(cert.AuthorityKeyId) > 0 {
roots.AddCert(cert)
} else {
intermediates.AddCert(cert) intermediates.AddCert(cert)
} }
} }
} }
// Verify the first cert in the bundle, using the combined roots and intermediates
_, err = newCerts[0].Verify(x509.VerifyOptions{Roots: roots, Intermediates: intermediates}) _, err = newCerts[0].Verify(x509.VerifyOptions{Roots: roots, Intermediates: intermediates})
if err != nil { if err != nil {
err = errors.Wrap(err, "new CA cert cannot be verified using old CA chain") err = errors.Wrap(err, "new CA cert cannot be verified using old CA chain")

View File

@ -219,7 +219,7 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo
return return
} }
caCert, caKey, key, err := getCACertAndKeys(server.Runtime.ServerCA, server.Runtime.ServerCAKey, server.Runtime.ServingKubeletKey) caCerts, caKey, key, err := getCACertAndKeys(server.Runtime.ServerCA, server.Runtime.ServerCAKey, server.Runtime.ServingKubeletKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
@ -245,7 +245,7 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo
DNSNames: []string{nodeName, "localhost"}, DNSNames: []string{nodeName, "localhost"},
IPs: ips, IPs: ips,
}, },
}, key, caCert[0], caKey) }, key, caCerts[0], caKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
@ -257,7 +257,7 @@ func servingKubeletCert(server *config.Control, keyFile string, auth nodePassBoo
return return
} }
resp.Write(append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...)) resp.Write(util.EncodeCertsPEM(cert, caCerts))
resp.Write(keyBytes) resp.Write(keyBytes)
}) })
} }
@ -275,7 +275,7 @@ func clientKubeletCert(server *config.Control, keyFile string, auth nodePassBoot
return return
} }
caCert, caKey, key, err := getCACertAndKeys(server.Runtime.ClientCA, server.Runtime.ClientCAKey, server.Runtime.ClientKubeletKey) caCerts, caKey, key, err := getCACertAndKeys(server.Runtime.ClientCA, server.Runtime.ClientCAKey, server.Runtime.ClientKubeletKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
@ -285,7 +285,7 @@ func clientKubeletCert(server *config.Control, keyFile string, auth nodePassBoot
CommonName: "system:node:" + nodeName, CommonName: "system:node:" + nodeName,
Organization: []string{user.NodesGroup}, Organization: []string{user.NodesGroup},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}, key, caCert[0], caKey) }, key, caCerts[0], caKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
@ -297,7 +297,7 @@ func clientKubeletCert(server *config.Control, keyFile string, auth nodePassBoot
return return
} }
resp.Write(append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...)) resp.Write(util.EncodeCertsPEM(cert, caCerts))
resp.Write(keyBytes) resp.Write(keyBytes)
}) })
} }

17
pkg/util/cert.go Normal file
View File

@ -0,0 +1,17 @@
package util
import (
"crypto/x509"
certutil "github.com/rancher/dynamiclistener/cert"
)
// EncodeCertsPEM is a wrapper around the EncodeCertPEM function to return the
// PEM encoding of a cert and chain, instead of just a single cert.
func EncodeCertsPEM(cert *x509.Certificate, caCerts []*x509.Certificate) []byte {
pemBytes := certutil.EncodeCertPEM(cert)
for _, caCert := range caCerts {
pemBytes = append(pemBytes, certutil.EncodeCertPEM(caCert)...)
}
return pemBytes
}