kubeadm: check all available CA certs against pinned certs

Currently kubeadm produces an error upon parsing multiple
certificates stored in the cluster-info configmap. Yet it
should check all available certificates in a scenario like
CA key rotation.

Check all available CA certs against pinned certificate hashes.

Fixes https://github.com/kubernetes/kubeadm/issues/1399
This commit is contained in:
Dmitry Rozhkov
2019-04-11 16:32:50 +03:00
parent c09cfb7178
commit 7f8fc5d189
4 changed files with 44 additions and 24 deletions

View File

@@ -119,14 +119,14 @@ func RetrieveValidatedConfigInfo(cfg *kubeadmapi.JoinConfiguration) (*clientcmda
for _, cluster := range insecureConfig.Clusters {
clusterCABytes = cluster.CertificateAuthorityData
}
clusterCA, err := parsePEMCert(clusterCABytes)
clusterCAs, err := parsePEMCerts(clusterCABytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse cluster CA from the %s configmap", bootstrapapi.ConfigMapClusterInfo)
}
// Validate the cluster CA public key against the pinned set
err = pubKeyPins.Check(clusterCA)
err = pubKeyPins.CheckAny(clusterCAs)
if err != nil {
return nil, errors.Wrapf(err, "cluster CA found in %s configmap is invalid", bootstrapapi.ConfigMapClusterInfo)
}
@@ -226,14 +226,27 @@ func fetchKubeConfigWithTimeout(apiEndpoint string, discoveryTimeout time.Durati
}
}
// parsePEMCert decodes a PEM-formatted certificate and returns it as an x509.Certificate
func parsePEMCert(certData []byte) (*x509.Certificate, error) {
pemBlock, trailingData := pem.Decode(certData)
if pemBlock == nil {
return nil, errors.New("invalid PEM data")
// parsePEMCerts decodes PEM-formatted certificates into a slice of x509.Certificates
func parsePEMCerts(certData []byte) ([]*x509.Certificate, error) {
var certificates []*x509.Certificate
var pemBlock *pem.Block
for {
pemBlock, certData = pem.Decode(certData)
if pemBlock == nil {
return nil, errors.New("invalid PEM data")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
return nil, errors.Wrap(err, "unable to parse certificate")
}
certificates = append(certificates, cert)
if len(certData) == 0 {
break
}
}
if len(trailingData) != 0 {
return nil, errors.New("trailing data after first PEM block")
}
return x509.ParseCertificate(pemBlock.Bytes)
return certificates, nil
}

View File

@@ -102,23 +102,24 @@ func TestParsePEMCert(t *testing.T) {
}{
{"invalid certificate data", []byte{0}, false},
{"certificate with junk appended", []byte(testCertPEM + "\nABC"), false},
{"multiple certificates", []byte(testCertPEM + "\n" + testCertPEM), false},
{"multiple certificates", []byte(testCertPEM + "\n" + testCertPEM), true},
{"valid", []byte(testCertPEM), true},
{"empty input", []byte{}, false},
} {
cert, err := parsePEMCert(testCase.input)
certs, err := parsePEMCerts(testCase.input)
if testCase.expectValid {
if err != nil {
t.Errorf("failed TestParsePEMCert(%s): unexpected error %v", testCase.name, err)
}
if cert == nil {
if certs == nil {
t.Errorf("failed TestParsePEMCert(%s): returned nil", testCase.name)
}
} else {
if err == nil {
t.Errorf("failed TestParsePEMCert(%s): expected an error", testCase.name)
}
if cert != nil {
t.Errorf("failed TestParsePEMCert(%s): expected not to get a certificate back, but got one", testCase.name)
if certs != nil {
t.Errorf("failed TestParsePEMCert(%s): expected not to get a certificate back, but got some", testCase.name)
}
}
}