Skip to content
This repository was archived by the owner on Apr 17, 2025. It is now read-only.

Commit 977e2c5

Browse files
authored
Merge pull request #285 from erikgb/dynamic-objects-webhook
Only create per-object webhooks for configured types
2 parents a0351d2 + 36139e3 commit 977e2c5

File tree

7 files changed

+103
-53
lines changed

7 files changed

+103
-53
lines changed

config/webhook/manifests.yaml

-21
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,6 @@ webhooks:
7171
resources:
7272
- hierarchyconfigurations
7373
sideEffects: None
74-
- admissionReviewVersions:
75-
- v1
76-
clientConfig:
77-
service:
78-
name: webhook-service
79-
namespace: system
80-
path: /validate-objects
81-
failurePolicy: Fail
82-
name: objects.hnc.x-k8s.io
83-
rules:
84-
- apiGroups:
85-
- '*'
86-
apiVersions:
87-
- '*'
88-
operations:
89-
- CREATE
90-
- UPDATE
91-
- DELETE
92-
resources:
93-
- '*'
94-
sideEffects: None
9574
- admissionReviewVersions:
9675
- v1
9776
clientConfig:

config/webhook/webhook_patch.yaml

+12-17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
---
2+
# Intentionally no rules, as these are maintained by the HNCConfiguration reconciler.
3+
# At present controller-gen is unable to generate a webhook without rules from kubebuilder markers.
24
apiVersion: admissionregistration.k8s.io/v1
35
kind: ValidatingWebhookConfiguration
46
metadata:
57
name: validating-webhook-configuration
68
webhooks:
7-
- name: objects.hnc.x-k8s.io
9+
- admissionReviewVersions:
10+
- v1
11+
clientConfig:
12+
service:
13+
name: webhook-service
14+
namespace: system
15+
path: /validate-objects
16+
failurePolicy: Fail
17+
name: objects.hnc.x-k8s.io
18+
sideEffects: None
819
timeoutSeconds: 2
920
# We only apply this object validator on non-excluded namespaces, which have
1021
# the "included-namespace" label set by the HC reconciler, so that when HNC
@@ -19,19 +30,3 @@ webhooks:
1930
namespaceSelector:
2031
matchLabels:
2132
hnc.x-k8s.io/included-namespace: "true"
22-
rules:
23-
# This overwrites the rules specified in the object validator to patch object
24-
# scope of `namespaced` since there's no kubebuilder marker for `scope`.
25-
# There's no way to just patch "scope: Namespaced" to the first rule, since
26-
# `rules` takes a list of rules and we can only patch the entire `rules`.
27-
- apiGroups:
28-
- '*'
29-
apiVersions:
30-
- '*'
31-
operations:
32-
- CREATE
33-
- UPDATE
34-
- DELETE
35-
resources:
36-
- '*'
37-
scope: "Namespaced"

internal/hncconfig/reconciler.go

+41
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/go-logr/logr"
11+
apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1"
1112
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1213
"k8s.io/apimachinery/pkg/api/errors"
1314
"k8s.io/apimachinery/pkg/api/meta"
@@ -27,6 +28,7 @@ import (
2728
"sigs.k8s.io/hierarchical-namespaces/internal/forest"
2829
"sigs.k8s.io/hierarchical-namespaces/internal/objects"
2930
"sigs.k8s.io/hierarchical-namespaces/internal/stats"
31+
"sigs.k8s.io/hierarchical-namespaces/internal/webhooks"
3032
)
3133

3234
// Reconciler is responsible for determining the HNC configuration from the HNCConfiguration CR,
@@ -84,6 +86,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
8486
return ctrl.Result{}, err
8587
}
8688

89+
if err := r.syncObjectWebhookConfigs(ctx); err != nil {
90+
return ctrl.Result{}, err
91+
}
92+
8793
// Create or sync corresponding ObjectReconcilers, if needed.
8894
syncErr := r.syncObjectReconcilers(ctx, inst)
8995

@@ -232,6 +238,41 @@ func (r *Reconciler) writeSingleton(ctx context.Context, inst *api.HNCConfigurat
232238
return nil
233239
}
234240

241+
func (r *Reconciler) syncObjectWebhookConfigs(ctx context.Context) error {
242+
vwc := &apiadmissionregistrationv1.ValidatingWebhookConfiguration{}
243+
if err := r.Get(ctx, client.ObjectKey{Name: webhooks.ValidatingWebhookConfigurationName}, vwc); err != nil {
244+
return err
245+
}
246+
cleanVWC := vwc.DeepCopy()
247+
248+
for i, wh := range vwc.Webhooks {
249+
if wh.Name == webhooks.ObjectsWebhookName {
250+
vwc.Webhooks[i].Rules = objectWebhookRules(r.activeGVKMode)
251+
return r.Patch(ctx, vwc, client.MergeFrom(cleanVWC))
252+
}
253+
}
254+
return fmt.Errorf("webhook %q not found in ValidatingWebhookConfiguration %q", webhooks.ObjectsWebhookName, webhooks.ValidatingWebhookConfigurationName)
255+
}
256+
257+
func objectWebhookRules(mode gr2gvkMode) []apiadmissionregistrationv1.RuleWithOperations {
258+
// Group GR by group to make nicer rules
259+
g2r := make(map[string][]string)
260+
for gr := range mode {
261+
g2r[gr.Group] = append(g2r[gr.Group], gr.Resource)
262+
}
263+
264+
var rules []apiadmissionregistrationv1.RuleWithOperations
265+
for g, r := range g2r {
266+
rule := apiadmissionregistrationv1.RuleWithOperations{}
267+
rule.APIGroups = []string{g}
268+
rule.Resources = r
269+
rule.APIVersions = []string{"*"}
270+
rule.Operations = []apiadmissionregistrationv1.OperationType{apiadmissionregistrationv1.Create, apiadmissionregistrationv1.Update, apiadmissionregistrationv1.Delete}
271+
rules = append(rules, rule)
272+
}
273+
return rules
274+
}
275+
235276
// syncObjectReconcilers creates or syncs ObjectReconcilers.
236277
//
237278
// For newly added types in the HNC configuration, the method will create corresponding ObjectReconcilers and

internal/integtest/setup.go

+40
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,21 @@ import (
2323

2424
. "github.com/onsi/ginkgo/v2" //lint:ignore ST1001 Ignoring this for now
2525
. "github.com/onsi/gomega" //lint:ignore ST1001 Ignoring this for now
26+
apiadmissionregistrationv1 "k8s.io/api/admissionregistration/v1"
2627
corev1 "k8s.io/api/core/v1"
2728
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
"k8s.io/client-go/kubernetes/scheme"
31+
"k8s.io/utils/pointer"
2932
ctrl "sigs.k8s.io/controller-runtime"
3033
"sigs.k8s.io/controller-runtime/pkg/client"
3134
"sigs.k8s.io/controller-runtime/pkg/envtest"
3235
logf "sigs.k8s.io/controller-runtime/pkg/log"
3336
"sigs.k8s.io/controller-runtime/pkg/log/zap"
37+
"sigs.k8s.io/controller-runtime/pkg/webhook"
38+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
39+
"sigs.k8s.io/hierarchical-namespaces/internal/objects"
40+
"sigs.k8s.io/hierarchical-namespaces/internal/webhooks"
3441

3542
// +kubebuilder:scaffold:imports
3643

@@ -69,8 +76,28 @@ func HNCBeforeSuite() {
6976
SetDefaultEventuallyTimeout(time.Second * 4)
7077

7178
By("configuring test environment")
79+
sideEffectClassNone := apiadmissionregistrationv1.SideEffectClassNone
7280
testEnv = &envtest.Environment{
7381
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
82+
WebhookInstallOptions: envtest.WebhookInstallOptions{
83+
ValidatingWebhooks: []*apiadmissionregistrationv1.ValidatingWebhookConfiguration{{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: webhooks.ValidatingWebhookConfigurationName,
86+
},
87+
Webhooks: []apiadmissionregistrationv1.ValidatingWebhook{{
88+
Name: webhooks.ObjectsWebhookName,
89+
AdmissionReviewVersions: []string{"v1"},
90+
SideEffects: &sideEffectClassNone,
91+
ClientConfig: apiadmissionregistrationv1.WebhookClientConfig{
92+
Service: &apiadmissionregistrationv1.ServiceReference{
93+
Namespace: "system",
94+
Name: "webhook-service",
95+
Path: pointer.String(objects.ServingPath),
96+
},
97+
},
98+
}},
99+
}},
100+
},
74101
}
75102

76103
By("starting test environment")
@@ -94,13 +121,20 @@ func HNCBeforeSuite() {
94121
// CF: https://github.com/microsoft/azure-databricks-operator/blob/0f722a710fea06b86ecdccd9455336ca712bf775/controllers/suite_test.go
95122

96123
By("creating manager")
124+
webhookInstallOptions := &testEnv.WebhookInstallOptions
97125
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
98126
NewClient: config.NewClient(false),
99127
MetricsBindAddress: "0", // disable metrics serving since 'go test' runs multiple suites in parallel processes
100128
Scheme: scheme.Scheme,
129+
Host: webhookInstallOptions.LocalServingHost,
130+
Port: webhookInstallOptions.LocalServingPort,
131+
CertDir: webhookInstallOptions.LocalServingCertDir,
101132
})
102133
Expect(err).ToNot(HaveOccurred())
103134

135+
// Register a dummy webhook since the test control plane is to test reconcilers
136+
k8sManager.GetWebhookServer().Register(objects.ServingPath, &webhook.Admission{Handler: &allowAllHandler{}})
137+
104138
By("creating reconcilers")
105139
opts := setup.Options{
106140
MaxReconciles: 100,
@@ -125,6 +159,12 @@ func HNCBeforeSuite() {
125159
}()
126160
}
127161

162+
type allowAllHandler struct{}
163+
164+
func (a allowAllHandler) Handle(_ context.Context, _ admission.Request) admission.Response {
165+
return webhooks.Allow("All requests are allowed by allowAllHandler")
166+
}
167+
128168
func HNCAfterSuite() {
129169
if k8sManagerCancelFn != nil {
130170
k8sManagerCancelFn()

internal/objects/validator.go

-11
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,6 @@ const (
3232
ServingPath = "/validate-objects"
3333
)
3434

35-
// Note: the validating webhook FAILS CLOSE. This means that if the webhook goes
36-
// down, all further changes are forbidden. In addition, the webhook `rules`
37-
// (groups, resources, versions, verbs) specified in the below kubebuilder marker
38-
// are overwritten by the `rules` configured in config/webhook/webhook_patch.yaml,
39-
// because there's no marker for `scope` and we only want this object webhook
40-
// to work on `namespaced` objects. Please make sure you edit the webhook_patch.yaml
41-
// file if you want to change the webhook `rules` and better make the rules
42-
// here the same as what's in the webhook_patch.yaml.
43-
//
44-
// +kubebuilder:webhook:admissionReviewVersions=v1,path=/validate-objects,mutating=false,failurePolicy=fail,groups="*",resources="*",sideEffects=None,verbs=create;update;delete,versions="*",name=objects.hnc.x-k8s.io
45-
4635
type Validator struct {
4736
Log logr.Logger
4837
Forest *forest.Forest

internal/setup/webhooks.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ import (
1616
"sigs.k8s.io/hierarchical-namespaces/internal/hrq"
1717
ns "sigs.k8s.io/hierarchical-namespaces/internal/namespace" // for some reason, by default this gets imported as "namespace*s*"
1818
"sigs.k8s.io/hierarchical-namespaces/internal/objects"
19+
"sigs.k8s.io/hierarchical-namespaces/internal/webhooks"
1920
)
2021

2122
const (
2223
serviceName = "hnc-webhook-service"
23-
vwhName = "hnc-validating-webhook-configuration"
24-
mwhName = "hnc-mutating-webhook-configuration"
2524
caName = "hnc-ca"
2625
caOrganization = "hnc"
2726
secretName = "hnc-webhook-server-cert"
@@ -50,10 +49,10 @@ func ManageCerts(mgr ctrl.Manager, setupFinished chan struct{}, restartOnSecretR
5049
IsReady: setupFinished,
5150
Webhooks: []cert.WebhookInfo{{
5251
Type: cert.Validating,
53-
Name: vwhName,
52+
Name: webhooks.ValidatingWebhookConfigurationName,
5453
}, {
5554
Type: cert.Mutating,
56-
Name: mwhName,
55+
Name: webhooks.MutatingWebhookConfigurationName,
5756
}},
5857
RestartOnSecretRefresh: restartOnSecretRefresh,
5958
})

internal/webhooks/webhooks.go

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import (
1414
"sigs.k8s.io/hierarchical-namespaces/internal/config"
1515
)
1616

17+
const (
18+
ValidatingWebhookConfigurationName = "hnc-validating-webhook-configuration"
19+
MutatingWebhookConfigurationName = "hnc-mutating-webhook-configuration"
20+
21+
ObjectsWebhookName = "objects.hnc.x-k8s.io"
22+
)
23+
1724
// IsHNCServiceAccount is inspired by isGKServiceAccount from open-policy-agent/gatekeeper.
1825
func IsHNCServiceAccount(user *authnv1.UserInfo) bool {
1926
if user == nil {

0 commit comments

Comments
 (0)