Skip to content

Commit

Permalink
Add overriding webhook operations
Browse files Browse the repository at this point in the history
Signed-off-by: Yi Rae Kim <[email protected]>
  • Loading branch information
yiraeChristineKim committed Oct 31, 2023
1 parent 0838887 commit 1359420
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 35 deletions.
5 changes: 5 additions & 0 deletions api/v1alpha1/gatekeeper_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,14 @@ type WebhookConfig struct {
// +optional
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
// +optional
Operations *[]OperationType `json:"operations,omitempty"`
// +optional
DisabledBuiltins []string `json:"disabledBuiltins,omitempty"`
}

// +kubebuilder:validation:Enum:=CONNECT;CREATE;UPDATE;DELETE;*
type OperationType admregv1.OperationType

// +kubebuilder:validation:Enum:=DEBUG;INFO;WARNING;ERROR
type LogLevelMode string

Expand Down
9 changes: 9 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions bundle/manifests/operator.gatekeeper.sh_gatekeepers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,17 @@ spec:
"value". The requirements are ANDed.
type: object
type: object
operations:
items:
description: OperationType specifies an operation for a request.
enum:
- CONNECT
- CREATE
- UPDATE
- DELETE
- '*'
type: string
type: array
replicas:
format: int32
minimum: 0
Expand Down
11 changes: 11 additions & 0 deletions config/crd/bases/operator.gatekeeper.sh_gatekeepers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,17 @@ spec:
"value". The requirements are ANDed.
type: object
type: object
operations:
items:
description: OperationType specifies an operation for a request.
enum:
- CONNECT
- CREATE
- UPDATE
- DELETE
- '*'
type: string
type: array
replicas:
format: int32
minimum: 0
Expand Down
12 changes: 12 additions & 0 deletions config/samples/gatekeeper_operations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: operator.gatekeeper.sh/v1alpha1
kind: Gatekeeper
metadata:
name: gatekeeper
spec:
# Add fields here
webhook:
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
46 changes: 46 additions & 0 deletions controllers/gatekeeper_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/gatekeeper/gatekeeper-operator/api/v1alpha1"
operatorv1alpha1 "github.com/gatekeeper/gatekeeper-operator/api/v1alpha1"
"github.com/gatekeeper/gatekeeper-operator/controllers/merge"
"github.com/gatekeeper/gatekeeper-operator/pkg/platform"
Expand Down Expand Up @@ -620,6 +621,7 @@ func webhookOverrides(obj *unstructured.Unstructured, webhook *operatorv1alpha1.
return nil
}

// override common properties
func webhookConfigurationOverrides(
obj *unstructured.Unstructured,
webhook *operatorv1alpha1.WebhookConfig,
Expand All @@ -644,9 +646,15 @@ func webhookConfigurationOverrides(
return err
}
}

if err := setOperators(obj, webhook.Operations, webhookName); err != nil {
return err
}

if err := setNamespaceSelector(obj, webhook.NamespaceSelector, gatekeeperNamespace, webhookName); err != nil {
return err
}

} else if err := setNamespaceSelector(obj, nil, gatekeeperNamespace, webhookName); err != nil {
return err
}
Expand Down Expand Up @@ -963,6 +971,44 @@ func setNamespaceSelector(
return setWebhookConfigurationWithFn(obj, webhookName, setNamespaceSelectorFn)
}

func setOperators(
obj *unstructured.Unstructured, operations *[]v1alpha1.OperationType, webhookName string,
) error {
// If no operations is provided, no override for operations
if operations == nil {
return nil
}

setOperatorsFn := func(webhook map[string]interface{}) error {
rules := webhook["rules"].([]interface{})
if rules[0] == nil {
return nil
}

converted := make([]interface{}, len(*operations))
for i, op := range *operations {
converted[i] = string(op)
}

firtRuleObj := rules[0].(map[string]interface{})
newfirstRule := map[string]interface{}{
"apiGroups": firtRuleObj["apiGroups"],
"apiVersions": firtRuleObj["apiVersions"],
"operations": converted,
"resources": firtRuleObj["resources"],
"scope": firtRuleObj["scope"],
}

if err := unstructured.SetNestedSlice(webhook, []interface{}{newfirstRule}, "rules"); err != nil {
return errors.Wrapf(err, "Failed to set webhook namespace selector")
}

return nil
}

return setWebhookConfigurationWithFn(obj, webhookName, setOperatorsFn)
}

// Generic setters

func setAffinity(obj *unstructured.Unstructured, spec operatorv1alpha1.GatekeeperSpec) error {
Expand Down
118 changes: 83 additions & 35 deletions test/e2e/gatekeeper_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"strings"

. "github.com/gatekeeper/gatekeeper-operator/test/e2e/util"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
admregv1 "k8s.io/api/admissionregistration/v1"
Expand All @@ -36,7 +37,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/gatekeeper/gatekeeper-operator/api/v1alpha1"
"github.com/gatekeeper/gatekeeper-operator/controllers"
Expand All @@ -48,6 +48,7 @@ const (
// Gatekeeper name and namespace
gkName = "gatekeeper"
gatekeeperWithAllValuesFile = "gatekeeper_with_all_values.yaml"
gatekeeperWithOperations = "gatekeeper_operations.yaml"
)

var (
Expand Down Expand Up @@ -93,39 +94,37 @@ var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), func() {
}
})

AfterEach(func() {
Expect(K8sClient.Delete(ctx, emptyGatekeeper(), client.PropagationPolicy(v1.DeletePropagationForeground))).Should(Succeed())

// Once this succeeds, clean up has happened for all owned resources.
Eventually(func() bool {
err := K8sClient.Get(ctx, gatekeeperName, &v1alpha1.Gatekeeper{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())

Eventually(func() bool {
err := K8sClient.Get(ctx, auditName, &appsv1.Deployment{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())

Eventually(func() bool {
err := K8sClient.Get(ctx, controllerManagerName, &appsv1.Deployment{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())
})

Describe("Overriding CR", Ordered, func() {
AfterEach(func() {
Kubectl("delete", "gatekeeper", "gatekeeper", "--ignore-not-found")
// Once this succeeds, clean up has happened for all owned resources.
Eventually(func() bool {
err := K8sClient.Get(ctx, gatekeeperName, &v1alpha1.Gatekeeper{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())

Eventually(func() bool {
err := K8sClient.Get(ctx, auditName, &appsv1.Deployment{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())

Eventually(func() bool {
err := K8sClient.Get(ctx, controllerManagerName, &appsv1.Deployment{})
if err == nil {
return false
}

return apierrors.IsNotFound(err)
}, deleteTimeout, pollInterval).Should(BeTrue())
})
It("Creating an empty gatekeeper contains default values", func() {
gatekeeper := emptyGatekeeper()
err := loadGatekeeperFromFile(gatekeeper, "gatekeeper_empty.yaml")
Expand Down Expand Up @@ -498,6 +497,55 @@ var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), func() {
auditDeployment, webhookDeployment = gatekeeperDeployments()
byCheckingMutationDisabled(auditDeployment, webhookDeployment)
})

It("Override Webhook operations with Create, Update, Delete, Connect", func() {
gatekeeper := &v1alpha1.Gatekeeper{}
gatekeeper.Namespace = gatekeeperNamespace
err := loadGatekeeperFromFile(gatekeeper, gatekeeperWithOperations)
Expect(err).ToNot(HaveOccurred())
Expect(K8sClient.Create(ctx, gatekeeper)).Should(Succeed())

By("Wait until new Deployments loaded")
gatekeeperDeployments()

By("ValidatingWebhookConfiguration Rules should have 4 operations")
validatingWebhookConfiguration := &admregv1.ValidatingWebhookConfiguration{}
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, validatingWebhookName, validatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(4))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations).Should(HaveLen(4))
}, timeout, pollInterval).Should(Succeed())

By("MutatingWebhookConfiguration Rules should have 4 operations")
mutatingWebhookConfiguration := &admregv1.MutatingWebhookConfiguration{}
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, mutatingWebhookName, mutatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(4))
}, timeout, pollInterval).Should(Succeed())

gatekeeper.Spec.Webhook.Operations = &[]v1alpha1.OperationType{"*"}
Expect(K8sClient.Update(ctx, gatekeeper)).Should(Succeed())

By("ValidatingWebhookConfiguration Rules should have 1 operations")
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, validatingWebhookName, validatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(1))
g.Expect(validatingWebhookConfiguration.Webhooks[0].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations).Should(HaveLen(1))
g.Expect(validatingWebhookConfiguration.Webhooks[1].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
}, timeout, pollInterval).Should(Succeed())

By("MutatingWebhookConfiguration Rules should have 1 operations")
Eventually(func(g Gomega) {
err := K8sClient.Get(ctx, mutatingWebhookName, mutatingWebhookConfiguration)
g.Expect(err).ShouldNot(HaveOccurred())
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations).Should(HaveLen(1))
g.Expect(mutatingWebhookConfiguration.Webhooks[0].Rules[0].Operations[0]).Should(BeEquivalentTo("*"))
}, timeout, pollInterval).Should(Succeed())
})
})
})

Expand All @@ -509,15 +557,15 @@ func gatekeeperAuditDeployment() (auditDeployment *appsv1.Deployment) {
auditDeployment = &appsv1.Deployment{}
Eventually(func() error {
return K8sClient.Get(ctx, auditName, auditDeployment)
}, timeout*2, pollInterval).ShouldNot(HaveOccurred())
}, timeout, pollInterval).ShouldNot(HaveOccurred())
return
}

func gatekeeperWebhookDeployment() (webhookDeployment *appsv1.Deployment) {
webhookDeployment = &appsv1.Deployment{}
Eventually(func() error {
return K8sClient.Get(ctx, controllerManagerName, webhookDeployment)
}, timeout*2, pollInterval).ShouldNot(HaveOccurred())
}, timeout, pollInterval).ShouldNot(HaveOccurred())
return
}

Expand Down

0 comments on commit 1359420

Please sign in to comment.