270 lines
6.6 KiB
Go
270 lines
6.6 KiB
Go
/*
|
|
Copyright 2020 VMware, Inc.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package apicerts
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
kubeinformers "k8s.io/client-go/informers"
|
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
|
kubetesting "k8s.io/client-go/testing"
|
|
|
|
"github.com/suzerain-io/controller-go"
|
|
"github.com/suzerain-io/pinniped/internal/testutil"
|
|
)
|
|
|
|
func TestExpirerControllerFilters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
namespace string
|
|
secret corev1.Secret
|
|
want bool
|
|
}{
|
|
{
|
|
name: "good name, good namespace",
|
|
namespace: "good-namespace",
|
|
secret: corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "api-serving-cert",
|
|
Namespace: "good-namespace",
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "bad name, good namespace",
|
|
namespace: "good-namespacee",
|
|
secret: corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bad-name",
|
|
Namespace: "good-namespace",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "good name, bad namespace",
|
|
namespace: "good-namespacee",
|
|
secret: corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "api-serving-cert",
|
|
Namespace: "bad-namespace",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "bad name, bad namespace",
|
|
namespace: "good-namespacee",
|
|
secret: corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bad-name",
|
|
Namespace: "bad-namespace",
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name+"-"+test.namespace, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
secretsInformer := kubeinformers.NewSharedInformerFactory(
|
|
kubernetesfake.NewSimpleClientset(),
|
|
0,
|
|
).Core().V1().Secrets()
|
|
withInformer := testutil.NewObservableWithInformerOption()
|
|
_ = NewCertsExpirerController(
|
|
test.namespace,
|
|
nil, // k8sClient, not needed
|
|
secretsInformer,
|
|
withInformer.WithInformer,
|
|
0, // ageThreshold, not needed
|
|
)
|
|
|
|
unrelated := corev1.Secret{}
|
|
filter := withInformer.GetFilterForInformer(secretsInformer)
|
|
require.Equal(t, test.want, filter.Add(&test.secret))
|
|
require.Equal(t, test.want, filter.Update(&unrelated, &test.secret))
|
|
require.Equal(t, test.want, filter.Update(&test.secret, &unrelated))
|
|
require.Equal(t, test.want, filter.Delete(&test.secret))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpirerControllerSync(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
ageThreshold float32
|
|
fillSecretData func(*testing.T, map[string][]byte)
|
|
configKubeAPIClient func(*kubernetesfake.Clientset)
|
|
wantDelete bool
|
|
wantError string
|
|
}{
|
|
{
|
|
name: "secret does not exist",
|
|
wantDelete: false,
|
|
},
|
|
{
|
|
name: "secret missing key",
|
|
fillSecretData: func(t *testing.T, m map[string][]byte) {},
|
|
wantDelete: false,
|
|
},
|
|
{
|
|
name: "lifetime below threshold",
|
|
ageThreshold: 0.7,
|
|
fillSecretData: func(t *testing.T, m map[string][]byte) {
|
|
caPEM, err := testutil.CreateCertificate(
|
|
time.Now().Add(-5*time.Hour),
|
|
time.Now().Add(5*time.Hour),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// See cert_manager.go for this constant.
|
|
m["caCertificate"] = caPEM
|
|
},
|
|
wantDelete: false,
|
|
},
|
|
{
|
|
name: "lifetime above threshold",
|
|
ageThreshold: 0.3,
|
|
fillSecretData: func(t *testing.T, m map[string][]byte) {
|
|
caPEM, err := testutil.CreateCertificate(
|
|
time.Now().Add(-5*time.Hour),
|
|
time.Now().Add(5*time.Hour),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// See cert_manager.go for this constant.
|
|
m["caCertificate"] = caPEM
|
|
},
|
|
wantDelete: true,
|
|
},
|
|
{
|
|
name: "delete failure",
|
|
ageThreshold: 0.3,
|
|
fillSecretData: func(t *testing.T, m map[string][]byte) {
|
|
caPEM, err := testutil.CreateCertificate(
|
|
time.Now().Add(-5*time.Hour),
|
|
time.Now().Add(5*time.Hour),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// See cert_manager.go for this constant.
|
|
m["caCertificate"] = caPEM
|
|
},
|
|
configKubeAPIClient: func(c *kubernetesfake.Clientset) {
|
|
c.PrependReactor("delete", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
|
return true, nil, errors.New("delete failed: some delete error")
|
|
})
|
|
},
|
|
wantError: "delete failed: some delete error",
|
|
},
|
|
{
|
|
name: "parse cert failure",
|
|
fillSecretData: func(t *testing.T, m map[string][]byte) {
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
require.NoError(t, err)
|
|
|
|
// See cert_manager.go for this constant.
|
|
m["caCertificate"] = x509.MarshalPKCS1PrivateKey(privateKey)
|
|
},
|
|
wantDelete: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
|
defer cancel()
|
|
|
|
kubeAPIClient := kubernetesfake.NewSimpleClientset()
|
|
if test.configKubeAPIClient != nil {
|
|
test.configKubeAPIClient(kubeAPIClient)
|
|
}
|
|
|
|
kubeInformerClient := kubernetesfake.NewSimpleClientset()
|
|
name := "api-serving-cert" // See cert_manager.go.
|
|
namespace := "some-namespace"
|
|
if test.fillSecretData != nil {
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
},
|
|
Data: map[string][]byte{},
|
|
}
|
|
test.fillSecretData(t, secret.Data)
|
|
|
|
require.NoError(t, kubeAPIClient.Tracker().Add(secret))
|
|
require.NoError(t, kubeInformerClient.Tracker().Add(secret))
|
|
}
|
|
|
|
kubeInformers := kubeinformers.NewSharedInformerFactory(
|
|
kubeInformerClient,
|
|
0,
|
|
)
|
|
|
|
c := NewCertsExpirerController(
|
|
namespace,
|
|
kubeAPIClient,
|
|
kubeInformers.Core().V1().Secrets(),
|
|
controller.WithInformer,
|
|
test.ageThreshold,
|
|
)
|
|
|
|
// Must start informers before calling TestRunSynchronously().
|
|
kubeInformers.Start(ctx.Done())
|
|
controller.TestRunSynchronously(t, c)
|
|
|
|
err := controller.TestSync(t, c, controller.Context{
|
|
Context: ctx,
|
|
})
|
|
if test.wantError != "" {
|
|
require.EqualError(t, err, test.wantError)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
exActions := []kubetesting.Action{}
|
|
if test.wantDelete {
|
|
exActions = append(
|
|
exActions,
|
|
kubetesting.NewDeleteAction(
|
|
schema.GroupVersionResource{
|
|
Group: "",
|
|
Version: "v1",
|
|
Resource: "secrets",
|
|
},
|
|
namespace,
|
|
name,
|
|
),
|
|
)
|
|
}
|
|
acActions := kubeAPIClient.Actions()
|
|
require.Equal(t, exActions, acActions)
|
|
})
|
|
}
|
|
}
|