Skip to content

Commit 898a84f

Browse files
davidjumaninfuden
andauthored
feat: Add Match Conditions to the validation webhook (#10554)
Co-authored-by: Nathan Fudenberg <[email protected]>
1 parent 5b5e872 commit 898a84f

File tree

11 files changed

+112
-29
lines changed

11 files changed

+112
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
changelog:
2+
- type: HELM
3+
issueLink: https://github.com/k8sgateway/k8sgateway/issues/9828
4+
resolvesIssue: false
5+
description: >-
6+
Adds support for match conditions (defined via Common Expression Language (CEL)) to the validating webhook to allow fine grained request filtering. They can be set via two new helm values :
7+
- `gateway.validation.matchConditions` on the Gloo webhook
8+
- `gateway.validation.kubeCoreMatchConditions` on the Kube webhook
9+
Note that match labels are supported from Kubernetes v1.30+ but need to be enabled in Kubernetes v1.27 to v1.30 via the AdmissionWebhookMatchConditions feature gate.

docs/content/reference/values.txt

+2
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,8 @@
628628
|gateway.validation.validationServerGrpcMaxSizeBytes|int|104857600|gRPC max message size in bytes for the gloo validation server|
629629
|gateway.validation.livenessProbeEnabled|bool||Set to true to enable a liveness probe for the gateway (default is false). You must also set the 'Probes' value to true.|
630630
|gateway.validation.fullEnvoyValidation|bool|false|enable feature which validates all final translated config against envoy Validate mode|
631+
|gateway.validation.matchConditions[].NAME|interface||Match conditions defined via Common Expression Language (CEL) that should evaluate to true for the Gloo resources webhook to be called. Used for fine-grained request filtering|
632+
|gateway.validation.kubeCoreMatchConditions[].NAME|interface||Match conditions defined via Common Expression Language (CEL) that should evaluate to true for the Kubernetes core resources webhook to be called. Used for fine-grained request filtering|
631633
|gateway.certGenJob.image.tag|string|<release_version, ex: 1.2.3>|The image tag for the container.|
632634
|gateway.certGenJob.image.repository|string|certgen|The image repository (name) for the container.|
633635
|gateway.certGenJob.image.digest|string||The container image's hash digest (e.g. 'sha256:12345...'), consumed when variant=standard.|

docs/content/static/content/osa_provided.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Name|Version|License
5656
[go.uber.org/zap](https://go.uber.org/zap)|v1.27.0|MIT License
5757
[x/crypto](https://golang.org/x/crypto)|v0.31.0|BSD 3-clause "New" or "Revised" License
5858
[x/exp](https://golang.org/x/exp)|v0.0.0-20240719175910-8a7402abbf56|BSD 3-clause "New" or "Revised" License
59+
[x/mod](https://golang.org/x/mod)|v0.21.0|BSD 3-clause "New" or "Revised" License
5960
[x/sync](https://golang.org/x/sync)|v0.10.0|BSD 3-clause "New" or "Revised" License
6061
[x/tools](https://golang.org/x/tools)|v0.24.0|BSD 3-clause "New" or "Revised" License
6162
[googleapis/api](https://google.golang.org/genproto/googleapis/api)|v0.0.0-20241021214115-324edc3d5d38|Apache License 2.0

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ require (
102102
github.com/stoewer/go-strcase v1.3.0
103103
github.com/stretchr/testify v1.9.0
104104
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
105+
golang.org/x/mod v0.21.0
105106
google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38
106107
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38
107108
istio.io/api v1.24.0-alpha.0.0.20241106042855-9e26cdd3450a
@@ -319,7 +320,6 @@ require (
319320
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
320321
go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect
321322
go.uber.org/atomic v1.11.0 // indirect
322-
golang.org/x/mod v0.21.0 // indirect
323323
golang.org/x/net v0.33.0 // indirect
324324
golang.org/x/oauth2 v0.23.0 // indirect
325325
golang.org/x/sys v0.28.0 // indirect

install/helm/gloo/generate/values.go

+16-14
Original file line numberDiff line numberDiff line change
@@ -482,20 +482,22 @@ type ServiceAccount struct {
482482
}
483483

484484
type GatewayValidation struct {
485-
Enabled *bool `json:"enabled,omitempty" desc:"enable Gloo Edge API Gateway validation hook (default true)"`
486-
AlwaysAcceptResources *bool `json:"alwaysAcceptResources,omitempty" desc:"unless this is set this to false in order to ensure validation webhook rejects invalid resources. by default, validation webhook will only log and report metrics for invalid resource admission without rejecting them outright."`
487-
AllowWarnings *bool `json:"allowWarnings,omitempty" desc:"set this to false in order to ensure validation webhook rejects resources that would have warning status or rejected status, rather than just rejected."`
488-
WarnMissingTlsSecret *bool `json:"warnMissingTlsSecret,omitempty" desc:"set this to false in order to treat missing tls secret references as errors, causing validation to fail."`
489-
ServerEnabled *bool `json:"serverEnabled,omitempty" desc:"By providing the validation field (parent of this object) the user is implicitly opting into validation. This field allows the user to opt out of the validation server, while still configuring pre-existing fields such as warn_route_short_circuiting and disable_transformation_validation."`
490-
DisableTransformationValidation *bool `json:"disableTransformationValidation,omitempty" desc:"set this to true to disable transformation validation. This may bring significant performance benefits if using many transformations, at the cost of possibly incorrect transformations being sent to Envoy. When using this value make sure to pre-validate transformations."`
491-
WarnRouteShortCircuiting *bool `json:"warnRouteShortCircuiting,omitempty" desc:"Write a warning to route resources if validation produced a route ordering warning (defaults to false). By setting to true, this means that Gloo Edge will start assigning warnings to resources that would result in route short-circuiting within a virtual host."`
492-
SecretName *string `json:"secretName,omitempty" desc:"Name of the Kubernetes Secret containing TLS certificates used by the validation webhook server. This secret will be created by the certGen Job if the certGen Job is enabled."`
493-
FailurePolicy *string `json:"failurePolicy,omitempty" desc:"Specify how to handle unrecognized errors for Gloo resources that are returned from the Gateway validation endpoint. Supported values are 'Ignore' or 'Fail'"`
494-
KubeCoreFailurePolicy *string `json:"kubeCoreFailurePolicy,omitempty" desc:"Specify how to handle unrecognized errors for Kubernetes core resources that are returned by the Gateway validation endpoint. Currently the [validation webhook](https://github.com/solo-io/gloo/blob/main/install/helm/gloo/templates/5-gateway-validation-webhook-configuration.yaml) is configured to handle errors for Kubernetes secrets and namespaces. Supported values are 'Ignore' or 'Fail'. If you set this value to 'Fail', you cannot modify these core resources if the 'gloo' service is unavailable."`
495-
Webhook *Webhook `json:"webhook,omitempty" desc:"webhook specific configuration"`
496-
ValidationServerGrpcMaxSizeBytes *int `json:"validationServerGrpcMaxSizeBytes,omitempty" desc:"gRPC max message size in bytes for the gloo validation server"`
497-
LivenessProbeEnabled *bool `json:"livenessProbeEnabled,omitempty" desc:"Set to true to enable a liveness probe for the gateway (default is false). You must also set the 'Probes' value to true."`
498-
FullEnvoyValidation *bool `json:"fullEnvoyValidation,omitempty" desc:"enable feature which validates all final translated config against envoy Validate mode"`
485+
Enabled *bool `json:"enabled,omitempty" desc:"enable Gloo Edge API Gateway validation hook (default true)"`
486+
AlwaysAcceptResources *bool `json:"alwaysAcceptResources,omitempty" desc:"unless this is set this to false in order to ensure validation webhook rejects invalid resources. by default, validation webhook will only log and report metrics for invalid resource admission without rejecting them outright."`
487+
AllowWarnings *bool `json:"allowWarnings,omitempty" desc:"set this to false in order to ensure validation webhook rejects resources that would have warning status or rejected status, rather than just rejected."`
488+
WarnMissingTlsSecret *bool `json:"warnMissingTlsSecret,omitempty" desc:"set this to false in order to treat missing tls secret references as errors, causing validation to fail."`
489+
ServerEnabled *bool `json:"serverEnabled,omitempty" desc:"By providing the validation field (parent of this object) the user is implicitly opting into validation. This field allows the user to opt out of the validation server, while still configuring pre-existing fields such as warn_route_short_circuiting and disable_transformation_validation."`
490+
DisableTransformationValidation *bool `json:"disableTransformationValidation,omitempty" desc:"set this to true to disable transformation validation. This may bring significant performance benefits if using many transformations, at the cost of possibly incorrect transformations being sent to Envoy. When using this value make sure to pre-validate transformations."`
491+
WarnRouteShortCircuiting *bool `json:"warnRouteShortCircuiting,omitempty" desc:"Write a warning to route resources if validation produced a route ordering warning (defaults to false). By setting to true, this means that Gloo Edge will start assigning warnings to resources that would result in route short-circuiting within a virtual host."`
492+
SecretName *string `json:"secretName,omitempty" desc:"Name of the Kubernetes Secret containing TLS certificates used by the validation webhook server. This secret will be created by the certGen Job if the certGen Job is enabled."`
493+
FailurePolicy *string `json:"failurePolicy,omitempty" desc:"Specify how to handle unrecognized errors for Gloo resources that are returned from the Gateway validation endpoint. Supported values are 'Ignore' or 'Fail'"`
494+
KubeCoreFailurePolicy *string `json:"kubeCoreFailurePolicy,omitempty" desc:"Specify how to handle unrecognized errors for Kubernetes core resources that are returned by the Gateway validation endpoint. Currently the [validation webhook](https://github.com/solo-io/gloo/blob/main/install/helm/gloo/templates/5-gateway-validation-webhook-configuration.yaml) is configured to handle errors for Kubernetes secrets and namespaces. Supported values are 'Ignore' or 'Fail'. If you set this value to 'Fail', you cannot modify these core resources if the 'gloo' service is unavailable."`
495+
Webhook *Webhook `json:"webhook,omitempty" desc:"webhook specific configuration"`
496+
ValidationServerGrpcMaxSizeBytes *int `json:"validationServerGrpcMaxSizeBytes,omitempty" desc:"gRPC max message size in bytes for the gloo validation server"`
497+
LivenessProbeEnabled *bool `json:"livenessProbeEnabled,omitempty" desc:"Set to true to enable a liveness probe for the gateway (default is false). You must also set the 'Probes' value to true."`
498+
FullEnvoyValidation *bool `json:"fullEnvoyValidation,omitempty" desc:"enable feature which validates all final translated config against envoy Validate mode"`
499+
MatchConditions []map[string]interface{} `json:"matchConditions,omitempty" desc:"Match conditions defined via Common Expression Language (CEL) that should evaluate to true for the Gloo resources webhook to be called. Used for fine-grained request filtering"`
500+
KubeCoreMatchConditions []map[string]interface{} `json:"kubeCoreMatchConditions,omitempty" desc:"Match conditions defined via Common Expression Language (CEL) that should evaluate to true for the Kubernetes core resources webhook to be called. Used for fine-grained request filtering"`
499501
}
500502

501503
type Webhook struct {

install/helm/gloo/templates/5-gateway-validation-webhook-configuration.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ specific resources, we will manage the resources that the webhook receives via t
6060
matchPolicy: Exact
6161
{{- if .Values.gateway.validation.webhook.timeoutSeconds }}
6262
timeoutSeconds: {{ .Values.gateway.validation.webhook.timeoutSeconds }}
63+
{{- end }}
64+
{{- with .Values.gateway.validation.matchConditions }}
65+
matchConditions:
66+
{{- toYaml . | nindent 4 }}
6367
{{- end }}
6468
admissionReviewVersions:
6569
- v1beta1 # v1beta1 still live in 1.22 https://github.com/kubernetes/api/blob/release-1.22/admission/v1beta1/types.go#L33
@@ -68,7 +72,7 @@ specific resources, we will manage the resources that the webhook receives via t
6872
{{- end }} {{- /* if .Values.gateway.validation.failurePolicy */}}
6973

7074
{{/* Webhook for core resources - only render if we need to */}}
71-
{{- if and
75+
{{- if and
7276
(not (has "*" .Values.gateway.validation.webhook.skipDeleteValidationResources))
7377
(or (not (has "secrets" .Values.gateway.validation.webhook.skipDeleteValidationResources))
7478
(not (has "namespaces" .Values.gateway.validation.webhook.skipDeleteValidationResources)))
@@ -99,6 +103,10 @@ specific resources, we will manage the resources that the webhook receives via t
99103
matchPolicy: Exact
100104
{{- if .Values.gateway.validation.webhook.timeoutSeconds }}
101105
timeoutSeconds: {{ .Values.gateway.validation.webhook.timeoutSeconds }}
106+
{{- end }}
107+
{{- with .Values.gateway.validation.kubeCoreMatchConditions }}
108+
matchConditions:
109+
{{- toYaml . | nindent 4 }}
102110
{{- end }}
103111
admissionReviewVersions:
104112
- v1beta1 # v1beta1 still live in 1.22 https://github.com/kubernetes/api/blob/release-1.22/admission/v1beta1/types.go#L33

pkg/utils/kubeutils/kubectl/cli.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kubectl
33
import (
44
"bytes"
55
"context"
6+
"encoding/json"
67
"fmt"
78
"io"
89
"os"
@@ -11,13 +12,14 @@ import (
1112
"time"
1213

1314
"k8s.io/apimachinery/pkg/types"
15+
"k8s.io/kubectl/pkg/cmd/version"
1416

1517
"github.com/solo-io/gloo/pkg/utils/cmdutils"
18+
"github.com/solo-io/gloo/pkg/utils/kubeutils/portforward"
1619
"github.com/solo-io/gloo/pkg/utils/requestutils/curl"
1720
"github.com/solo-io/k8s-utils/testutils/kube"
1821

1922
"github.com/avast/retry-go/v4"
20-
"github.com/solo-io/gloo/pkg/utils/kubeutils/portforward"
2123
)
2224

2325
// Cli is a utility for executing `kubectl` commands
@@ -373,3 +375,14 @@ func (c *Cli) GetPodsInNsWithLabel(ctx context.Context, namespace string, label
373375
glooPodNames := strings.Fields(glooPodNamesString)
374376
return glooPodNames, nil
375377
}
378+
379+
// Version returns the unmarshalled output of `kubectl version -o json`
380+
func (c *Cli) Version(ctx context.Context) (version.Version, error) {
381+
ver := version.Version{}
382+
out, _, err := c.Execute(ctx, "version", "-o", "json")
383+
if err != nil {
384+
return ver, err
385+
}
386+
err = json.Unmarshal([]byte(out), &ver)
387+
return ver, err
388+
}

test/kubernetes/e2e/features/validation/split_webhook/suite.go

+38-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/solo-io/gloo/test/kubernetes/testutils/helper"
1414
"github.com/solo-io/solo-kit/pkg/api/v1/clients"
1515
"github.com/solo-io/solo-kit/pkg/api/v1/resources"
16+
"golang.org/x/mod/semver"
1617
"sigs.k8s.io/controller-runtime/pkg/client"
1718

1819
"github.com/stretchr/testify/suite"
@@ -62,6 +63,8 @@ func (s *testingSuite) SetupSuite() {
6263
}
6364

6465
func (s *testingSuite) BeforeTest(suiteName, testName string) {
66+
s.skipUnsupportedTests(testName)
67+
6568
// Apply the upgrade values file
6669
var err error
6770
s.rollback, err = s.testHelper.UpgradeGloo(s.ctx, 600*time.Second, helper.WithExtraArgs([]string{
@@ -99,6 +102,8 @@ func (s *testingSuite) BeforeTest(suiteName, testName string) {
99102
}
100103

101104
func (s *testingSuite) AfterTest(suiteName, testName string) {
105+
s.skipUnsupportedTests(testName)
106+
102107
// Scale gloo deployment back to original replica count
103108
err := s.testInstallation.Actions.Kubectl().Scale(s.ctx, s.testInstallation.Metadata.InstallNamespace, "deployment/gloo", uint(s.glooReplicas))
104109
s.Assert().NoError(err, "can scale gloo deployment back to %d", s.glooReplicas)
@@ -161,6 +166,14 @@ func (s *testingSuite) TestKubeFailurePolicyIgnore() {
161166
s.testDeleteResource(validation.Secret, true)
162167
}
163168

169+
func (s *testingSuite) TestGlooFailurePolicyMatchConditions() {
170+
s.testDeleteResource(validation.BasicUpstream, true)
171+
}
172+
173+
func (s *testingSuite) TestKubeFailurePolicyMatchConditions() {
174+
s.testDeleteResource(validation.Secret, true)
175+
}
176+
164177
func (s *testingSuite) testDeleteResource(fileName string, shouldDelete bool) {
165178
output, err := s.testInstallation.Actions.Kubectl().DeleteFileWithOutput(s.ctx, fileName, "-n", s.testInstallation.Metadata.InstallNamespace)
166179

@@ -172,14 +185,29 @@ func (s *testingSuite) testDeleteResource(fileName string, shouldDelete bool) {
172185
s.Assert().Error(err)
173186
s.Assert().Contains(output, "Internal error occurred: failed calling webhook")
174187
}
188+
}
175189

190+
func (s *testingSuite) skipUnsupportedTests(testName string) {
191+
// Skip the MatchCondition tests as they are supported only in k8s v1.30+
192+
if strings.Contains(testName, "MatchConditions") {
193+
ver, _ := s.testInstallation.Actions.Kubectl().Version(s.ctx)
194+
serverVersion := ver.ServerVersion.GitVersion
195+
// This handles scenarios where the server version is invalid or the prior command returns an error
196+
// semver.Compare("v1.30.0", "") = 1
197+
// semver.Compare("v1.30.0", "v1.28.8") = 1
198+
if semver.Compare("v1.30.0", serverVersion) == 1 {
199+
s.T().Skip(fmt.Sprintf("Skipping %s as the k8s version %s is below the required version (v1.30.0+)", testName, serverVersion))
200+
}
201+
}
176202
}
177203

178204
var upgradeValues = map[string]string{
179-
"TestGlooFailurePolicyFail": validation.GlooFailurePolicyFailValues,
180-
"TestKubeFailurePolicyFail": validation.KubeFailurePolicyFailValues,
181-
"TestGlooFailurePolicyIgnore": validation.GlooFailurePolicyIgnoreValues,
182-
"TestKubeFailurePolicyIgnore": validation.KubeFailurePolicyIgnoreValues,
205+
"TestGlooFailurePolicyFail": validation.GlooFailurePolicyFailValues,
206+
"TestKubeFailurePolicyFail": validation.KubeFailurePolicyFailValues,
207+
"TestGlooFailurePolicyIgnore": validation.GlooFailurePolicyIgnoreValues,
208+
"TestKubeFailurePolicyIgnore": validation.KubeFailurePolicyIgnoreValues,
209+
"TestGlooFailurePolicyMatchConditions": validation.GlooFailurePolicyMatchConditions,
210+
"TestKubeFailurePolicyMatchConditions": validation.KubeFailurePolicyMatchConditions,
183211
}
184212

185213
// These tests create one resource and try to delete it, so don't need lists of resources
@@ -223,9 +251,11 @@ var (
223251
}
224252

225253
manifests = map[string]*testManifest{
226-
"TestGlooFailurePolicyFail": upstreamManifest,
227-
"TestGlooFailurePolicyIgnore": upstreamManifest,
228-
"TestKubeFailurePolicyFail": secretManifest,
229-
"TestKubeFailurePolicyIgnore": secretManifest,
254+
"TestGlooFailurePolicyFail": upstreamManifest,
255+
"TestGlooFailurePolicyIgnore": upstreamManifest,
256+
"TestGlooFailurePolicyMatchConditions": upstreamManifest,
257+
"TestKubeFailurePolicyFail": secretManifest,
258+
"TestKubeFailurePolicyIgnore": secretManifest,
259+
"TestKubeFailurePolicyMatchConditions": secretManifest,
230260
}
231261
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
gateway:
2+
validation:
3+
failurePolicy: Fail # For "strict" validation mode, fail the validation if webhook server is not available
4+
matchConditions:
5+
- name: skip-upstreams
6+
expression: '!(request.resource.group == "gloo.solo.io" && request.resource.resource == "upstreams")' # Match non-upstream resources.
7+
webhook:
8+
skipDeleteValidationResources: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
gateway:
2+
validation:
3+
kubeCoreFailurePolicy: Fail # For "strict" validation mode, fail the validation if webhook server is not available
4+
kubeCoreMatchConditions:
5+
- name: skip-secrets
6+
expression: '!(request.resource.group == "" && request.resource.resource == "secrets")' # Match non-secret resources.
7+
webhook:
8+
skipDeleteValidationResources: []

0 commit comments

Comments
 (0)