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 bd5f357
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 6 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
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
68 changes: 62 additions & 6 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 Down Expand Up @@ -81,7 +81,7 @@ func initializeGlobals() {
}
}

var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), func() {
var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), Ordered, func() {
BeforeEach(func() {
if !useExistingCluster() {
Skip("Test requires existing cluster. Set environment variable USE_EXISTING_CLUSTER=true and try again.")
Expand All @@ -94,8 +94,7 @@ var _ = Describe("Gatekeeper", Label("gatekeeper-controller"), func() {
})

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

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{})
Expand Down Expand Up @@ -498,6 +497,63 @@ 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{
ObjectMeta: v1.ObjectMeta{
Name: gkName,
Namespace: gatekeeperNamespace,
},
Spec: v1alpha1.GatekeeperSpec{
Webhook: &v1alpha1.WebhookConfig{
Operations: &[]v1alpha1.OperationType{"CREATE", "UPDATE", "DELETE", "CONNECT"},
},
},
}

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 +565,15 @@ func gatekeeperAuditDeployment() (auditDeployment *appsv1.Deployment) {
auditDeployment = &appsv1.Deployment{}
Eventually(func() error {
return K8sClient.Get(ctx, auditName, auditDeployment)
}, timeout*2, pollInterval).ShouldNot(HaveOccurred())
}, timeout*3, 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*3, pollInterval).ShouldNot(HaveOccurred())
return
}

Expand Down

0 comments on commit bd5f357

Please sign in to comment.