From e62bfaf367726b567b153a6fb1272fce3c54c8c7 Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Thu, 4 Aug 2022 12:21:12 +0100 Subject: [PATCH] Add test to check InvalidRequest handling for certificates Signed-off-by: James Munnelly --- ...erates_new_private_key_per_request_test.go | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/test/integration/certificates/generates_new_private_key_per_request_test.go b/test/integration/certificates/generates_new_private_key_per_request_test.go index 5c13b4c9859..b2790b4f4a6 100644 --- a/test/integration/certificates/generates_new_private_key_per_request_test.go +++ b/test/integration/certificates/generates_new_private_key_per_request_test.go @@ -47,6 +47,136 @@ import ( "github.com/cert-manager/cert-manager/test/integration/framework" ) +func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { + namespace := "default" + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + + config, stopFn := framework.RunControlPlane(t, ctx) + defer stopFn() + + // Build, instantiate and run all required controllers + stopControllers := runAllControllers(t, ctx, config) + defer stopControllers() + + _, _, cmCl, _ := framework.NewClients(t, config) + crt, err := cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Name: "testcrt"}, + Spec: cmapi.CertificateSpec{ + SecretName: "testsecret", + DNSNames: []string{"something"}, + IssuerRef: cmmeta.ObjectReference{ + Name: "issuer", + }, + PrivateKey: &cmapi.CertificatePrivateKey{ + // This doesn't actually make any difference in this test case because there is no existing private + // key, meaning there's no private key to re-use. + RotationPolicy: cmapi.RotationPolicyAlways, + }, + }, + }, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("failed to create certificate: %v", err) + } + + var req *cmapi.CertificateRequest + if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return false, err + } + + if len(reqs.Items) > 1 { + return false, fmt.Errorf("invalid state, expected only one CR but got %d", len(reqs.Items)) + } + + if len(reqs.Items) == 0 { + return false, nil + } + + req = &reqs.Items[0] + return true, nil + }); err != nil { + t.Fatal(err) + } + + t.Logf("Found CertificateRequest") + // Remember the CSR data used for the first request so we can compare it later + originalCSR := req.Spec.Request + + // Mark the CSR as 'Failed' + apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionInvalidRequest, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonFailed, "manually failed") + _, err = cmCl.CertmanagerV1().CertificateRequests(req.Namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf("failed to mark CertificateRequest as Failed: %v", err) + } + t.Log("Marked CertificateRequest as InvalidRequest") + + // Wait for Certificate to be marked as Failed + if err := wait.Poll(time.Millisecond*500, time.Second*50, func() (done bool, err error) { + crt, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + + return apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionReady).Status == cmmeta.ConditionFalse && + apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionIssuing).Status == cmmeta.ConditionFalse, nil + }); err != nil { + t.Fatal(err) + } + t.Logf("Issuance acknowledged as failed as expected") + t.Logf("Triggering new issuance") + + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("failed to get certificate: %v", err) + } + + apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManualTrigger", "triggered by test case manually") + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf("failed to update certificate: %v", err) + } + + if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return false, err + } + + if len(reqs.Items) > 1 { + return false, fmt.Errorf("invalid state, expected only one CR but got %d", len(reqs.Items)) + } + + if len(reqs.Items) == 0 { + return false, nil + } + + req = &reqs.Items[0] + return true, nil + }); err != nil { + t.Fatal(err) + } + t.Logf("Second request created successfully") + t.Logf("Comparing public keys of first and second request...") + + csr1, err := pki.DecodeX509CertificateRequestBytes(originalCSR) + if err != nil { + t.Fatalf("failed to parse first CSR: %v", err) + } + csr2, err := pki.DecodeX509CertificateRequestBytes(req.Spec.Request) + if err != nil { + t.Fatalf("failed to parse first CSR: %v", err) + } + + pk1 := csr1.PublicKey.(crypto.PublicKey) + pk2 := csr2.PublicKey.(crypto.PublicKey) + + if pk1.(comparablePublicKey).Equal(pk2) { + t.Errorf("expected the two requests to have been signed by distinct private keys, but the private key has been reused") + } +} + // Runs all Certificate controllers to exercise the full flow of attempting issuance. // Checks to make sure that when an issuance fails and is re-attempted, a new private key is used // to sign the second request.