cert-manager/pkg/controller/webhookbootstrap/controller_test.go
JoshVanL 0e18ba7b1d
Run a First func for webhook secret bootstrap and catch recursive unit
test

Signed-off-by: JoshVanL <vleeuwenjoshua@gmail.com>
2020-02-05 11:49:31 +00:00

1028 lines
33 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 webhookbootstrap
import (
"bytes"
"context"
"crypto"
"crypto/x509"
"testing"
"github.com/jetstack/cert-manager/pkg/util/pki"
"github.com/jetstack/cert-manager/test/unit/gen"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
coretesting "k8s.io/client-go/testing"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
logf "github.com/jetstack/cert-manager/pkg/logs"
)
const (
defaultWebhookNamespace = "testns"
defaultWebhookCAName = "ca-secret"
defaultWebhookServingName = "serving-secret"
)
var (
defaultWebhookDNSNames = []string{"testdomain.com"}
)
type testT struct {
builder *testpkg.Builder
generatePrivateKeyBytes generatePrivateKeyBytesFn
signCertificate signCertificateFunc
key string
expectedErr bool
}
func runTest(t *testing.T, test testT) {
test.builder.T = t
c := &controller{}
test.builder.Init()
defer test.builder.Stop()
test.builder.Context.WebhookBootstrapOptions.Namespace = defaultWebhookNamespace
test.builder.Context.WebhookBootstrapOptions.DNSNames = defaultWebhookDNSNames
test.builder.Context.WebhookBootstrapOptions.ServingSecretName = defaultWebhookServingName
test.builder.Context.WebhookBootstrapOptions.CASecretName = defaultWebhookCAName
_, waitSync, runFn, err := c.Register(test.builder.Context)
if err != nil {
t.Errorf("failed to setup controller: %v", err)
t.FailNow()
}
test.builder.RegisterAdditionalSyncFuncs(waitSync...)
test.builder.Start(runFn...)
if test.generatePrivateKeyBytes != nil {
c.generatePrivateKeyBytes = test.generatePrivateKeyBytes
}
if test.signCertificate != nil {
c.signCertificate = test.signCertificate
}
test.builder.Sync()
err = c.ProcessItem(context.Background(), test.key)
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 TestProcessItem(t *testing.T) {
exampleBundle := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
gen.SetCertificateDNSNames(defaultWebhookDNSNames...),
gen.SetCertificateOrganization("cert-manager.system"),
))
exampleBadDNSNameBundle := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
gen.SetCertificateDNSNames("nottherightdomain.com"),
gen.SetCertificateOrganization("cert-manager.system"),
))
exampleBundleCA := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
gen.SetCertificateCommonName("cert-manager.webhook.ca"),
gen.SetCertificateIsCA(true),
gen.SetCertificateOrganization("cert-manager.system"),
))
caSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Type: corev1.SecretTypeTLS,
}
caSecretKey := caSecret.Namespace + "/" + caSecret.Name
servingSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Type: corev1.SecretTypeTLS,
}
servingSecretKey := servingSecret.Namespace + "/" + servingSecret.Name
tests := map[string]testT{
"do nothing if the secret's namespace does not match the webhook namespace": {
key: "notmyns/" + defaultWebhookCAName,
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: "notmyns",
},
},
},
ExpectedActions: []testpkg.Action{},
ExpectedEvents: []string{},
},
},
"generate a new private key and certificate for the CA secret if no private key exists": {
key: caSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundleCA.privateKeyBytes),
signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
caSecret,
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"generate a new private key for the CA secret and sign a certificate if existing private key is garbage": {
key: caSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundleCA.privateKeyBytes),
signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: []byte("garbage"),
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"return an error for the serving secret if the ca secret is empty": {
key: servingSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
caSecret,
servingSecret,
},
ExpectedActions: []testpkg.Action{},
ExpectedEvents: []string{},
},
expectedErr: true,
},
"return an error for the serving secret if the ca certificate data is empty": {
key: servingSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
},
Type: corev1.SecretTypeTLS,
},
servingSecret,
},
ExpectedActions: []testpkg.Action{},
ExpectedEvents: []string{},
},
expectedErr: true,
},
"generate a new private key for the serving secret if none exists and sign certificate": {
key: servingSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
corev1.TLSCertKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
servingSecret,
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"generate a new private key for the serving secret if existing private key is garbage and sign certificate": {
key: servingSecretKey,
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
corev1.TLSCertKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: []byte("garbage"),
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new CA certificate if none currently exists": {
key: caSecretKey,
signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new CA certificate if existing one is garbage": {
key: caSecretKey,
signCertificate: testSignCertificateFn(exampleBundleCA.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
corev1.TLSCertKey: []byte("garbage"),
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
caSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new serving certificate if none currently exists": {
key: servingSecretKey,
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new serving certificate if existing one is garbage": {
key: servingSecretKey,
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new serving certificate if existing one contains mismatching private/cert pair": {
key: servingSecretKey,
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"sign a new serving certificate if existing one contains wrong dnsNames": {
key: servingSecretKey,
signCertificate: testSignCertificateFn(exampleBundle.certBytes),
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: caSecret.Namespace,
Name: caSecret.Name,
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundleCA.certBytes,
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBadDNSNameBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBadDNSNameBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBadDNSNameBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
testpkg.NewAction(coretesting.NewUpdateAction(
corev1.SchemeGroupVersion.WithResource("secrets"),
servingSecret.Namespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: servingSecret.Namespace,
Name: servingSecret.Name,
Annotations: map[string]string{
cmapi.AllowsInjectionFromSecretAnnotation: "true",
},
},
Data: map[string][]byte{
corev1.TLSCertKey: exampleBundle.certBytes,
corev1.TLSPrivateKeyKey: exampleBadDNSNameBundle.privateKeyBytes,
cmmeta.TLSCAKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
)),
},
ExpectedEvents: []string{},
},
},
"do nothing if the existing CA secret is up to date": {
key: caSecretKey,
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
corev1.TLSCertKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{},
ExpectedEvents: []string{},
},
},
"do nothing if the existing serving secret is up to date": {
key: servingSecretKey,
builder: &testpkg.Builder{
KubeObjects: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookCAName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundleCA.privateKeyBytes,
corev1.TLSCertKey: exampleBundleCA.certBytes,
},
Type: corev1.SecretTypeTLS,
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: defaultWebhookServingName,
Namespace: defaultWebhookNamespace,
},
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundle.privateKeyBytes,
corev1.TLSCertKey: exampleBundle.certBytes,
},
Type: corev1.SecretTypeTLS,
},
},
ExpectedActions: []testpkg.Action{},
ExpectedEvents: []string{},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
runTest(t, test)
})
}
}
type cryptoBundle struct {
// certificate is the Certificate resource used to create this bundle
certificate *cmapi.Certificate
// privateKey is the private key used as the complement to the certificates
// in this bundle
privateKey crypto.Signer
privateKeyBytes []byte
// cert is a signed certificate
cert *x509.Certificate
certBytes []byte
}
func mustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate) cryptoBundle {
c, err := createCryptoBundle(crt)
if err != nil {
t.Fatalf("error generating crypto bundle: %v", err)
}
return *c
}
func createCryptoBundle(crt *cmapi.Certificate) (*cryptoBundle, error) {
privateKey, err := pki.GeneratePrivateKeyForCertificate(crt)
if err != nil {
return nil, err
}
privateKeyBytes, err := pki.EncodePrivateKey(privateKey, crt.Spec.KeyEncoding)
if err != nil {
return nil, err
}
unsignedCert, err := pki.GenerateTemplate(crt)
if err != nil {
return nil, err
}
certBytes, cert, err := pki.SignCertificate(unsignedCert, unsignedCert, privateKey.Public(), privateKey)
if err != nil {
return nil, err
}
return &cryptoBundle{
certificate: crt,
privateKey: privateKey,
privateKeyBytes: privateKeyBytes,
cert: cert,
certBytes: certBytes,
}, nil
}
func testGeneratePrivateKeyBytesFn(b []byte) generatePrivateKeyBytesFn {
return func(*cmapi.Certificate) ([]byte, error) {
return b, nil
}
}
func testSignCertificateFn(b []byte) signCertificateFunc {
return func(_ *cmapi.Certificate, _, _ crypto.Signer, _ *x509.Certificate) ([]byte, error) {
return b, nil
}
}
func TestReadSecretPrivateKey(t *testing.T) {
exampleBundle := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
gen.SetCertificateDNSNames(defaultWebhookDNSNames...),
gen.SetCertificateOrganization("cert-manager.system"),
))
exampleBundle2 := mustCreateCryptoBundle(t, gen.Certificate(defaultWebhookCAName,
gen.SetCertificateDNSNames(defaultWebhookDNSNames...),
gen.SetCertificateOrganization("cert-manager.system"),
))
tests := map[string]struct {
secret *corev1.Secret
crt *cmapi.Certificate
expPKData []byte
}{
"if the secret contains no private key data then should be generated": {
secret: &corev1.Secret{
Data: map[string][]byte{},
},
expPKData: exampleBundle.privateKeyBytes,
crt: exampleBundle.certificate,
},
"if the secret contains bad private key bytes then generate new": {
secret: &corev1.Secret{
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: []byte("bad key"),
},
},
expPKData: exampleBundle.privateKeyBytes,
crt: exampleBundle.certificate,
},
"if the secret contains a well formed private key data then should not be generated": {
secret: &corev1.Secret{
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: exampleBundle2.privateKeyBytes,
},
},
expPKData: exampleBundle2.privateKeyBytes,
crt: exampleBundle2.certificate,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
log := logf.FromContext(context.Background())
c := &controller{
generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle.privateKeyBytes),
}
_, pkData, err := c.readSecretPrivateKey(log, test.secret, test.crt)
if err != nil {
t.Errorf("unexpected error: %s", err)
t.FailNow()
}
if !bytes.Equal(test.expPKData, pkData) {
t.Errorf("got unexpected private key data returned, exp=%s got=%s",
pkData, exampleBundle.privateKeyBytes)
}
})
}
}