From 29bb8bf3445e0dd86e46e73de373e2aa04422bfb Mon Sep 17 00:00:00 2001 From: Jack Kleeman Date: Mon, 2 Mar 2020 19:48:38 +0000 Subject: [PATCH] Support intermediate certificate in certificate store Kubernetes-commit: 83a223f5ee3790b7ed54197986d4317e75da1652 --- util/certificate/certificate_manager_test.go | 62 ++++++++++++++++ util/certificate/certificate_store.go | 14 +++- util/certificate/certificate_store_test.go | 77 ++++++++++++++++++++ 3 files changed, 149 insertions(+), 4 deletions(-) diff --git a/util/certificate/certificate_manager_test.go b/util/certificate/certificate_manager_test.go index 6fe95130..cdc6e4ec 100644 --- a/util/certificate/certificate_manager_test.go +++ b/util/certificate/certificate_manager_test.go @@ -60,6 +60,68 @@ iQIgZX08DA8VfvcA5/Xj1Zjdey9FVY6POLXen6RPiabE97UCICp6eUW7ht+2jjar e35EltCRCjoejRHTuN9TC0uCoVipAiAXaJIx/Q47vGwiw6Y8KXsNU6y54gTbOSxX 54LzHNk/+Q== -----END RSA PRIVATE KEY-----`) +var storeTwoCertsData = newCertificateData(`-----BEGIN CERTIFICATE----- +MIIDfTCCAyegAwIBAgIUFBl4gUoqZDP/wUJDn37/VJ9upD0wDQYJKoZIhvcNAQEF +BQAwfjELMAkGA1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9u +ZG9uMRgwFgYDVQQKDA9HbG9iYWwgU2VjdXJpdHkxFjAUBgNVBAsMDUlUIERlcGFy +dG1lbnQxGzAZBgNVBAMMEnRlc3QtY2VydGlmaWNhdGUtMDAeFw0yMDAzMDIxOTM3 +MDBaFw0yMTAzMDIxOTM3MDBaMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs +aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEdMBsGA1UEChMURXhhbXBs +ZSBDb21wYW55LCBMTEMxEzARBgNVBAsTCk9wZXJhdGlvbnMxGDAWBgNVBAMTD3d3 +dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMiR +DNpmwTICFr+P16fKDVjbNCzSjWq+MTu8vAfS6GrLpBTUEe+6zVqxUza/fZenxo8O +ucV2JTUv5J4nkT/vG6Qm/mToVJ4vQzLQ5jR2w7v/7cf3oWCwTAKUafgo6/Ga95gn +lQB3+Fd8sy96zfFr/7wDSMPPueR5kSFax+cEd30wwv5O7tWj0ro1mrxLssBlwPaR +ZlzkkvxBYTzWCqKZsWktQlXciqlFSos0ua7uvwqKN5CTxfC/xoyMxx9kfZm7BzPN +ZDqYMFw2HiWdEiLzI4jj+Gh0D5t47tnvlpUMihcX9x0jP6/+hnfcQ8GAP2jR/BXY +5YZRRY70LiCXPevlRAECAwEAAaOBqTCBpjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE +FOoiE+kh7gGDpyx0KZuCc1lrlTRKMB8GA1UdIwQYMBaAFNtosvGlpDUsb9JwcRcX +q37L52VTMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20w +DQYJKoZIhvcNAQEFBQADQQAw6mxQONAD2sivfzIf1eDFd6LU7aE+MnkdlEQjjPCi +tlUITFIuO3XavISupP6V9wE0b1wTF1pTlVWArf/0YQXs +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICRzCCAfGgAwIBAgIJALMb7ecMIk3MMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEYMBYGA1UE +CgwPR2xvYmFsIFNlY3VyaXR5MRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRswGQYD +VQQDDBJ0ZXN0LWNlcnRpZmljYXRlLTAwIBcNMTcwNDI2MjMyNjUyWhgPMjExNzA0 +MDIyMzI2NTJaMH4xCzAJBgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNV +BAcMBkxvbmRvbjEYMBYGA1UECgwPR2xvYmFsIFNlY3VyaXR5MRYwFAYDVQQLDA1J +VCBEZXBhcnRtZW50MRswGQYDVQQDDBJ0ZXN0LWNlcnRpZmljYXRlLTAwXDANBgkq +hkiG9w0BAQEFAANLADBIAkEAtBMa7NWpv3BVlKTCPGO/LEsguKqWHBtKzweMY2CV +tAL1rQm913huhxF9w+ai76KQ3MHK5IVnLJjYYA5MzP2H5QIDAQABo1AwTjAdBgNV +HQ4EFgQU22iy8aWkNSxv0nBxFxerfsvnZVMwHwYDVR0jBBgwFoAU22iy8aWkNSxv +0nBxFxerfsvnZVMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAANBAEOefGbV +NcHxklaW06w6OBYJPwpIhCVozC1qdxGX1dg8VkEKzjOzjgqVD30m59OFmSlBmHsl +nkVA6wyOSDYBf3o= +-----END CERTIFICATE-----`, `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyJEM2mbBMgIWv4/Xp8oNWNs0LNKNar4xO7y8B9LoasukFNQR +77rNWrFTNr99l6fGjw65xXYlNS/knieRP+8bpCb+ZOhUni9DMtDmNHbDu//tx/eh +YLBMApRp+Cjr8Zr3mCeVAHf4V3yzL3rN8Wv/vANIw8+55HmRIVrH5wR3fTDC/k7u +1aPSujWavEuywGXA9pFmXOSS/EFhPNYKopmxaS1CVdyKqUVKizS5ru6/Coo3kJPF +8L/GjIzHH2R9mbsHM81kOpgwXDYeJZ0SIvMjiOP4aHQPm3ju2e+WlQyKFxf3HSM/ +r/6Gd9xDwYA/aNH8FdjlhlFFjvQuIJc96+VEAQIDAQABAoIBAQCc6R3tH8a1oPy7 +EYXeNy0J/zRqfK82e2V5HsbcOByssHTF9sOxkatm8KPxiQ5wv0mQUiz0VuH1Imrx +cHMqWZ5+ZiNQPpM0zjT8ZII1OVUYl7knYIxYYJSW0BW3mAw/EMXzu8POgg1AJMbq +tmC4J44DQW6EAtej75ejSKpsCgqRXVoi3iEk9eMLHUFIHqkzl/aKEc7k/P+eKo2h +PHsDoKZdmOmZA3OKzw61xAqJICYyplRHatQcEiWJgnLer+9qvUGc4k8eqAYeDGm7 +T78XcUvsXOug2GClVWGZu1quFhf7MxjzFfOjz4q9HwPex7X6nQL0IX2hzMECkaMC +iUMZGGEhAoGBAOLY1KSNOjvt54MkKznI8stHkx8V73c0Nxbz5Rj8gM0Gwk1FWVas +jgoAbKPQ2UL/RglLX1JZvztKvNuWSEeZGqggDvhzB38leiEH+OY7DZ7a0c5sWwdF +CpcT1mJb91ww5xEC09WO8Oq3i5olVBBivOl5EjwKHOQn2TUh2OSLhqf/AoGBAOJX +mxqdTEUwFU9ecsAOK9labjI7mA5so0vIq8eq1Q670NFszChfSMKJAqQ90N1LEu9z +L0f6CBXYCn7sMmOlF4CKE+u2/ieJfD1OkKq7RwEd3pi4X3xtAlcPK8F/QprmQWo0 +wi33BDBb4zYkuQB6Q5RYIV2di7k+HBpoQPottBP/AoGAIB4xJUc1qoyJjeDOGfVg +ovV0WB9j8026Sw6nLj16Aw1k70nVV1dBGRtsRllomXrJMMGyMleworV3PePuQezk +gE9hrz2iHxdwTkLxs69Cw24Z7I8c6E+XK0LMxMpeoHfwD1GGKqN9as4n/uAwIc3J +D4lr0oJgCtG1iDdNnTZAD4MCgYAkOpWPCwJ8SJgAnkOLzjjij4D39WX/WRBCPxqP +2R5FP3bLLrj29Vl2GewcUfCumyeqwCsfQDwvEueLLU9bd79tSayqnB3OQklqnrq1 +OUjCOv+4Pjq6ddBcEweT70S/+n8Z+tvh85nuC6cwsWwTUX6jrf+ZNnB49CIXb/yG +ju42DQKBgAPtbB/ON3+GtnSTHBSY6HwZvGJrBDicrXmr1U9zuA8yYxv8qaRXZkpn +2cpLLvO2MJutwXMYf+T3x1ZCFMkE56pOswSTGrCQWRl3hOiJayLHQyAOYHPnYeZB +78iRJPUZ0biEQUZQ62GBxWkcB0qkxa9m759h/TvLwvV0RrO5Uzd0 +-----END RSA PRIVATE KEY-----`) var expiredStoreCertData = newCertificateData(`-----BEGIN CERTIFICATE----- MIIBFzCBwgIJALhygXnxXmN1MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCGhv c3QtMTIzMB4XDTE4MTEwNDIzNTc1NFoXDTE4MTEwNTIzNTc1NFowEzERMA8GA1UE diff --git a/util/certificate/certificate_store.go b/util/certificate/certificate_store.go index 9b008084..7b73c0a6 100644 --- a/util/certificate/certificate_store.go +++ b/util/certificate/certificate_store.go @@ -25,6 +25,7 @@ import ( "path/filepath" "time" + certutil "k8s.io/client-go/util/cert" "k8s.io/klog" ) @@ -197,11 +198,16 @@ func (s *fileStore) Update(certData, keyData []byte) (*tls.Certificate, error) { return nil, fmt.Errorf("could not open %q: %v", certPath, err) } defer f.Close() - certBlock, _ := pem.Decode(certData) - if certBlock == nil { - return nil, fmt.Errorf("invalid certificate data") + + // First cert is leaf, remainder are intermediates + certs, err := certutil.ParseCertsPEM(certData) + if err != nil { + return nil, fmt.Errorf("invalid certificate data: %v", err) } - pem.Encode(f, certBlock) + for _, c := range certs { + pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}) + } + keyBlock, _ := pem.Decode(keyData) if keyBlock == nil { return nil, fmt.Errorf("invalid key data") diff --git a/util/certificate/certificate_store_test.go b/util/certificate/certificate_store_test.go index f18a6da5..4bb9e1a2 100644 --- a/util/certificate/certificate_store_test.go +++ b/util/certificate/certificate_store_test.go @@ -284,6 +284,43 @@ func TestUpdateRotation(t *testing.T) { } } +func TestUpdateTwoCerts(t *testing.T) { + prefix := "kubelet-server" + dir, err := ioutil.TempDir("", "k8s-test-certstore-current") + if err != nil { + t.Fatalf("Unable to create the test directory %q: %v", dir, err) + } + defer func() { + if err := os.RemoveAll(dir); err != nil { + t.Errorf("Unable to clean up test directory %q: %v", dir, err) + } + }() + keyFile := filepath.Join(dir, "kubelet.key") + if err := ioutil.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil { + t.Fatalf("Unable to create the file %q: %v", keyFile, err) + } + certFile := filepath.Join(dir, "kubelet.crt") + if err := ioutil.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil { + t.Fatalf("Unable to create the file %q: %v", certFile, err) + } + + s, err := NewFileStore(prefix, dir, dir, certFile, keyFile) + if err != nil { + t.Fatalf("Got %v while creating a new store.", err) + } + + cert, err := s.Update(storeTwoCertsData.certificatePEM, storeTwoCertsData.keyPEM) + if err != nil { + t.Errorf("Got %v while updating certificate store.", err) + } + if cert == nil { + t.Errorf("Got nil certificate, expected something real.") + } + if len(cert.Certificate) != 2 { + t.Fatalf("Unexpected number of certificates, expected 2, got %v", len(cert.Certificate)) + } +} + func TestUpdateWithBadCertKeyData(t *testing.T) { prefix := "kubelet-server" dir, err := ioutil.TempDir("", "k8s-test-certstore-current") @@ -394,6 +431,46 @@ func TestCurrentCertKeyFiles(t *testing.T) { } } +func TestCurrentTwoCerts(t *testing.T) { + prefix := "kubelet-server" + dir, err := ioutil.TempDir("", "k8s-test-certstore-current") + if err != nil { + t.Fatalf("Unable to create the test directory %q: %v", dir, err) + } + defer func() { + if err := os.RemoveAll(dir); err != nil { + t.Errorf("Unable to clean up test directory %q: %v", dir, err) + } + }() + certFile := filepath.Join(dir, "kubelet.crt") + if err := ioutil.WriteFile(certFile, storeTwoCertsData.certificatePEM, 0600); err != nil { + t.Fatalf("Unable to create the file %q: %v", certFile, err) + } + keyFile := filepath.Join(dir, "kubelet.key") + if err := ioutil.WriteFile(keyFile, storeTwoCertsData.keyPEM, 0600); err != nil { + t.Fatalf("Unable to create the file %q: %v", keyFile, err) + } + + store, err := NewFileStore(prefix, dir, dir, certFile, keyFile) + if err != nil { + t.Fatalf("Failed to initialize certificate store: %v", err) + } + + cert, err := store.Current() + if err != nil { + t.Fatalf("Could not load certificate from disk: %v", err) + } + if cert == nil { + t.Fatalf("There was no error, but no certificate data was returned.") + } + if cert.Leaf == nil { + t.Fatalf("Got an empty leaf, expected private data.") + } + if len(cert.Certificate) != 2 { + t.Fatalf("Unexpected number of certificates, expected 2, got %v", len(cert.Certificate)) + } +} + func TestCurrentNoFiles(t *testing.T) { dir, err := ioutil.TempDir("", "k8s-test-certstore-current") if err != nil {