cert-manager/pkg/controller/certificaterequests/sync_test.go
JoshVanL 6b1ff0148b Adds certificate request base controller and CA issuer controller
instance

Signed-off-by: JoshVanL <vleeuwenjoshua@gmail.com>
2019-07-03 15:42:03 +01:00

312 lines
9.4 KiB
Go

/*
Copyright 2019 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 certificaterequests
import (
"bytes"
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"math/big"
"net"
"net/url"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
coretesting "k8s.io/client-go/testing"
clock "k8s.io/utils/clock/testing"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
"github.com/jetstack/cert-manager/pkg/issuer"
"github.com/jetstack/cert-manager/pkg/issuer/fake"
_ "github.com/jetstack/cert-manager/pkg/issuer/selfsigned"
"github.com/jetstack/cert-manager/pkg/util/pki"
"github.com/jetstack/cert-manager/test/unit/gen"
)
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
func generateCSR(commonName string) ([]byte, error) {
csr := &x509.CertificateRequest{
Version: 3,
SignatureAlgorithm: x509.SHA256WithRSA,
PublicKeyAlgorithm: x509.RSA,
Subject: pkix.Name{
Organization: []string{"my-org"},
CommonName: commonName,
},
URIs: []*url.URL{
{
Scheme: "http",
Host: "example.com",
},
},
IPAddresses: []net.IP{
net.IPv4(8, 8, 8, 8),
},
}
sk, err := pki.GenerateRSAPrivateKey(2048)
if err != nil {
return nil, err
}
csrBytes, err := pki.EncodeCSR(csr, sk)
if err != nil {
return nil, err
}
csrPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST", Bytes: csrBytes,
})
return csrPEM, nil
}
func generatePrivateKey(t *testing.T) *rsa.PrivateKey {
pk, err := pki.GenerateRSAPrivateKey(2048)
if err != nil {
t.Errorf("failed to generate private key: %v", err)
t.FailNow()
}
return pk
}
func generateSelfSignedCert(t *testing.T, cr *cmapi.CertificateRequest, sn *big.Int, key crypto.Signer, notBefore, notAfter time.Time) []byte {
template, err := pki.GenerateTemplateFromCertificateRequest(cr)
if err != nil {
t.Errorf("failed to generate cert template from CSR: %v", err)
t.FailNow()
}
template.NotAfter = notAfter
template.NotBefore = notBefore
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
if err != nil {
t.Errorf("error signing cert: %v", err)
t.FailNow()
}
pemByteBuffer := bytes.NewBuffer([]byte{})
err = pem.Encode(pemByteBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
t.Errorf("failed to encode cert: %v", err)
t.FailNow()
}
return pemByteBuffer.Bytes()
}
func TestSync(t *testing.T) {
nowTime := time.Now()
nowMetaTime := metav1.NewTime(nowTime)
fixedClock := clock.NewFakeClock(nowTime)
csr1, err := generateCSR("csr1")
if err != nil {
t.Errorf("failed to generate CSR for testing: %s", err)
t.FailNow()
}
csr2, err := generateCSR("csr2")
if err != nil {
t.Errorf("failed to generate CSR for testing: %s", err)
t.FailNow()
}
pk := generatePrivateKey(t)
exampleCR := gen.CertificateRequest("test",
gen.SetCertificateRequestIsCA(false),
gen.SetCertificateRequestIssuer(cmapi.ObjectReference{Name: "test"}),
gen.SetCertificateRequestCSR(csr1),
)
exampleCRNotFoundCondition := gen.CertificateRequestFrom(exampleCR,
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
Type: cmapi.CertificateRequestConditionReady,
Status: cmapi.ConditionFalse,
Reason: "NotExists",
Message: "Certificate does not exist",
LastTransitionTime: &nowMetaTime,
}),
)
cert1PEM := generateSelfSignedCert(t, exampleCR, nil, pk, nowTime, nowTime.Add(time.Hour*12))
certPEMExpired := generateSelfSignedCert(t, exampleCR, nil, pk, nowTime.Add(-time.Hour*13), nowTime.Add(-time.Hour*12))
exampleSignedCR := exampleCR.DeepCopy()
exampleSignedCR.Status.Certificate = cert1PEM
exampleSignedExpiredCR := exampleCR.DeepCopy()
exampleSignedExpiredCR.Status.Certificate = certPEMExpired
exampleCRReadyCondition := gen.CertificateRequestFrom(exampleSignedCR,
gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
Type: cmapi.CertificateRequestConditionReady,
Status: cmapi.ConditionTrue,
Reason: "Ready",
Message: "Certificate exists and is signed",
LastTransitionTime: &nowMetaTime,
}),
)
exampleCRExpiredReadyCondition := exampleSignedExpiredCR
exampleCRExpiredReadyCondition.Status.Conditions = exampleCRReadyCondition.Status.Conditions
exampleSignedNotMatchCR := exampleSignedCR.DeepCopy()
exampleSignedNotMatchCR.Spec.CSRPEM = csr2
exampleGarbageCertCR := exampleSignedCR.DeepCopy()
exampleGarbageCertCR.Status.Certificate = []byte("not a certificate")
tests := map[string]controllerFixture{
"should update certificate request with NotExists if issuer does not return a response": {
Issuer: gen.Issuer("test",
gen.AddIssuerCondition(cmapi.IssuerCondition{
Type: cmapi.IssuerConditionReady,
Status: cmapi.ConditionTrue,
}),
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
),
CertificateRequest: *exampleCR,
IssuerImpl: &fake.Issuer{
FakeSign: func(context.Context, *cmapi.CertificateRequest) (*issuer.IssueResponse, error) {
// By not returning a response, we trigger a 'no-op' action which
// causes the certificate request controller to update the status of
// the CertificateRequest with !Ready - NotExists.
return nil, nil
},
},
Builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{gen.CertificateRequest("test")},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateAction(
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
gen.DefaultTestNamespace,
exampleCRNotFoundCondition,
)),
},
},
CheckFn: func(t *testing.T, s *controllerFixture, args ...interface{}) {
},
Err: false,
},
"should update the status with a freshly signed certificate only when one doesn't exist": {
Issuer: gen.Issuer("test",
gen.AddIssuerCondition(cmapi.IssuerCondition{
Type: cmapi.IssuerConditionReady,
Status: cmapi.ConditionTrue,
}),
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
),
CertificateRequest: *exampleCR,
IssuerImpl: &fake.Issuer{
FakeSign: func(context.Context, *cmapi.CertificateRequest) (*issuer.IssueResponse, error) {
return &issuer.IssueResponse{
Certificate: cert1PEM,
}, nil
},
},
Builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{gen.CertificateRequest("test")},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateAction(
cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
gen.DefaultTestNamespace,
exampleCRReadyCondition,
)),
},
},
CheckFn: func(t *testing.T, s *controllerFixture, args ...interface{}) {
},
Err: false,
},
"should not update certificate request if certificate exists, even if out of date": {
Issuer: gen.Issuer("test",
gen.AddIssuerCondition(cmapi.IssuerCondition{
Type: cmapi.IssuerConditionReady,
Status: cmapi.ConditionTrue,
}),
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
),
CertificateRequest: *exampleSignedExpiredCR,
IssuerImpl: &fake.Issuer{
FakeSign: func(context.Context, *cmapi.CertificateRequest) (*issuer.IssueResponse, error) {
return nil, errors.New("unexpected sign call")
},
},
Builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{gen.CertificateRequest("test")},
ExpectedActions: []testpkg.Action{}, // no update
},
CheckFn: func(t *testing.T, s *controllerFixture, args ...interface{}) {
},
Err: false,
},
"fail if bytes contains no certificate but len > 0": {
Issuer: gen.Issuer("test",
gen.AddIssuerCondition(cmapi.IssuerCondition{
Type: cmapi.IssuerConditionReady,
Status: cmapi.ConditionTrue,
}),
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
),
CertificateRequest: *exampleGarbageCertCR,
IssuerImpl: &fake.Issuer{
FakeSign: func(context.Context, *cmapi.CertificateRequest) (*issuer.IssueResponse, error) {
return nil, errors.New("unexpected sign call")
},
},
Builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{gen.CertificateRequest("test")},
ExpectedActions: []testpkg.Action{},
},
CheckFn: func(t *testing.T, s *controllerFixture, args ...interface{}) {
},
Err: true,
},
}
for n, test := range tests {
t.Run(n, func(t *testing.T) {
if test.Builder == nil {
test.Builder = &testpkg.Builder{}
}
test.Clock = fixedClock
test.Setup(t)
crCopy := test.CertificateRequest.DeepCopy()
err := test.controller.Sync(test.Ctx, crCopy)
if err != nil && !test.Err {
t.Errorf("Expected function to not error, but got: %v", err)
}
if err == nil && test.Err {
t.Errorf("Expected function to get an error, but got: %v", err)
}
test.Finish(t, crCopy, err)
})
}
}