Adds Vault CertificateSigningRequest Issuer controller

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
This commit is contained in:
joshvanl 2021-06-14 20:20:26 +01:00
parent 59c2a2d9f4
commit f5b609e446
5 changed files with 707 additions and 9 deletions

View File

@ -73,6 +73,7 @@ filegroup(
"//pkg/controller/certificatesigningrequests/fake:all-srcs",
"//pkg/controller/certificatesigningrequests/selfsigned:all-srcs",
"//pkg/controller/certificatesigningrequests/util:all-srcs",
"//pkg/controller/certificatesigningrequests/vault:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],

View File

@ -0,0 +1,68 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["vault.go"],
importpath = "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/vault",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/util:go_default_library",
"//pkg/apis/certmanager/v1:go_default_library",
"//pkg/apis/experimental/v1alpha1:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/certificatesigningrequests:go_default_library",
"//pkg/controller/certificatesigningrequests/util:go_default_library",
"//pkg/internal/vault:go_default_library",
"//pkg/logs:go_default_library",
"//pkg/util/pki:go_default_library",
"@io_k8s_api//certificates/v1:go_default_library",
"@io_k8s_api//core/v1:go_default_library",
"@io_k8s_apimachinery//pkg/api/errors:go_default_library",
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
"@io_k8s_client_go//kubernetes/typed/certificates/v1:go_default_library",
"@io_k8s_client_go//listers/core/v1:go_default_library",
"@io_k8s_client_go//tools/record:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["vault_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/api/util:go_default_library",
"//pkg/apis/certmanager:go_default_library",
"//pkg/apis/certmanager/v1:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/controller/certificatesigningrequests:go_default_library",
"//pkg/controller/certificatesigningrequests/util:go_default_library",
"//pkg/controller/test:go_default_library",
"//pkg/internal/vault:go_default_library",
"//pkg/internal/vault/fake:go_default_library",
"//test/unit/gen:go_default_library",
"@io_k8s_api//authorization/v1:go_default_library",
"@io_k8s_api//certificates/v1:go_default_library",
"@io_k8s_api//core/v1:go_default_library",
"@io_k8s_apimachinery//pkg/api/errors:go_default_library",
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
"@io_k8s_apimachinery//pkg/runtime/schema:go_default_library",
"@io_k8s_client_go//listers/core/v1:go_default_library",
"@io_k8s_client_go//testing:go_default_library",
"@io_k8s_utils//clock/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,156 @@
/*
Copyright 2021 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 vault
import (
"context"
"crypto"
"crypto/x509"
"encoding/base64"
"fmt"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/record"
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
experimentalapi "github.com/jetstack/cert-manager/pkg/apis/experimental/v1alpha1"
controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
"github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests"
"github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util"
internalvault "github.com/jetstack/cert-manager/pkg/internal/vault"
logf "github.com/jetstack/cert-manager/pkg/logs"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
const (
CSRControllerName = "certificatesigningrequests-issuer-vault"
)
type signingFn func(*x509.Certificate, *x509.Certificate, crypto.PublicKey, interface{}) ([]byte, *x509.Certificate, error)
// Vault is a controller for signing Kubernetes CertificateSigningRequest
// using Vault Issuers.
type Vault struct {
issuerOptions controllerpkg.IssuerOptions
secretsLister corelisters.SecretLister
recorder record.EventRecorder
certClient certificatesclient.CertificateSigningRequestInterface
clientBuilder internalvault.ClientBuilder
}
func init() {
// create certificate signing request controller for vault issuer
controllerpkg.Register(CSRControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
return controllerpkg.NewBuilder(ctx, CSRControllerName).
For(certificatesigningrequests.New(apiutil.IssuerVault, NewVault(ctx))).
Complete()
})
}
func NewVault(ctx *controllerpkg.Context) *Vault {
return &Vault{
issuerOptions: ctx.IssuerOptions,
secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
recorder: ctx.Recorder,
certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(),
clientBuilder: internalvault.New,
}
}
// Sign attempts to sign the given CertificateSigningRequest based on the
// provided Vault Issuer or ClusterIssuer. This function will update the
// resource if signing was successful. Returns an error which, if not nil,
// should trigger a retry.
func (v *Vault) Sign(ctx context.Context, csr *certificatesv1.CertificateSigningRequest, issuerObj cmapi.GenericIssuer) error {
log := logf.FromContext(ctx, "sign")
log = logf.WithRelatedResource(log, issuerObj)
resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj)
client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj)
if apierrors.IsNotFound(err) {
message := "Required secret resource not found"
log.Error(err, message)
v.recorder.Event(csr, corev1.EventTypeWarning, "SecretNotFound", message)
util.CertificateSigningRequestSetFailed(csr, "SecretNotFound", message)
_, err := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{})
return err
}
if err != nil {
message := fmt.Sprintf("Failed to initialise vault client for signing: %s", err)
log.Error(err, message)
v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorVaultInit", message)
return err
}
duration, err := pki.DurationFromCertificateSigningRequest(csr)
if err != nil {
message := fmt.Sprintf("Failed to parse requested duration: %s", err)
log.Error(err, message)
v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorParseDuration", message)
util.CertificateSigningRequestSetFailed(csr, "ErrorParseDuration", message)
_, err := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{})
return err
}
certPEM, caPEM, err := client.Sign(csr.Spec.Request, duration)
if err != nil {
message := fmt.Sprintf("Vault failed to sign: %s", err)
log.Error(err, message)
v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorSigning", message)
util.CertificateSigningRequestSetFailed(csr, "ErrorSigning", message)
_, err := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{})
return err
}
log.V(logf.DebugLevel).Info("certificate issued")
// Update the status.certificate first so that the sync from updating will
// not cause another issuance before setting the CA.
csr.Status.Certificate = certPEM
csr, err = v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{})
if err != nil {
message := "Error updating certificate"
v.recorder.Eventf(csr, corev1.EventTypeWarning, "ErrorUpdate", "%s: %s", message, err)
return err
}
if csr.Annotations == nil {
csr.Annotations = make(map[string]string)
}
csr.Annotations[experimentalapi.CertificateSigningRequestCAAnnotationKey] = base64.StdEncoding.EncodeToString(caPEM)
_, err = v.certClient.Update(ctx, csr, metav1.UpdateOptions{})
if err != nil {
message := fmt.Sprintf("Error setting %q", experimentalapi.CertificateSigningRequestCAAnnotationKey)
v.recorder.Eventf(csr, corev1.EventTypeWarning, "ErrorCAUpdate", "%s: %s", message, err)
return err
}
log.V(logf.DebugLevel).Info("vault certificate issued")
v.recorder.Event(csr, corev1.EventTypeNormal, "CertificateIssued", "Certificate signed successfully")
return nil
}

View File

@ -0,0 +1,460 @@
/*
Copyright 2021 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 vault
import (
"context"
"crypto/x509"
"errors"
"testing"
"time"
authzv1 "k8s.io/api/authorization/v1"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
corelisters "k8s.io/client-go/listers/core/v1"
coretesting "k8s.io/client-go/testing"
fakeclock "k8s.io/utils/clock/testing"
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
"github.com/jetstack/cert-manager/pkg/apis/certmanager"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests"
"github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util"
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
internalvault "github.com/jetstack/cert-manager/pkg/internal/vault"
fakevault "github.com/jetstack/cert-manager/pkg/internal/vault/fake"
"github.com/jetstack/cert-manager/test/unit/gen"
)
var (
fixedClockStart = time.Now()
fixedClock = fakeclock.NewFakeClock(fixedClockStart)
)
func TestProcessItem(t *testing.T) {
metaFixedClockStart := metav1.NewTime(fixedClockStart)
util.Clock = fixedClock
baseIssuer := gen.Issuer("test-issuer",
gen.SetIssuerVault(cmapi.VaultIssuer{
Auth: cmapi.VaultAuth{
Kubernetes: &cmapi.VaultKubernetesAuth{
Path: "/v1/kubernetes",
Role: "kube-pki",
SecretRef: cmmeta.SecretKeySelector{
Key: "token",
LocalObjectReference: cmmeta.LocalObjectReference{
Name: "sa-token",
},
},
},
},
}),
gen.AddIssuerCondition(cmapi.IssuerCondition{
Type: cmapi.IssuerConditionReady,
Status: cmmeta.ConditionTrue,
}),
)
csrPEM, _, err := gen.CSR(x509.RSA)
if err != nil {
t.Fatal(err)
}
baseCSR := gen.CertificateSigningRequest("test-cr",
gen.SetCertificateSigningRequestRequest(csrPEM),
gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/default-unit-test-ns.test-issuer"),
gen.SetCertificateSigningRequestDuration("1440h"),
gen.SetCertificateSigningRequestUsername("user-1"),
gen.SetCertificateSigningRequestGroups([]string{"group-1", "group-2"}),
gen.SetCertificateSigningRequestUID("uid-1"),
gen.SetCertificateSigningRequestExtra(map[string]certificatesv1.ExtraValue{
"extra": []string{"1", "2"},
}),
)
tests := map[string]struct {
builder *testpkg.Builder
csr *certificatesv1.CertificateSigningRequest
clientBuilder internalvault.ClientBuilder
expectedErr bool
}{
"a CertificateSigningRequest without an approved condition should do nothing": {
csr: gen.CertificateSigningRequestFrom(baseCSR),
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
},
},
"a CertificateSigningRequest with a denied condition should do nothing": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateDenied,
Status: corev1.ConditionTrue,
}),
),
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
ExpectedEvents: []string{},
ExpectedActions: nil,
},
},
"an approved CSR where the vault client builder returns a not found error should mark as Failed": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
),
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
return nil, apierrors.NewNotFound(schema.GroupResource{}, "test-secret")
},
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
ExpectedEvents: []string{
"Warning SecretNotFound Required secret resource not found",
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"),
"",
&authzv1.SubjectAccessReview{
Spec: authzv1.SubjectAccessReviewSpec{
User: "user-1",
Groups: []string{"group-1", "group-2"},
Extra: map[string]authzv1.ExtraValue{
"extra": []string{"1", "2"},
},
UID: "uid-1",
ResourceAttributes: &authzv1.ResourceAttributes{
Group: certmanager.GroupName,
Resource: "signers",
Verb: "reference",
Namespace: baseIssuer.Namespace,
Name: baseIssuer.Name,
Version: "*",
},
},
},
)),
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
"status",
"",
gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateFailed,
Status: corev1.ConditionTrue,
Reason: "SecretNotFound",
Message: "Required secret resource not found",
LastTransitionTime: metaFixedClockStart,
LastUpdateTime: metaFixedClockStart,
}),
),
)),
},
},
},
"an approved CSR where the vault client builder returns a generic error should return error to retry": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
),
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
return nil, errors.New("generic error")
},
expectedErr: true,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
ExpectedEvents: []string{
"Warning ErrorVaultInit Failed to initialise vault client for signing: generic error",
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"),
"",
&authzv1.SubjectAccessReview{
Spec: authzv1.SubjectAccessReviewSpec{
User: "user-1",
Groups: []string{"group-1", "group-2"},
Extra: map[string]authzv1.ExtraValue{
"extra": []string{"1", "2"},
},
UID: "uid-1",
ResourceAttributes: &authzv1.ResourceAttributes{
Group: certmanager.GroupName,
Resource: "signers",
Verb: "reference",
Namespace: baseIssuer.Namespace,
Name: baseIssuer.Name,
Version: "*",
},
},
},
)),
},
},
},
"an approved CSR which has an invalid duration string should be marked as Failed": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestDuration("bad-duration"),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
),
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
return fakevault.New(), nil
},
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
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 "bad-duration"`,
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"),
"",
&authzv1.SubjectAccessReview{
Spec: authzv1.SubjectAccessReviewSpec{
User: "user-1",
Groups: []string{"group-1", "group-2"},
Extra: map[string]authzv1.ExtraValue{
"extra": []string{"1", "2"},
},
UID: "uid-1",
ResourceAttributes: &authzv1.ResourceAttributes{
Group: certmanager.GroupName,
Resource: "signers",
Verb: "reference",
Namespace: baseIssuer.Namespace,
Name: baseIssuer.Name,
Version: "*",
},
},
},
)),
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
"status",
"",
gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(),
gen.SetCertificateSigningRequestDuration("bad-duration"),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
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 "bad-duration"`,
LastTransitionTime: metaFixedClockStart,
LastUpdateTime: metaFixedClockStart,
}),
),
)),
},
},
},
"an approved CSR which errors when invoking sign on the vault client should mark the CSR as Failed": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
),
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
return fakevault.New().WithSign(nil, nil, errors.New("sign error")), nil
},
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
ExpectedEvents: []string{
"Warning ErrorSigning Vault failed to sign: sign error",
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"),
"",
&authzv1.SubjectAccessReview{
Spec: authzv1.SubjectAccessReviewSpec{
User: "user-1",
Groups: []string{"group-1", "group-2"},
Extra: map[string]authzv1.ExtraValue{
"extra": []string{"1", "2"},
},
UID: "uid-1",
ResourceAttributes: &authzv1.ResourceAttributes{
Group: certmanager.GroupName,
Resource: "signers",
Verb: "reference",
Namespace: baseIssuer.Namespace,
Name: baseIssuer.Name,
Version: "*",
},
},
},
)),
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
"status",
"",
gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateFailed,
Status: corev1.ConditionTrue,
Reason: "ErrorSigning",
Message: "Vault failed to sign: sign error",
LastTransitionTime: metaFixedClockStart,
LastUpdateTime: metaFixedClockStart,
}),
),
)),
},
},
},
"an approved CSR which successfully signs, should update the Certificate and CA fields": {
csr: gen.CertificateSigningRequestFrom(baseCSR,
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
),
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
return fakevault.New().WithSign([]byte("signed-cert"), []byte("signing-ca"), nil), nil
},
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()},
ExpectedEvents: []string{
"Normal CertificateIssued Certificate signed successfully",
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"),
"",
&authzv1.SubjectAccessReview{
Spec: authzv1.SubjectAccessReviewSpec{
User: "user-1",
Groups: []string{"group-1", "group-2"},
Extra: map[string]authzv1.ExtraValue{
"extra": []string{"1", "2"},
},
UID: "uid-1",
ResourceAttributes: &authzv1.ResourceAttributes{
Group: certmanager.GroupName,
Resource: "signers",
Verb: "reference",
Namespace: baseIssuer.Namespace,
Name: baseIssuer.Name,
Version: "*",
},
},
},
)),
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
"status",
"",
gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
gen.SetCertificateSigningRequestCertificate([]byte("signed-cert")),
),
)),
testpkg.NewAction(coretesting.NewUpdateAction(
certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"),
"",
gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(),
gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.CertificateApproved,
Status: corev1.ConditionTrue,
}),
gen.SetCertificateSigningRequestCertificate([]byte("signed-cert")),
gen.SetCertificateSigningRequestCA([]byte("signing-ca")),
),
)),
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if test.csr != nil {
test.builder.KubeObjects = append(test.builder.KubeObjects, test.csr)
}
fixedClock.SetTime(fixedClockStart)
test.builder.Clock = fixedClock
test.builder.T = t
test.builder.Init()
// Always return true for SubjectAccessReviews in tests
test.builder.FakeKubeClient().PrependReactor("create", "*", func(action coretesting.Action) (bool, runtime.Object, error) {
if action.GetResource() != authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews") {
return false, nil, nil
}
return true, &authzv1.SubjectAccessReview{
Status: authzv1.SubjectAccessReviewStatus{
Allowed: true,
},
}, nil
})
defer test.builder.Stop()
vault := NewVault(test.builder.Context)
vault.clientBuilder = test.clientBuilder
controller := certificatesigningrequests.New(apiutil.IssuerVault, vault)
controller.Register(test.builder.Context)
test.builder.Start()
err := controller.ProcessItem(context.Background(), test.csr.Name)
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)
})
}
}

View File

@ -31,15 +31,9 @@ import (
// GenerateTemplateFromCertificateSigningRequest will create an
// *x509.Certificate from the given CertificateSigningRequest resource
func GenerateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) {
duration := cmapi.DefaultCertificateDuration
requestedDuration, ok := csr.Annotations[experimentalapi.CertificateSigningRequestDurationAnnotationKey]
if ok {
dur, err := time.ParseDuration(requestedDuration)
if err != nil {
return nil, fmt.Errorf("failed to parse requested duration on annotation %q: %w",
experimentalapi.CertificateSigningRequestDurationAnnotationKey, err)
}
duration = dur
duration, err := DurationFromCertificateSigningRequest(csr)
if err != nil {
return nil, err
}
ku, eku, err := BuildKeyUsagesKube(csr.Spec.Usages)
@ -52,6 +46,25 @@ func GenerateTemplateFromCertificateSigningRequest(csr *certificatesv1.Certifica
return GenerateTemplateFromCSRPEMWithUsages(csr.Spec.Request, duration, isCA, ku, eku)
}
// DurationFromCertificateSigningRequest will return the time.Duration of the
// requested duration on the CertificateSigningRequest. If the annotation is
// empty, will return the cert-manager default certificate duration
func DurationFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (time.Duration, error) {
requestedDuration, ok := csr.Annotations[experimentalapi.CertificateSigningRequestDurationAnnotationKey]
if !ok {
// Return default certificate duration if one not requested
return cmapi.DefaultCertificateDuration, nil
}
duration, err := time.ParseDuration(requestedDuration)
if err != nil {
return -1, fmt.Errorf("failed to parse requested duration on annotation %q: %w",
experimentalapi.CertificateSigningRequestDurationAnnotationKey, err)
}
return duration, nil
}
func BuildKeyUsagesKube(usages []certificatesv1.KeyUsage) (x509.KeyUsage, []x509.ExtKeyUsage, error) {
var unk []certificatesv1.KeyUsage
if len(usages) == 0 {