Skip to content

Commit d121ff1

Browse files
authored
Chore/refactor webhook validation (#510)
- Move webhook validation code to the state package so that the package is responsible for enforcing it. As a result, it will not need require the users of that package to validate the supplied Gateway API resources, which will make it easier to use the package and reduce the number of possible bugs. Fixes #416 - Fix Webhook Validation is bypassed for existing resources when NKG starts. Fixes #433
1 parent 500eec5 commit d121ff1

14 files changed

+737
-485
lines changed

internal/manager/controllers.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ type controllerConfig struct {
2626
k8sPredicate predicate.Predicate
2727
fieldIndices index.FieldIndices
2828
newReconciler newReconcilerFunc
29-
webhookValidator reconciler.ValidatorFunc
3029
}
3130

3231
type controllerOption func(*controllerConfig)
@@ -56,12 +55,6 @@ func withNewReconciler(newReconciler newReconcilerFunc) controllerOption {
5655
}
5756
}
5857

59-
func withWebhookValidator(validator reconciler.ValidatorFunc) controllerOption {
60-
return func(cfg *controllerConfig) {
61-
cfg.webhookValidator = validator
62-
}
63-
}
64-
6558
func defaultControllerConfig() controllerConfig {
6659
return controllerConfig{
6760
newReconciler: reconciler.NewImplementation,
@@ -73,7 +66,6 @@ func registerController(
7366
objectType client.Object,
7467
mgr manager.Manager,
7568
eventCh chan<- interface{},
76-
recorder reconciler.EventRecorder,
7769
options ...controllerOption,
7870
) error {
7971
cfg := defaultControllerConfig()
@@ -100,8 +92,6 @@ func registerController(
10092
ObjectType: objectType,
10193
EventCh: eventCh,
10294
NamespacedNameFilter: cfg.namespacedNameFilter,
103-
WebhookValidator: cfg.webhookValidator,
104-
EventRecorder: recorder,
10595
}
10696

10797
err := builder.Complete(cfg.newReconciler(recCfg))

internal/manager/controllers_test.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/onsi/gomega/types"
1212
"k8s.io/apimachinery/pkg/runtime"
1313
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
14-
"k8s.io/apimachinery/pkg/util/validation/field"
1514
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1615
"sigs.k8s.io/controller-runtime/pkg/log/zap"
1716
"sigs.k8s.io/gateway-api/apis/v1beta1"
@@ -21,7 +20,6 @@ import (
2120
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/managerfakes"
2221
"github.com/nginxinc/nginx-kubernetes-gateway/internal/manager/predicate"
2322
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler"
24-
"github.com/nginxinc/nginx-kubernetes-gateway/internal/reconciler/reconcilerfakes"
2523
)
2624

2725
func TestRegisterController(t *testing.T) {
@@ -86,12 +84,6 @@ func TestRegisterController(t *testing.T) {
8684
namespacedNameFilter := filter.CreateFilterForGatewayClass("test")
8785
fieldIndexes := index.CreateEndpointSliceFieldIndices()
8886

89-
webhookValidator := createValidator(func(_ *v1beta1.HTTPRoute) field.ErrorList {
90-
return nil
91-
})
92-
93-
eventRecorder := &reconcilerfakes.FakeEventRecorder{}
94-
9587
eventCh := make(chan<- interface{})
9688

9789
beSameFunctionPointer := func(expected interface{}) types.GomegaMatcher {
@@ -109,8 +101,6 @@ func TestRegisterController(t *testing.T) {
109101
g.Expect(c.Getter).To(BeIdenticalTo(test.fakes.mgr.GetClient()))
110102
g.Expect(c.ObjectType).To(BeIdenticalTo(objectType))
111103
g.Expect(c.EventCh).To(BeIdenticalTo(eventCh))
112-
g.Expect(c.EventRecorder).To(BeIdenticalTo(eventRecorder))
113-
g.Expect(c.WebhookValidator).Should(beSameFunctionPointer(webhookValidator))
114104
g.Expect(c.NamespacedNameFilter).Should(beSameFunctionPointer(namespacedNameFilter))
115105

116106
return reconciler.NewImplementation(c)
@@ -121,12 +111,10 @@ func TestRegisterController(t *testing.T) {
121111
objectType,
122112
test.fakes.mgr,
123113
eventCh,
124-
eventRecorder,
125114
withNamespacedNameFilter(namespacedNameFilter),
126115
withK8sPredicate(predicate.ServicePortsChangedPredicate{}),
127116
withFieldIndices(fieldIndexes),
128117
withNewReconciler(newReconciler),
129-
withWebhookValidator(webhookValidator),
130118
)
131119

132120
if test.expectedErr == nil {

internal/manager/manager.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"sigs.k8s.io/controller-runtime/pkg/manager"
1515
k8spredicate "sigs.k8s.io/controller-runtime/pkg/predicate"
1616
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
17-
gwapivalidation "sigs.k8s.io/gateway-api/apis/v1beta1/validation"
1817

1918
"github.com/nginxinc/nginx-kubernetes-gateway/internal/config"
2019
"github.com/nginxinc/nginx-kubernetes-gateway/internal/events"
@@ -75,21 +74,13 @@ func Start(cfg config.Config) error {
7574
objectType: &gatewayv1beta1.GatewayClass{},
7675
options: []controllerOption{
7776
withNamespacedNameFilter(filter.CreateFilterForGatewayClass(cfg.GatewayClassName)),
78-
// as of v0.6.2, the Gateway API Webhook doesn't include a validation function
79-
// for the GatewayClass resource
8077
},
8178
},
8279
{
8380
objectType: &gatewayv1beta1.Gateway{},
84-
options: []controllerOption{
85-
withWebhookValidator(createValidator(gwapivalidation.ValidateGateway)),
86-
},
8781
},
8882
{
8983
objectType: &gatewayv1beta1.HTTPRoute{},
90-
options: []controllerOption{
91-
withWebhookValidator(createValidator(gwapivalidation.ValidateHTTPRoute)),
92-
},
9384
},
9485
{
9586
objectType: &apiv1.Service{},
@@ -111,11 +102,8 @@ func Start(cfg config.Config) error {
111102

112103
ctx := ctlr.SetupSignalHandler()
113104

114-
recorderName := fmt.Sprintf("nginx-kubernetes-gateway-%s", cfg.GatewayClassName)
115-
recorder := mgr.GetEventRecorderFor(recorderName)
116-
117105
for _, regCfg := range controllerRegCfgs {
118-
err := registerController(ctx, regCfg.objectType, mgr, eventCh, recorder, regCfg.options...)
106+
err := registerController(ctx, regCfg.objectType, mgr, eventCh, regCfg.options...)
119107
if err != nil {
120108
return fmt.Errorf("cannot register controller for %T: %w", regCfg.objectType, err)
121109
}
@@ -124,6 +112,9 @@ func Start(cfg config.Config) error {
124112
secretStore := secrets.NewSecretStore()
125113
secretMemoryMgr := secrets.NewSecretDiskMemoryManager(secretsFolder, secretStore)
126114

115+
recorderName := fmt.Sprintf("nginx-kubernetes-gateway-%s", cfg.GatewayClassName)
116+
recorder := mgr.GetEventRecorderFor(recorderName)
117+
127118
processor := state.NewChangeProcessorImpl(state.ChangeProcessorConfig{
128119
GatewayCtlrName: cfg.GatewayCtlrName,
129120
GatewayClassName: cfg.GatewayClassName,
@@ -134,6 +125,8 @@ func Start(cfg config.Config) error {
134125
Validators: validation.Validators{
135126
HTTPFieldsValidator: ngxvalidation.HTTPValidator{},
136127
},
128+
EventRecorder: recorder,
129+
Scheme: scheme,
137130
})
138131

139132
configGenerator := ngxcfg.NewGeneratorImpl()

internal/manager/validators.go

Lines changed: 0 additions & 27 deletions
This file was deleted.

internal/manager/validators_test.go

Lines changed: 0 additions & 77 deletions
This file was deleted.

internal/reconciler/implementation.go

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"reflect"
77

8-
apiv1 "k8s.io/api/core/v1"
98
apierrors "k8s.io/apimachinery/pkg/api/errors"
109
"k8s.io/apimachinery/pkg/types"
1110
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -19,9 +18,6 @@ import (
1918
// If the function returns false, the reconciler will log the returned string.
2019
type NamespacedNameFilterFunc func(nsname types.NamespacedName) (bool, string)
2120

22-
// ValidatorFunc validates a Kubernetes resource.
23-
type ValidatorFunc func(object client.Object) error
24-
2521
// Config contains the configuration for the Implementation.
2622
type Config struct {
2723
// Getter gets a resource from the k8s API.
@@ -32,10 +28,6 @@ type Config struct {
3228
EventCh chan<- interface{}
3329
// NamespacedNameFilter filters resources the controller will process. Can be nil.
3430
NamespacedNameFilter NamespacedNameFilterFunc
35-
// WebhookValidator validates a resource using the same rules as in the Gateway API Webhook. Can be nil.
36-
WebhookValidator ValidatorFunc
37-
// EventRecorder records event about resources.
38-
EventRecorder EventRecorder
3931
}
4032

4133
// Implementation is a reconciler for Kubernetes resources.
@@ -66,12 +58,6 @@ func newObject(objectType client.Object) client.Object {
6658
return reflect.New(t).Interface().(client.Object)
6759
}
6860

69-
const (
70-
webhookValidationErrorLogMsg = "Rejected the resource because the Gateway API webhook failed to reject it with " +
71-
"a validation error; make sure the webhook is installed and running correctly; " +
72-
"NKG will delete any existing NGINX configuration that corresponds to the resource"
73-
)
74-
7561
// Reconcile implements the reconcile.Reconciler Reconcile method.
7662
func (r *Implementation) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
7763
logger := log.FromContext(ctx)
@@ -98,22 +84,10 @@ func (r *Implementation) Reconcile(ctx context.Context, req reconcile.Request) (
9884
obj = nil
9985
}
10086

101-
var validationError error
102-
if obj != nil && r.cfg.WebhookValidator != nil {
103-
validationError = r.cfg.WebhookValidator(obj)
104-
}
105-
106-
if validationError != nil {
107-
logger.Error(validationError, webhookValidationErrorLogMsg)
108-
r.cfg.EventRecorder.Eventf(obj, apiv1.EventTypeWarning, "Rejected",
109-
webhookValidationErrorLogMsg+"; validation error: %v", validationError)
110-
}
111-
11287
var e interface{}
11388
var op string
11489

115-
if obj == nil || validationError != nil {
116-
// In case of a validation error, we handle the resource as if it was deleted.
90+
if obj == nil {
11791
e = &events.DeleteEvent{
11892
Type: r.cfg.ObjectType,
11993
NamespacedName: req.NamespacedName,

0 commit comments

Comments
 (0)