352 lines
14 KiB
Go
352 lines
14 KiB
Go
/*
|
|
Copyright 2022 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 certificatesigningrequests
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
certificatesv1 "k8s.io/api/certificates/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
coretesting "k8s.io/client-go/testing"
|
|
fakeclock "k8s.io/utils/clock/testing"
|
|
|
|
"github.com/cert-manager/cert-manager/pkg/api/util"
|
|
cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
|
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
|
|
"github.com/cert-manager/cert-manager/pkg/controller"
|
|
"github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/fake"
|
|
csrutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util"
|
|
testpkg "github.com/cert-manager/cert-manager/pkg/controller/test"
|
|
"github.com/cert-manager/cert-manager/test/unit/gen"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
var (
|
|
fixedClockStart = time.Now()
|
|
fixedClock = fakeclock.NewFakeClock(fixedClockStart)
|
|
)
|
|
|
|
func TestController_Sync(t *testing.T) {
|
|
metaFixedTime := metav1.NewTime(fixedClockStart)
|
|
// This Clock is used to get values for last transition time and last
|
|
// update time when a condition is set on a CertificateSigningRequest.
|
|
csrutil.Clock = fixedClock
|
|
|
|
tests := map[string]testT{
|
|
"malformed signer name": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("%698~1")),
|
|
},
|
|
"signer group is not cert-manager.io": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.foo.io/foo-issuer")),
|
|
},
|
|
"CertificateSigningRequest has failed": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateFailed,
|
|
})),
|
|
},
|
|
"CertificateSigningRequest has been denied": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateDenied,
|
|
})),
|
|
},
|
|
"CertificateSigningRequest has not yet been approved": {
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
"Normal WaitingApproval Waiting for the Approved condition before issuing",
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/foo-issuer")),
|
|
},
|
|
"Certificate has already been issued": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
}),
|
|
gen.SetCertificateSigningRequestCertificate([]byte("test"))),
|
|
},
|
|
"Signer is not Issuer or ClusterIssuer": {
|
|
builder: &testpkg.Builder{},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("foo.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
"Issuer is not found": {
|
|
builder: &testpkg.Builder{
|
|
ExpectedEvents: []string{
|
|
"Warning IssuerNotFound Referenced Issuer /foo-issuer not found",
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
"Issuer is not one of cert-manager issuer types": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.Issuer("foo-issuer", gen.SetIssuerNamespace("default")),
|
|
},
|
|
ExpectedEvents: []string{
|
|
"Warning IssuerTypeMissing Referenced Issuer default/foo-issuer is missing type",
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/default.foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
// The controller is initialized with self-signed issuer type
|
|
"Issuer is not this controller's issuer type": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.Issuer("foo-issuer", gen.SetIssuerNamespace("default"),
|
|
gen.SetIssuerCA(cmapi.CAIssuer{})),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/default.foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
"Duration annotation has been provided, but is invalid": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})),
|
|
},
|
|
ExpectedEvents: []string{
|
|
"Warning ErrorParseDuration Failed to parse requested duration: failed to parse requested duration on annotation \"experimental.cert-manager.io/request-duration\": time: invalid duration \"foo\"",
|
|
},
|
|
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
|
|
"status",
|
|
"",
|
|
gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestDuration("foo"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
}),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateFailed,
|
|
Status: corev1.ConditionTrue,
|
|
Reason: "ErrorParseDuration",
|
|
Message: `Failed to parse requested duration: failed to parse requested duration on annotation "experimental.cert-manager.io/request-duration": time: invalid duration "foo"`,
|
|
LastTransitionTime: metaFixedTime,
|
|
LastUpdateTime: metaFixedTime,
|
|
})),
|
|
)),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestDuration("foo"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
"Duration annotation has been provided with a value less than 600s": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})),
|
|
},
|
|
ExpectedEvents: []string{
|
|
"Warning InvalidDuration CertificateSigningRequest minimum allowed duration is 10m0s, requested 9m59s",
|
|
},
|
|
|
|
ExpectedActions: []testpkg.Action{
|
|
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
|
|
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
|
|
"status",
|
|
"",
|
|
gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestDuration("599s"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
}),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateFailed,
|
|
Status: corev1.ConditionTrue,
|
|
Reason: "InvalidDuration",
|
|
Message: `CertificateSigningRequest minimum allowed duration is 10m0s, requested 9m59s`,
|
|
LastTransitionTime: metaFixedTime,
|
|
LastUpdateTime: metaFixedTime,
|
|
})),
|
|
)),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestDuration("599s"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
// TODO (irbekrm) Test the scenario where the user is not allowed to reference Issuer
|
|
// Perhaps restructure and use fake SubjectAccessReview https://github.com/kubernetes/client-go/blob/master/kubernetes/typed/authorization/v1/fake/fake_subjectaccessreview.go
|
|
"Referenced ClusterIssuer is not ready": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})),
|
|
},
|
|
ExpectedEvents: []string{
|
|
"Warning IssuerNotReady Referenced ClusterIssuer /foo-issuer does not have a Ready status condition",
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
},
|
|
"Signing fails": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
|
|
gen.AddIssuerCondition(cmapi.IssuerCondition{
|
|
Type: cmapi.IssuerConditionReady,
|
|
Status: cmmeta.ConditionTrue,
|
|
})),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
signerImpl: &fake.Signer{
|
|
FakeSign: func(context.Context, *certificatesv1.CertificateSigningRequest, cmapi.GenericIssuer) error {
|
|
return errors.New("some error")
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
"Signing succeeds": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
|
|
gen.AddIssuerCondition(cmapi.IssuerCondition{
|
|
Type: cmapi.IssuerConditionReady,
|
|
Status: cmmeta.ConditionTrue,
|
|
})),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
signerImpl: &fake.Signer{
|
|
FakeSign: func(context.Context, *certificatesv1.CertificateSigningRequest, cmapi.GenericIssuer) error {
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
"Signing succeeds with a valid duration annotation": {
|
|
builder: &testpkg.Builder{
|
|
CertManagerObjects: []runtime.Object{
|
|
gen.ClusterIssuer("foo-issuer",
|
|
gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}),
|
|
gen.AddIssuerCondition(cmapi.IssuerCondition{
|
|
Type: cmapi.IssuerConditionReady,
|
|
Status: cmmeta.ConditionTrue,
|
|
})),
|
|
},
|
|
},
|
|
csr: gen.CertificateSigningRequest("test",
|
|
gen.SetCertificateSigningRequestSignerName("clusterissuers.cert-manager.io/foo-issuer"),
|
|
gen.SetCertificateSigningRequestDuration("600s"),
|
|
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
|
|
Type: certificatesv1.CertificateApproved,
|
|
})),
|
|
signerImpl: &fake.Signer{
|
|
FakeSign: func(context.Context, *certificatesv1.CertificateSigningRequest, cmapi.GenericIssuer) error {
|
|
return nil
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for name, scenario := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
if scenario.csr != nil {
|
|
scenario.builder.KubeObjects = append(scenario.builder.KubeObjects, scenario.csr)
|
|
}
|
|
fixedClock.SetTime(fixedClockStart)
|
|
scenario.builder.Clock = fixedClock
|
|
scenario.builder.T = t
|
|
scenario.builder.Init()
|
|
|
|
defer scenario.builder.Stop()
|
|
|
|
if scenario.signerImpl == nil {
|
|
scenario.signerImpl = &fake.Signer{
|
|
FakeSign: func(context.Context, *certificatesv1.CertificateSigningRequest, cmapi.GenericIssuer) error {
|
|
return errors.New("unexpected sign call")
|
|
},
|
|
}
|
|
}
|
|
|
|
c := New(util.IssuerSelfSigned, func(*controller.Context) Signer { return scenario.signerImpl })
|
|
c.Register(scenario.builder.Context)
|
|
|
|
scenario.builder.Start()
|
|
|
|
err := c.Sync(context.Background(), scenario.csr)
|
|
if (err == nil) == scenario.wantErr {
|
|
t.Errorf("expected error: %v, but got: %v", scenario.wantErr, err)
|
|
}
|
|
scenario.builder.CheckAndFinish(err)
|
|
})
|
|
}
|
|
}
|
|
|
|
type testT struct {
|
|
builder *testpkg.Builder
|
|
csr *certificatesv1.CertificateSigningRequest
|
|
signerImpl Signer
|
|
wantErr bool
|
|
}
|