From 38716d69aef5ec3d41fef06df7310d430b2e9af1 Mon Sep 17 00:00:00 2001 From: JoshVanL Date: Mon, 13 Apr 2020 17:28:34 +0100 Subject: [PATCH] Adds issuing controller issuing integration test Signed-off-by: JoshVanL --- test/integration/certificates/BUILD.bazel | 9 +- .../certificates/issuing_controller_test.go | 220 ++++++++++++++++++ test/unit/gen/certificaterequest.go | 6 + 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 test/integration/certificates/issuing_controller_test.go diff --git a/test/integration/certificates/BUILD.bazel b/test/integration/certificates/BUILD.bazel index 917e1863f..9e1be6c22 100644 --- a/test/integration/certificates/BUILD.bazel +++ b/test/integration/certificates/BUILD.bazel @@ -2,15 +2,22 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") go_test( name = "go_default_test", - srcs = ["trigger_controller_test.go"], + srcs = [ + "issuing_controller_test.go", + "trigger_controller_test.go", + ], deps = [ "//pkg/api/util:go_default_library", "//pkg/apis/certmanager/v1alpha2:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//pkg/controller:go_default_library", + "//pkg/controller/expcertificates/issuing:go_default_library", "//pkg/controller/expcertificates/trigger:go_default_library", "//pkg/logs:go_default_library", + "//pkg/util/pki:go_default_library", "//test/integration/framework:go_default_library", + "//test/unit/gen:go_default_library", + "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_apimachinery//pkg/util/wait:go_default_library", "@io_k8s_utils//clock:go_default_library", diff --git a/test/integration/certificates/issuing_controller_test.go b/test/integration/certificates/issuing_controller_test.go new file mode 100644 index 000000000..80b013f6c --- /dev/null +++ b/test/integration/certificates/issuing_controller_test.go @@ -0,0 +1,220 @@ +/* +Copyright 2020 The Jetstack cert-manager contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificates + +import ( + "bytes" + "context" + "encoding/pem" + "fmt" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/clock" + + apiutil "github.com/jetstack/cert-manager/pkg/api/util" + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" + controllerpkg "github.com/jetstack/cert-manager/pkg/controller" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/issuing" + logf "github.com/jetstack/cert-manager/pkg/logs" + utilpki "github.com/jetstack/cert-manager/pkg/util/pki" + "github.com/jetstack/cert-manager/test/integration/framework" + "github.com/jetstack/cert-manager/test/unit/gen" +) + +// TestIssuingController performs a basic test to ensure that the issuing +// controller works when instantiated. +// This is not an exhaustive set of test cases. It only ensures that the signed +// certificate, ca, and private key is stored into the target Secret to +// complete Issuing the Certificate. +func TestIssuingController(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(), + 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) + } + + skBytes := utilpki.EncodePKCS1PrivateKey(sk) + + // 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{ + corev1.TLSPrivateKeyKey: skBytes, + }, + }, 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.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], skBytes) || + !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) + } +} diff --git a/test/unit/gen/certificaterequest.go b/test/unit/gen/certificaterequest.go index 3ebc8d4b0..db8e3d17c 100644 --- a/test/unit/gen/certificaterequest.go +++ b/test/unit/gen/certificaterequest.go @@ -128,6 +128,12 @@ func AddCertificateRequestAnnotations(annotations map[string]string) Certificate } } +func AddCertificateRequestOwnerReferences(owners ...metav1.OwnerReference) CertificateRequestModifier { + return func(cr *v1alpha2.CertificateRequest) { + cr.OwnerReferences = append(cr.OwnerReferences, owners...) + } +} + func SetCertificateRequestAnnotations(annotations map[string]string) CertificateRequestModifier { return func(cr *v1alpha2.CertificateRequest) { cr.SetAnnotations(annotations)