ingress-nginx-helm/images/kube-webhook-certgen/rootfs/pkg/k8s/k8s_test.go
Mateusz Gozdek 54523641a8
images/kube-webhook-certgen/rootfs: add missing tests and fix regression (#7801)
* images/kube-webhook-certgen/rootfs: improve tests objects creation

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: use context with deadline for tests

So in case some operations are taking more time, we respect -timeout
flag.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: add missing tests implementation

It should've been added in 9acf62d867.

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>

* images/kube-webhook-certgen/rootfs: fix patching only mutating webhook

Signed-off-by: Mateusz Gozdek <mgozdek@microsoft.com>
2021-10-12 10:07:47 -07:00

359 lines
9.5 KiB
Go

package k8s
import (
"bytes"
"context"
"errors"
"math/rand"
"testing"
"time"
admissionv1 "k8s.io/api/admissionregistration/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
aggregatorfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
)
const (
testWebhookName = "c7c95710-d8c3-4cc3-a2a8-8d2b46909c76"
testSecretName = "15906410-af2a-4f9b-8a2d-c08ffdd5e129"
testAPIServiceName = "37f6a2d1-b401-4275-833b-9ff5004f0301"
testNamespace = "7cad5f92-c0d5-4bc9-87a3-6f44d5a5619d"
)
var (
fail = admissionv1.Fail
ignore = admissionv1.Ignore
)
func genSecretData() (ca, cert, key []byte) {
ca = make([]byte, 4)
cert = make([]byte, 4)
key = make([]byte, 4)
rand.Read(cert)
rand.Read(key)
return
}
func newTestSimpleK8s(objects ...runtime.Object) *k8s {
return &k8s{
clientset: fake.NewSimpleClientset(objects...),
aggregatorClientset: aggregatorfake.NewSimpleClientset(),
}
}
func TestGetCaFromCertificate(t *testing.T) {
ca, cert, key := genSecretData()
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: testSecretName,
Namespace: testNamespace,
},
Data: map[string][]byte{"ca": ca, "cert": cert, "key": key},
}
k := newTestSimpleK8s(secret)
retrievedCa := k.GetCaFromSecret(contextWithDeadline(t), testSecretName, testNamespace)
if !bytes.Equal(retrievedCa, ca) {
t.Error("Was not able to retrieve CA information that was saved")
}
}
func TestSaveCertsToSecret(t *testing.T) {
k := newTestSimpleK8s()
ca, cert, key := genSecretData()
ctx := contextWithDeadline(t)
k.SaveCertsToSecret(ctx, testSecretName, testNamespace, "cert", "key", ca, cert, key)
secret, _ := k.clientset.CoreV1().Secrets(testNamespace).Get(ctx, testSecretName, metav1.GetOptions{})
if !bytes.Equal(secret.Data["cert"], cert) {
t.Error("'cert' saved data does not match retrieved")
}
if !bytes.Equal(secret.Data["key"], key) {
t.Error("'key' saved data does not match retrieved")
}
}
func TestSaveThenLoadSecret(t *testing.T) {
k := newTestSimpleK8s()
ca, cert, key := genSecretData()
ctx := contextWithDeadline(t)
k.SaveCertsToSecret(ctx, testSecretName, testNamespace, "cert", "key", ca, cert, key)
retrievedCert := k.GetCaFromSecret(ctx, testSecretName, testNamespace)
if !bytes.Equal(retrievedCert, ca) {
t.Error("Was not able to retrieve CA information that was saved")
}
}
func TestPatchWebhookConfigurations(t *testing.T) {
ca, _, _ := genSecretData()
k := newTestSimpleK8s(
&admissionv1.MutatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName,
},
Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}},
},
&admissionv1.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName,
},
Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}},
},
)
ctx := contextWithDeadline(t)
if err := k.patchWebhookConfigurations(ctx, testWebhookName, ca, fail, true, true); err != nil {
t.Fatalf("Unexpected error patching webhooks: %s: %v", err.Error(), errors.Unwrap(err))
}
whmut, err := k.clientset.
AdmissionregistrationV1().
MutatingWebhookConfigurations().
Get(ctx, testWebhookName, metav1.GetOptions{})
if err != nil {
t.Error(err)
}
whval, err := k.clientset.
AdmissionregistrationV1().
MutatingWebhookConfigurations().
Get(ctx, testWebhookName, metav1.GetOptions{})
if err != nil {
t.Error(err)
}
if !bytes.Equal(whmut.Webhooks[0].ClientConfig.CABundle, ca) {
t.Error("Ca retrieved from first mutating webhook configuration does not match")
}
if !bytes.Equal(whmut.Webhooks[1].ClientConfig.CABundle, ca) {
t.Error("Ca retrieved from second mutating webhook configuration does not match")
}
if !bytes.Equal(whval.Webhooks[0].ClientConfig.CABundle, ca) {
t.Error("Ca retrieved from first validating webhook configuration does not match")
}
if !bytes.Equal(whval.Webhooks[1].ClientConfig.CABundle, ca) {
t.Error("Ca retrieved from second validating webhook configuration does not match")
}
if whmut.Webhooks[0].FailurePolicy == nil {
t.Errorf("Expected first mutating webhook failure policy to be set to %s", fail)
}
if whmut.Webhooks[1].FailurePolicy == nil {
t.Errorf("Expected second mutating webhook failure policy to be set to %s", fail)
}
if whval.Webhooks[0].FailurePolicy == nil {
t.Errorf("Expected first validating webhook failure policy to be set to %s", fail)
}
if whval.Webhooks[1].FailurePolicy == nil {
t.Errorf("Expected second validating webhook failure policy to be set to %s", fail)
}
}
func Test_Patching_objects(t *testing.T) {
t.Parallel()
ctx := contextWithDeadline(t)
t.Run("returns_error_when", func(t *testing.T) {
t.Parallel()
t.Run("failure_policy_is_defined_but_no_webhooks_will_be_patched", func(t *testing.T) {
t.Parallel()
k := testK8sWithUnpatchedObjects()
o := PatchOptions{
FailurePolicyType: admissionv1.Fail,
}
if err := k.PatchObjects(ctx, o); err == nil {
t.Fatalf("Expected error while patching")
}
})
// This is to preserve old behavior and log format, it could be improved.
t.Run("diffent_non_empty_names_are_specified_for_validating_and_mutating_webhook", func(t *testing.T) {
t.Parallel()
k := testK8sWithUnpatchedObjects()
o := PatchOptions{
ValidatingWebhookConfigurationName: "foo",
MutatingWebhookConfigurationName: "bar",
}
if err := k.PatchObjects(ctx, o); err == nil {
t.Fatalf("Expected error while patching")
}
})
t.Run("patching_webhook_is_requested_and_it_does_not_exist", func(t *testing.T) {
t.Parallel()
k := newTestSimpleK8s()
o := PatchOptions{
ValidatingWebhookConfigurationName: "foo",
}
if err := k.PatchObjects(ctx, o); err == nil {
t.Fatalf("Expected error while patching")
}
})
t.Run("patching_APIService_is_requested_and_it_does_not_exist", func(t *testing.T) {
t.Parallel()
k := newTestSimpleK8s()
o := PatchOptions{
APIServiceName: "foo",
}
if err := k.PatchObjects(ctx, o); err == nil {
t.Fatalf("Expected error while patching")
}
})
})
t.Run("when_patching_APIService_object", func(t *testing.T) {
t.Parallel()
k := testK8sWithUnpatchedObjects()
o := PatchOptions{
APIServiceName: testAPIServiceName,
CABundle: []byte("foo"),
}
if err := k.PatchObjects(ctx, o); err != nil {
t.Fatalf("Unexpected error while patching objects: %v", err)
}
c := k.aggregatorClientset.ApiregistrationV1().APIServices()
apiService, err := c.Get(ctx, testAPIServiceName, metav1.GetOptions{})
if err != nil {
t.Fatalf("Unexpected error while getting APIService object %q: %v", testAPIServiceName, err)
}
// This is required when CABundle field is populated.
t.Run("sets_insecure_skip_tls_verity_to_false", func(t *testing.T) {
t.Parallel()
if apiService.Spec.InsecureSkipTLSVerify {
t.Fatalf("Expected insecureSkipTLSVerify of APIService to be false")
}
})
t.Run("sets_ca_bundle_with_ca_certificate_from_created_secret", func(t *testing.T) {
t.Parallel()
if len(apiService.Spec.CABundle) == 0 {
t.Fatalf("Expected CABundle of APIService to be not empty")
}
if !bytes.Equal(o.CABundle, apiService.Spec.CABundle) {
t.Fatalf("CABundle content of APIService does not match requested bundle")
}
})
})
t.Run("allows_patching_only_validating_webhook", func(t *testing.T) {
t.Parallel()
k := testK8sWithUnpatchedObjects()
o := PatchOptions{
ValidatingWebhookConfigurationName: testWebhookName,
}
if err := k.PatchObjects(ctx, o); err != nil {
t.Fatalf("Unexpected error patching objects: %v", err)
}
})
t.Run("allows_patching_only_mutating_webhook", func(t *testing.T) {
t.Parallel()
k := testK8sWithUnpatchedObjects()
o := PatchOptions{
MutatingWebhookConfigurationName: testWebhookName,
}
if err := k.PatchObjects(ctx, o); err != nil {
t.Fatalf("Unexpected error patching objects: %v", err)
}
})
}
const (
// Arbitrary amount of time to let tests exit cleanly before main process terminates.
timeoutGracePeriod = 10 * time.Second
)
// contextWithDeadline returns context with will timeout before t.Deadline().
func contextWithDeadline(t *testing.T) context.Context {
t.Helper()
deadline, ok := t.Deadline()
if !ok {
return context.Background()
}
ctx, cancel := context.WithDeadline(context.Background(), deadline.Truncate(timeoutGracePeriod))
t.Cleanup(cancel)
return ctx
}
func testK8sWithUnpatchedObjects() *k8s {
ca, cert, key := genSecretData()
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: testSecretName,
Namespace: testNamespace,
},
Data: map[string][]byte{"ca": ca, "cert": cert, "key": key},
}
validatingWebhook := &admissionv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName,
},
Webhooks: []admissionv1.ValidatingWebhook{{Name: "v1"}, {Name: "v2"}},
}
mutatingWebhook := &admissionv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: testWebhookName,
},
Webhooks: []admissionv1.MutatingWebhook{{Name: "m1"}, {Name: "m2"}},
}
apiService := &apiregistrationv1.APIService{
ObjectMeta: metav1.ObjectMeta{
Name: testAPIServiceName,
},
}
return &k8s{
clientset: fake.NewSimpleClientset(secret, validatingWebhook, mutatingWebhook),
aggregatorClientset: aggregatorfake.NewSimpleClientset(apiService),
}
}