From 7e87db23e74e518d462aef06e3b4551708807fdc Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Tue, 23 Jun 2020 17:12:19 +0100 Subject: [PATCH] Add integration test for PKCS#8 keyFormat Signed-off-by: James Munnelly --- .../certificates/issuing_controller_test.go | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/test/integration/certificates/issuing_controller_test.go b/test/integration/certificates/issuing_controller_test.go index 607c4ae43..435898c4d 100644 --- a/test/integration/certificates/issuing_controller_test.go +++ b/test/integration/certificates/issuing_controller_test.go @@ -86,6 +86,7 @@ func TestIssuingController(t *testing.T) { t.Fatal(err) } + // Encode the private key as PKCS#1, the default format skBytes := utilpki.EncodePKCS1PrivateKey(sk) // Store new private key in secret @@ -221,3 +222,187 @@ func TestIssuingController(t *testing.T) { t.Fatalf("Failed to wait for final state: %+v", crt) } } + +func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { + config, stopFn := framework.RunControlPlane(t) + defer stopFn() + + // Build, instantiate and run the issuing controller. + kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + controllerOptions := controllerpkg.CertificateOptions{ + EnableOwnerRef: true, + } + + ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions) + c := controllerpkg.NewController( + context.Background(), + "issuing_test", + metrics.New(logf.Log), + ctrl.ProcessItem, + mustSync, + nil, + queue, + ) + stopController := framework.StartInformersAndController(t, factory, cmFactory, c) + defer stopController() + + ctx, cancel := context.WithTimeout(context.TODO(), time.Second*20) + defer cancel() + + var ( + crtName = "testcrt" + revision = 1 + namespace = "testns" + nextPrivateKeySecretName = "next-private-key-test-crt" + secretName = "test-crt-tls" + ) + + // Create a new private key + sk, err := utilpki.GenerateRSAPrivateKey(2048) + if err != nil { + t.Fatal(err) + } + + // Encode the private key as PKCS#1, the default format + skBytesPKCS1 := utilpki.EncodePKCS1PrivateKey(sk) + skBytesPKCS8, err := utilpki.EncodePKCS8PrivateKey(sk) + if err != nil { + t.Fatal(err) + } + + // Store new private key in secret + _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + // store PKCS#1 bytes so we can ensure they are correctly converted to + // PKCS#8 later on + corev1.TLSPrivateKeyKey: skBytesPKCS1, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // Create Certificate + crt := gen.Certificate(crtName, + gen.SetCertificateNamespace(namespace), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateKeyAlgorithm(cmapi.RSAKeyAlgorithm), + gen.SetCertificateKeyEncoding(cmapi.PKCS8), + gen.SetCertificateKeySize(2048), + gen.SetCertificateSecretName(secretName), + gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer"}), + ) + + crt, err = cmCl.CertmanagerV1alpha2().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // Create x509 CSR from Certificate + csr, err := utilpki.GenerateCSR(crt) + if err != nil { + t.Fatal(err) + } + + // Encode CSR + csrDER, err := utilpki.EncodeCSR(csr, sk) + if err != nil { + t.Fatal(err) + } + + csrPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", Bytes: csrDER, + }) + + // Sign Certificate + certTemplate, err := utilpki.GenerateTemplate(crt) + if err != nil { + t.Fatal(err) + } + + // Sign and encode the certificate + certPem, _, err := utilpki.SignCertificate(certTemplate, certTemplate, sk.Public(), sk) + if err != nil { + t.Fatal(err) + } + + // Create CertificateRequest + req := gen.CertificateRequest(crtName, + gen.SetCertificateRequestNamespace(namespace), + gen.SetCertificateRequestCSR(csrPEM), + gen.SetCertificateRequestIssuer(crt.Spec.IssuerRef), + gen.SetCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: fmt.Sprintf("%d", revision+1), + }), + gen.AddCertificateRequestOwnerReferences(*metav1.NewControllerRef( + crt, + cmapi.SchemeGroupVersion.WithKind("Certificate"), + )), + ) + req, err = cmCl.CertmanagerV1alpha2().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + // Set CertificateRequest as ready + req.Status.CA = certPem + req.Status.Certificate = certPem + apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "") + req, err = cmCl.CertmanagerV1alpha2().CertificateRequests(namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + if err != nil { + t.Fatal(err) + } + + // Add Issuing condition to Certificate + apiutil.SetCertificateCondition(crt, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "", "") + crt.Status.NextPrivateKeySecretName = &nextPrivateKeySecretName + crt.Status.Revision = &revision + crt, err = cmCl.CertmanagerV1alpha2().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + if err != nil { + t.Fatal(err) + } + + // Wait for the Certificate to have the 'Issuing' condition removed, and for + // the signed certificate, ca, and private key stored in the Secret. + err = wait.Poll(time.Millisecond*100, time.Second*5, func() (done bool, err error) { + crt, err = cmCl.CertmanagerV1alpha2().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) + if err != nil { + t.Logf("Failed to fetch Certificate resource, retrying: %v", err) + return false, nil + } + + if cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing); cond != nil { + t.Logf("Certificate does not have expected condition, got=%#v", cond) + return false, nil + } + + if crt.Status.Revision == nil || + *crt.Status.Revision != 2 { + t.Logf("Certificate does not have a revision of 2: %v", crt.Status.Revision) + return false, nil + } + + secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, crt.Spec.SecretName, metav1.GetOptions{}) + if err != nil { + t.Logf("Failed to fetch Secret %s/%s: %s", namespace, crt.Spec.SecretName, err) + return false, nil + } + + if !bytes.Equal(secret.Data[corev1.TLSPrivateKeyKey], skBytesPKCS8) || + !bytes.Equal(secret.Data[corev1.TLSCertKey], certPem) || + !bytes.Equal(secret.Data[cmmeta.TLSCAKey], certPem) { + t.Logf("Contents of secret did not match expected: %+v", secret.Data) + return false, nil + } + + return true, nil + }) + + if err != nil { + t.Fatalf("Failed to wait for final state: %+v", crt) + } +}