787 lines
28 KiB
Go
787 lines
28 KiB
Go
/*
|
|
Copyright 2020 The cert-manager Authors.
|
|
|
|
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 acme
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"errors"
|
|
"math/big"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
coretesting "k8s.io/client-go/testing"
|
|
fakeclock "k8s.io/utils/clock/testing"
|
|
|
|
apiutil "github.com/cert-manager/cert-manager/pkg/api/util"
|
|
cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1"
|
|
"github.com/cert-manager/cert-manager/pkg/apis/certmanager"
|
|
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
|
v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
|
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
|
|
cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1"
|
|
"github.com/cert-manager/cert-manager/pkg/controller"
|
|
"github.com/cert-manager/cert-manager/pkg/controller/certificaterequests"
|
|
testpkg "github.com/cert-manager/cert-manager/pkg/controller/test"
|
|
"github.com/cert-manager/cert-manager/pkg/util/pki"
|
|
"github.com/cert-manager/cert-manager/test/unit/gen"
|
|
testlisters "github.com/cert-manager/cert-manager/test/unit/listers"
|
|
)
|
|
|
|
var (
|
|
fixedClockStart = time.Now()
|
|
fixedClock = fakeclock.NewFakeClock(fixedClockStart)
|
|
)
|
|
|
|
func generateCSR(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames ...string) []byte {
|
|
csr, err := gen.CSRWithSigner(secretKey,
|
|
gen.SetCSRCommonName(commonName),
|
|
gen.SetCSRDNSNames(dnsNames...),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return csr
|
|
}
|
|
|
|
func generateCSRWithIPs(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames []string, ips []string) []byte {
|
|
csr, err := gen.CSRWithSigner(secretKey,
|
|
gen.SetCSRCommonName(commonName),
|
|
gen.SetCSRDNSNames(dnsNames...),
|
|
gen.SetCSRIPAddressesFromStrings(ips...),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return csr
|
|
}
|
|
|
|
func TestSign(t *testing.T) {
|
|
metaFixedClockStart := metav1.NewTime(fixedClockStart)
|
|
baseIssuer := gen.Issuer("test-issuer",
|
|
gen.SetIssuerACME(cmacme.ACMEIssuer{}),
|
|
gen.AddIssuerCondition(cmapi.IssuerCondition{
|
|
Type: cmapi.IssuerConditionReady,
|
|
Status: cmmeta.ConditionTrue,
|
|
}),
|
|
)
|
|
|
|
rootPK, err := pki.GenerateECPrivateKey(256)
|
|
if err != nil {
|
|
t.Fatal()
|
|
}
|
|
|
|
rootTmpl := &x509.Certificate{
|
|
Version: 2,
|
|
BasicConstraintsValid: true,
|
|
SerialNumber: big.NewInt(0),
|
|
Subject: pkix.Name{
|
|
CommonName: "root",
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(time.Minute),
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
PublicKey: rootPK.Public(),
|
|
IsCA: true,
|
|
}
|
|
|
|
_, rootCert, err := pki.SignCertificate(rootTmpl, rootTmpl, rootPK.Public(), rootPK)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sk, err := pki.GenerateRSAPrivateKey(2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
csrPEM := generateCSR(t, sk, "example.com", "example.com", "foo.com")
|
|
csrPEMExampleNotPresent := generateCSR(t, sk, "example.com", "foo.com")
|
|
|
|
baseCRNotApproved := gen.CertificateRequest("test-cr",
|
|
gen.SetCertificateRequestCSR(csrPEM),
|
|
gen.SetCertificateRequestIsCA(false),
|
|
gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 60}),
|
|
gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{
|
|
Name: baseIssuer.Name,
|
|
Group: certmanager.GroupName,
|
|
Kind: "Issuer",
|
|
}),
|
|
)
|
|
baseCRDenied := gen.CertificateRequestFrom(baseCRNotApproved,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionDenied,
|
|
Status: cmmeta.ConditionTrue,
|
|
Reason: "Foo",
|
|
Message: "Certificate request has been denied by cert-manager.io",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
)
|
|
baseCR := gen.CertificateRequestFrom(baseCRNotApproved,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionApproved,
|
|
Status: cmmeta.ConditionTrue,
|
|
Reason: "cert-manager.io",
|
|
Message: "Certificate request has been approved by cert-manager.io",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
)
|
|
|
|
csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
template, err := pki.CertificateTemplateFromCertificateRequest(baseCR)
|
|
if err != nil {
|
|
t.Errorf("error generating template: %v", err)
|
|
}
|
|
|
|
certBundle, err := pki.SignCSRTemplate([]*x509.Certificate{rootCert}, rootPK, template)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Another version of Key, CSR and Cert where the only difference is the signer key value.
|
|
// For use in testing the PublicKeyMatchesCertificate check of the controller.
|
|
sk2, err := pki.GenerateRSAPrivateKey(2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
template2, err := pki.CertificateTemplateFromCSRPEM(
|
|
generateCSR(t, sk2, "example.com", "example.com", "foo.com"),
|
|
pki.CertificateTemplateOverrideDuration(time.Hour),
|
|
pki.CertificateTemplateOverrideBasicConstraints(false, nil),
|
|
pki.CertificateTemplateOverrideKeyUsages(0, nil),
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cert2Bundle, err := pki.SignCSRTemplate([]*x509.Certificate{rootCert}, rootPK, template2)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ipCSRPEM := generateCSRWithIPs(t, sk, "10.0.0.1", nil, []string{"10.0.0.1"})
|
|
ipCSR, err := pki.DecodeX509CertificateRequestBytes(ipCSRPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ipBaseCR := gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR(ipCSRPEM))
|
|
ipBaseOrder, err := buildOrder(ipBaseCR, ipCSR, baseIssuer.GetSpec().ACME.EnableDurationFeature)
|
|
if err != nil {
|
|
t.Fatalf("failed to build order during testing: %s", err)
|
|
}
|
|
|
|
baseOrder, err := buildOrder(baseCR, csr, baseIssuer.GetSpec().ACME.EnableDurationFeature)
|
|
if err != nil {
|
|
t.Fatalf("failed to build order during testing: %s", err)
|
|
}
|
|
|
|
tests := map[string]testT{
|
|
"a CertificateRequest without an approved condition should do nothing": {
|
|
certificateRequest: baseCRNotApproved.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
KubeObjects: []runtime.Object{},
|
|
CertManagerObjects: []runtime.Object{baseCRNotApproved.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
"Normal WaitingForApproval Not signing CertificateRequest until it is Approved",
|
|
},
|
|
},
|
|
},
|
|
"a CertificateRequest with a denied condition should update Ready condition with 'Denied'": {
|
|
certificateRequest: baseCRDenied.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
KubeObjects: []runtime.Object{},
|
|
CertManagerObjects: []runtime.Object{baseCRDenied.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCRDenied,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: "Denied",
|
|
Message: "The CertificateRequest was denied by an approval controller",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestFailureTime(metaFixedClockStart),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
"a badly formed CSR should report failure": {
|
|
certificateRequest: gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR([]byte("a bad csr")),
|
|
),
|
|
builder: &testpkg.Builder{
|
|
KubeObjects: []runtime.Object{},
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
"Warning RequestParsingError Failed to decode CSR in spec.request: error decoding certificate request PEM block",
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR([]byte("a bad csr")),
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonFailed,
|
|
Message: "Failed to decode CSR in spec.request: error decoding certificate request PEM block",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestFailureTime(metaFixedClockStart),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
"if the common name is not present in the DNS names then should hard fail": {
|
|
certificateRequest: gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR(csrPEMExampleNotPresent),
|
|
),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
`Warning InvalidOrder The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "example.com" does not exist in [foo.com] or []`,
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR(csrPEMExampleNotPresent),
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonFailed,
|
|
Message: `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "example.com" does not exist in [foo.com] or []`,
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestFailureTime(metaFixedClockStart),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the common name is not present in the IP Addresses then should hard fail": {
|
|
certificateRequest: gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR(generateCSR(t, sk, "10.0.0.1", "example.com")),
|
|
),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
`Warning InvalidOrder The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "10.0.0.1" does not exist in [example.com] or []`,
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestCSR(generateCSR(t, sk, "10.0.0.1", "example.com")),
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonFailed,
|
|
Message: `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "10.0.0.1" does not exist in [example.com] or []`,
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestFailureTime(metaFixedClockStart),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"pass if the CN is set in the IPs": {
|
|
certificateRequest: gen.CertificateRequestFrom(ipBaseCR,
|
|
gen.SetCertificateRequestCSR(ipCSRPEM),
|
|
),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{ipBaseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
"Normal OrderCreated Created Order resource default-unit-test-ns/test-cr-3104426127",
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewCreateAction(
|
|
cmacme.SchemeGroupVersion.WithResource("orders"),
|
|
gen.DefaultTestNamespace,
|
|
ipBaseOrder,
|
|
)),
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(ipBaseCR,
|
|
gen.SetCertificateRequestCSR(ipCSRPEM),
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonPending,
|
|
Message: "Created Order resource default-unit-test-ns/test-cr-3104426127",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
//TODO: Think of a creative way to get `buildOrder` to fail :thinking_face:
|
|
|
|
"if order doesn't exist then attempt to create one": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedEvents: []string{
|
|
"Normal OrderCreated Created Order resource default-unit-test-ns/test-cr-1733622556",
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewCreateAction(
|
|
cmacme.SchemeGroupVersion.WithResource("orders"),
|
|
gen.DefaultTestNamespace,
|
|
baseOrder,
|
|
)),
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonPending,
|
|
Message: "Created Order resource default-unit-test-ns/test-cr-1733622556",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"should exit nil and set status pending if referenced issuer is not ready": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(),
|
|
gen.Issuer(baseIssuer.DeepCopy().Name,
|
|
gen.SetIssuerACME(cmacme.ACMEIssuer{}),
|
|
)},
|
|
ExpectedEvents: []string{
|
|
"Normal IssuerNotReady Referenced issuer does not have a Ready status condition",
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: "Pending",
|
|
Message: "Referenced issuer does not have a Ready status condition",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if we fail to get the order resource due to a transient error then we should report pending and return error to re-sync": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
"Normal OrderGetError Failed to get order resource default-unit-test-ns/test-cr-1733622556: this is a network error",
|
|
},
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonPending,
|
|
Message: "Failed to get order resource default-unit-test-ns/test-cr-1733622556: this is a network error",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
fakeOrderLister: &testlisters.FakeOrderLister{
|
|
OrdersFn: func(namespace string) cmacmelisters.OrderNamespaceLister {
|
|
return &testlisters.FakeOrderNamespaceLister{
|
|
GetFn: func(name string) (ret *cmacme.Order, err error) {
|
|
return nil, errors.New("this is a network error")
|
|
},
|
|
}
|
|
},
|
|
},
|
|
expectedErr: true,
|
|
},
|
|
|
|
"if the order resource is in a failed state then we should report failure": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
`Warning OrderFailed Failed to wait for order resource "test-cr-1733622556" to become ready: order is in "invalid" state: simulated failure`,
|
|
},
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy(),
|
|
gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Invalid),
|
|
gen.SetOrderReason("simulated failure"),
|
|
),
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonFailed,
|
|
Message: `Failed to wait for order resource "test-cr-1733622556" to become ready: order is in "invalid" state: simulated failure`,
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestFailureTime(metaFixedClockStart),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the order is in an unknown state, then report pending": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
`Normal OrderPending Waiting on certificate issuance from order default-unit-test-ns/test-cr-1733622556: "pending"`,
|
|
},
|
|
CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy(),
|
|
gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Pending),
|
|
),
|
|
},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonPending,
|
|
Message: `Waiting on certificate issuance from order default-unit-test-ns/test-cr-1733622556: "pending"`,
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the order is in Valid state but Certificate has not yet been populated": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
"Normal OrderPending Waiting for order-controller to add certificate data to Order default-unit-test-ns/test-cr-1733622556",
|
|
},
|
|
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Valid),
|
|
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionFalse,
|
|
Reason: cmapi.CertificateRequestReasonPending,
|
|
Message: "Waiting for order-controller to add certificate data to Order default-unit-test-ns/test-cr-1733622556",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the order is in Valid state but the certificate is badly formed": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Valid),
|
|
gen.SetOrderCertificate([]byte("bad certificate bytes")),
|
|
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewDeleteAction(
|
|
cmacme.SchemeGroupVersion.WithResource("orders"),
|
|
gen.DefaultTestNamespace,
|
|
baseOrder.Name,
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the order is in Valid state but the certificate has wrong public key": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Valid),
|
|
gen.SetOrderCertificate(cert2Bundle.ChainPEM),
|
|
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewDeleteAction(
|
|
cmacme.SchemeGroupVersion.WithResource("orders"),
|
|
gen.DefaultTestNamespace,
|
|
baseOrder.Name,
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
|
|
"if the order is in Valid state then return the certificate as response": {
|
|
certificateRequest: baseCR.DeepCopy(),
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
"Normal CertificateIssued Certificate fetched from issuer successfully",
|
|
},
|
|
CertManagerObjects: []runtime.Object{gen.OrderFrom(baseOrder,
|
|
gen.SetOrderState(cmacme.Valid),
|
|
gen.SetOrderCertificate(certBundle.ChainPEM),
|
|
), baseCR.DeepCopy(), baseIssuer.DeepCopy()},
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
|
|
"status",
|
|
gen.DefaultTestNamespace,
|
|
gen.CertificateRequestFrom(baseCR,
|
|
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
|
|
Type: cmapi.CertificateRequestConditionReady,
|
|
Status: cmmeta.ConditionTrue,
|
|
Reason: cmapi.CertificateRequestReasonIssued,
|
|
Message: "Certificate fetched from issuer successfully",
|
|
LastTransitionTime: &metaFixedClockStart,
|
|
}),
|
|
gen.SetCertificateRequestCertificate(certBundle.ChainPEM),
|
|
),
|
|
)),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
fixedClock.SetTime(fixedClockStart)
|
|
test.builder.Clock = fixedClock
|
|
runTest(t, test)
|
|
})
|
|
}
|
|
}
|
|
|
|
type testT struct {
|
|
builder *testpkg.Builder
|
|
certificateRequest *cmapi.CertificateRequest
|
|
|
|
expectedErr bool
|
|
|
|
fakeOrderLister *testlisters.FakeOrderLister
|
|
}
|
|
|
|
func runTest(t *testing.T, test testT) {
|
|
test.builder.T = t
|
|
test.builder.Init()
|
|
defer test.builder.Stop()
|
|
|
|
ac := NewACME(test.builder.Context).(*ACME)
|
|
if test.fakeOrderLister != nil {
|
|
ac.orderLister = test.fakeOrderLister
|
|
}
|
|
|
|
controller := certificaterequests.New(
|
|
apiutil.IssuerACME,
|
|
func(*controller.Context) certificaterequests.Issuer { return ac },
|
|
)
|
|
_, _, err := controller.Register(test.builder.Context)
|
|
if err != nil {
|
|
t.Errorf("Error registering the controller: %v", err)
|
|
}
|
|
test.builder.Start()
|
|
|
|
err = controller.Sync(context.Background(), test.certificateRequest)
|
|
if err != nil && !test.expectedErr {
|
|
t.Errorf("expected to not get an error, but got: %v", err)
|
|
}
|
|
if err == nil && test.expectedErr {
|
|
t.Errorf("expected to get an error but did not get one")
|
|
}
|
|
|
|
test.builder.CheckAndFinish(err)
|
|
}
|
|
|
|
func Test_buildOrder(t *testing.T) {
|
|
sk, err := pki.GenerateRSAPrivateKey(2048)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
csrPEM := generateCSR(t, sk, "example.com", "example.com")
|
|
csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cr := gen.CertificateRequest("test", gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}), gen.SetCertificateRequestCSR(csrPEM))
|
|
type args struct {
|
|
cr *v1.CertificateRequest
|
|
csr *x509.CertificateRequest
|
|
enableDurationFeature bool
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *cmacme.Order
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "Normal building of order",
|
|
args: args{
|
|
cr: cr,
|
|
csr: csr,
|
|
enableDurationFeature: false,
|
|
},
|
|
want: &cmacme.Order{
|
|
Spec: cmacme.OrderSpec{
|
|
Request: csrPEM,
|
|
CommonName: "example.com",
|
|
DNSNames: []string{"example.com"},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Building with enableDurationFeature",
|
|
args: args{
|
|
cr: cr,
|
|
csr: csr,
|
|
enableDurationFeature: true,
|
|
},
|
|
want: &cmacme.Order{
|
|
Spec: cmacme.OrderSpec{
|
|
Request: csrPEM,
|
|
CommonName: "example.com",
|
|
DNSNames: []string{"example.com"},
|
|
Duration: &metav1.Duration{Duration: time.Hour},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := buildOrder(tt.args.cr, tt.args.csr, tt.args.enableDurationFeature)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("buildOrder() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
// for the current purpose we only test the spec
|
|
if !reflect.DeepEqual(got.Spec, tt.want.Spec) {
|
|
t.Errorf("buildOrder() got = %v, want %v", got.Spec, tt.want.Spec)
|
|
}
|
|
})
|
|
}
|
|
|
|
longCrOne := gen.CertificateRequest(
|
|
"test-comparison-that-is-at-the-fifty-two-character-l",
|
|
gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}),
|
|
gen.SetCertificateRequestCSR(csrPEM))
|
|
orderOne, err := buildOrder(longCrOne, csr, false)
|
|
if err != nil {
|
|
t.Errorf("buildOrder() received error %v", err)
|
|
return
|
|
}
|
|
|
|
t.Run("Builds two orders from different long CRs to guarantee unique name", func(t *testing.T) {
|
|
longCrTwo := gen.CertificateRequest(
|
|
"test-comparison-that-is-at-the-fifty-two-character-l-two",
|
|
gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}),
|
|
gen.SetCertificateRequestCSR(csrPEM))
|
|
|
|
orderTwo, err := buildOrder(longCrTwo, csr, false)
|
|
if err != nil {
|
|
t.Errorf("buildOrder() received error %v", err)
|
|
return
|
|
}
|
|
|
|
if orderOne.Name == orderTwo.Name {
|
|
t.Errorf(
|
|
"orders built from different CR have equal names: %s == %s",
|
|
orderOne.Name,
|
|
orderTwo.Name)
|
|
}
|
|
})
|
|
|
|
t.Run("Builds two orders from the same long CRs to guarantee same name", func(t *testing.T) {
|
|
orderOne, err := buildOrder(longCrOne, csr, false)
|
|
if err != nil {
|
|
t.Errorf("buildOrder() received error %v", err)
|
|
return
|
|
}
|
|
|
|
orderTwo, err := buildOrder(longCrOne, csr, false)
|
|
if err != nil {
|
|
t.Errorf("buildOrder() received error %v", err)
|
|
return
|
|
}
|
|
if orderOne.Name != orderTwo.Name {
|
|
t.Errorf(
|
|
"orders built from the same CR have unequal names: %s != %s",
|
|
orderOne.Name,
|
|
orderTwo.Name)
|
|
}
|
|
})
|
|
}
|