From b860eb798b51e4908024259050839956271d5045 Mon Sep 17 00:00:00 2001 From: jdesouza Date: Fri, 28 Aug 2026 10:39:40 -0300 Subject: [PATCH] Add support to Gateway checks in Polaris --- docs/checks/security.md | 17 + pkg/config/checks.go | 9 + .../checks/gatewayAllowedRoutesAll.yaml | 25 + .../gatewayCrossNamespaceCertificateRef.yaml | 6 + .../gatewayInsecureFrontendValidation.yaml | 40 ++ pkg/config/checks/gatewayTLSMissing.yaml | 41 ++ .../checks/httpRouteBackendTLSMissing.yaml | 9 + .../httpRouteCrossNamespaceBackendRef.yaml | 6 + .../checks/httpRouteInsecureListener.yaml | 6 + .../checks/httpRouteWildcardOrEmptyHost.yaml | 19 + ...gatewayBackendTLSVerificationDisabled.yaml | 17 + pkg/config/default.yaml | 9 + pkg/config/examples/config-full.yaml | 9 + pkg/config/schema.go | 1 + pkg/kube/resources.go | 20 +- pkg/kube/resources_test.go | 42 ++ pkg/validator/gateway_validator.go | 485 ++++++++++++++++++ .../gatewayAllowedRoutesAll/failure.yaml | 13 + .../gatewayAllowedRoutesAll/success.yaml | 16 + .../failure.yaml | 15 + .../success.yaml | 30 ++ .../failure.yaml | 15 + .../success.yaml | 15 + .../failure.missing-tls.yaml | 10 + test/checks/gatewayTLSMissing/failure.yaml | 13 + test/checks/gatewayTLSMissing/success.yaml | 22 + .../httpRouteBackendTLSMissing/failure.yaml | 12 + .../success.kgateway.yaml | 39 ++ .../httpRouteBackendTLSMissing/success.yaml | 26 + .../failure.yaml | 13 + .../success.yaml | 28 + .../httpRouteInsecureListener/failure.yaml | 31 ++ .../success.unattached-http.yaml | 31 ++ .../httpRouteInsecureListener/success.yaml | 37 ++ .../failure.empty.yaml | 9 + .../httpRouteWildcardOrEmptyHost/failure.yaml | 11 + .../httpRouteWildcardOrEmptyHost/success.yaml | 11 + .../failure.yaml | 11 + .../success.yaml | 12 + 39 files changed, 1176 insertions(+), 5 deletions(-) create mode 100644 pkg/config/checks/gatewayAllowedRoutesAll.yaml create mode 100644 pkg/config/checks/gatewayCrossNamespaceCertificateRef.yaml create mode 100644 pkg/config/checks/gatewayInsecureFrontendValidation.yaml create mode 100644 pkg/config/checks/gatewayTLSMissing.yaml create mode 100644 pkg/config/checks/httpRouteBackendTLSMissing.yaml create mode 100644 pkg/config/checks/httpRouteCrossNamespaceBackendRef.yaml create mode 100644 pkg/config/checks/httpRouteInsecureListener.yaml create mode 100644 pkg/config/checks/httpRouteWildcardOrEmptyHost.yaml create mode 100644 pkg/config/checks/kgatewayBackendTLSVerificationDisabled.yaml create mode 100644 pkg/validator/gateway_validator.go create mode 100644 test/checks/gatewayAllowedRoutesAll/failure.yaml create mode 100644 test/checks/gatewayAllowedRoutesAll/success.yaml create mode 100644 test/checks/gatewayCrossNamespaceCertificateRef/failure.yaml create mode 100644 test/checks/gatewayCrossNamespaceCertificateRef/success.yaml create mode 100644 test/checks/gatewayInsecureFrontendValidation/failure.yaml create mode 100644 test/checks/gatewayInsecureFrontendValidation/success.yaml create mode 100644 test/checks/gatewayTLSMissing/failure.missing-tls.yaml create mode 100644 test/checks/gatewayTLSMissing/failure.yaml create mode 100644 test/checks/gatewayTLSMissing/success.yaml create mode 100644 test/checks/httpRouteBackendTLSMissing/failure.yaml create mode 100644 test/checks/httpRouteBackendTLSMissing/success.kgateway.yaml create mode 100644 test/checks/httpRouteBackendTLSMissing/success.yaml create mode 100644 test/checks/httpRouteCrossNamespaceBackendRef/failure.yaml create mode 100644 test/checks/httpRouteCrossNamespaceBackendRef/success.yaml create mode 100644 test/checks/httpRouteInsecureListener/failure.yaml create mode 100644 test/checks/httpRouteInsecureListener/success.unattached-http.yaml create mode 100644 test/checks/httpRouteInsecureListener/success.yaml create mode 100644 test/checks/httpRouteWildcardOrEmptyHost/failure.empty.yaml create mode 100644 test/checks/httpRouteWildcardOrEmptyHost/failure.yaml create mode 100644 test/checks/httpRouteWildcardOrEmptyHost/success.yaml create mode 100644 test/checks/kgatewayBackendTLSVerificationDisabled/failure.yaml create mode 100644 test/checks/kgatewayBackendTLSVerificationDisabled/success.yaml diff --git a/docs/checks/security.md b/docs/checks/security.md index f2024a82..53c23e94 100644 --- a/docs/checks/security.md +++ b/docs/checks/security.md @@ -24,6 +24,15 @@ key | default | description `hostNetworkSet` | `warning` | Fails when `hostNetwork` attribute is configured. `hostPortSet` | `warning` | Fails when `hostPort` attribute is configured. `tlsSettingsMissing` | `warning` | Fails when an Ingress lacks TLS settings. +`gatewayTLSMissing` | `warning` | Fails when an HTTPS, GRPC, or terminating TLS Gateway listener lacks certificate references. +`gatewayAllowedRoutesAll` | `warning` | Fails when a Gateway listener allows Routes from every namespace. +`gatewayInsecureFrontendValidation` | `warning` | Fails when Gateway frontend client certificate validation allows insecure fallback. +`gatewayCrossNamespaceCertificateRef` | `warning` | Fails when a Gateway references a certificate in another namespace without a matching ReferenceGrant. Cluster audits only. +`httpRouteWildcardOrEmptyHost` | `warning` | Fails when an HTTPRoute omits hostnames or uses a wildcard hostname. +`httpRouteInsecureListener` | `warning` | Fails when an HTTPRoute serves application traffic over HTTP without a full HTTPS redirect. Cluster audits only. +`httpRouteCrossNamespaceBackendRef` | `warning` | Fails when an HTTPRoute references a backend in another namespace without a matching ReferenceGrant. Cluster audits only. +`httpRouteBackendTLSMissing` | `warning` | Fails when an HTTPRoute TLS backend lacks a BackendTLSPolicy or kgateway BackendConfigPolicy. Cluster audits only. +`kgatewayBackendTLSVerificationDisabled` | `warning` | Fails when a kgateway BackendConfigPolicy disables TLS certificate verification. `sensitiveContainerEnvVar` | `danger` | Fails when the container sets potentially sensitive environment variables. `sensitiveConfigmapContent` | `danger` | Fails when potentially sensitive content is detected in the ConfigMap keys or values. `missingNetworkPolicy` | `warning` @@ -40,6 +49,14 @@ key | default | description Securing workloads in Kubernetes is an important part of overall cluster security. The overall goal should be to ensure that containers are running with as minimal privileges as possible. This includes avoiding privilege escalation, not running containers with a root user, not giving excessive access to the host network, and using read only file systems wherever possible. +### Gateway API + +Gateway API separates listeners, routes, and backend TLS policy across different resources. Polaris checks standard `Gateway` and `HTTPRoute` resources for listener TLS, namespace isolation, host specificity, HTTPS redirects, cross-namespace authorization, and backend TLS. These checks work with conformant implementations such as kgateway. + +`httpRouteBackendTLSMissing` also recognizes kgateway's `Backend` and `BackendConfigPolicy` resources. It identifies TLS backends from ports 443 and 8443, Service port names and `appProtocol`, and kgateway static Backend ports. `kgatewayBackendTLSVerificationDisabled` checks the kgateway-specific `insecureSkipVerify` setting. Authentication, authorization, and rate-limiting requirements are organization-specific and should be implemented as custom checks. + +Checks marked "Cluster audits only" need related resources that are not available when Polaris evaluates a single admission request. They pass without a resource provider rather than rejecting an object without enough context. + A pod running with the `hostNetwork` attribute enabled will have access to the loopback device, services listening on localhost, and could be used to snoop on network activity of other pods on the same node. There are certain examples where setting `hostNetwork` to true is required, such as deploying a networking plugin like Flannel. Setting the `hostPort` attribute on a container will ensure that it is accessible on that specific port on each node it is deployed to. Unfortunately when this is specified, it limits where a pod can actually be scheduled in a cluster. diff --git a/pkg/config/checks.go b/pkg/config/checks.go index 0888755a..430f6363 100644 --- a/pkg/config/checks.go +++ b/pkg/config/checks.go @@ -57,6 +57,15 @@ var ( "sensitiveContainerEnvVar", // Other checks "tlsSettingsMissing", + "gatewayTLSMissing", + "gatewayAllowedRoutesAll", + "gatewayInsecureFrontendValidation", + "gatewayCrossNamespaceCertificateRef", + "httpRouteWildcardOrEmptyHost", + "httpRouteInsecureListener", + "httpRouteCrossNamespaceBackendRef", + "httpRouteBackendTLSMissing", + "kgatewayBackendTLSVerificationDisabled", "pdbDisruptionsIsZero", "metadataAndInstanceMismatched", "missingPodDisruptionBudget", diff --git a/pkg/config/checks/gatewayAllowedRoutesAll.yaml b/pkg/config/checks/gatewayAllowedRoutesAll.yaml new file mode 100644 index 00000000..650ea717 --- /dev/null +++ b/pkg/config/checks/gatewayAllowedRoutesAll.yaml @@ -0,0 +1,25 @@ +successMessage: Gateway listeners restrict route attachment by namespace +failureMessage: Gateway listeners should not allow routes from all namespaces +category: Security +target: gateway.networking.k8s.io/Gateway +schema: + '$schema': https://json-schema.org/draft/2019-09/schema + type: object + properties: + spec: + type: object + properties: + listeners: + type: array + items: + type: object + properties: + allowedRoutes: + type: object + properties: + namespaces: + type: object + properties: + from: + not: + const: All diff --git a/pkg/config/checks/gatewayCrossNamespaceCertificateRef.yaml b/pkg/config/checks/gatewayCrossNamespaceCertificateRef.yaml new file mode 100644 index 00000000..13f5e806 --- /dev/null +++ b/pkg/config/checks/gatewayCrossNamespaceCertificateRef.yaml @@ -0,0 +1,6 @@ +successMessage: Gateway cross-namespace certificate references are authorized +failureMessage: Gateway cross-namespace certificate references should have a matching ReferenceGrant +category: Security +target: gateway.networking.k8s.io/Gateway +relatedKinds: + - gateway.networking.k8s.io/ReferenceGrant diff --git a/pkg/config/checks/gatewayInsecureFrontendValidation.yaml b/pkg/config/checks/gatewayInsecureFrontendValidation.yaml new file mode 100644 index 00000000..c16bea2d --- /dev/null +++ b/pkg/config/checks/gatewayInsecureFrontendValidation.yaml @@ -0,0 +1,40 @@ +successMessage: Gateway frontend client certificate validation fails closed +failureMessage: Gateway frontend client certificate validation should not allow insecure fallback +category: Security +target: gateway.networking.k8s.io/Gateway +schema: + '$schema': https://json-schema.org/draft/2019-09/schema + type: object + properties: + spec: + type: object + properties: + tls: + type: object + properties: + frontend: + type: object + properties: + default: + type: object + properties: + validation: + type: object + properties: + mode: + not: + const: AllowInsecureFallback + perPort: + type: array + items: + type: object + properties: + tls: + type: object + properties: + validation: + type: object + properties: + mode: + not: + const: AllowInsecureFallback diff --git a/pkg/config/checks/gatewayTLSMissing.yaml b/pkg/config/checks/gatewayTLSMissing.yaml new file mode 100644 index 00000000..9fbc29be --- /dev/null +++ b/pkg/config/checks/gatewayTLSMissing.yaml @@ -0,0 +1,41 @@ +successMessage: Gateway TLS listeners have certificates configured +failureMessage: Gateway HTTPS, GRPC, and terminating TLS listeners should configure certificateRefs +category: Security +target: gateway.networking.k8s.io/Gateway +schema: + '$schema': https://json-schema.org/draft/2019-09/schema + type: object + properties: + spec: + type: object + properties: + listeners: + type: array + items: + type: object + if: + anyOf: + - required: [protocol] + properties: + protocol: + enum: [HTTPS, GRPC] + - required: [protocol] + properties: + protocol: + const: TLS + tls: + type: object + properties: + mode: + not: + const: Passthrough + then: + required: [tls] + properties: + tls: + type: object + required: [certificateRefs] + properties: + certificateRefs: + type: array + minItems: 1 diff --git a/pkg/config/checks/httpRouteBackendTLSMissing.yaml b/pkg/config/checks/httpRouteBackendTLSMissing.yaml new file mode 100644 index 00000000..ab9803c6 --- /dev/null +++ b/pkg/config/checks/httpRouteBackendTLSMissing.yaml @@ -0,0 +1,9 @@ +successMessage: HTTPRoute TLS backends have TLS origination configured +failureMessage: HTTPRoute backends on TLS ports should have a BackendTLSPolicy or kgateway BackendConfigPolicy +category: Security +target: gateway.networking.k8s.io/HTTPRoute +relatedKinds: + - Service + - gateway.networking.k8s.io/BackendTLSPolicy + - gateway.kgateway.dev/Backend + - gateway.kgateway.dev/BackendConfigPolicy diff --git a/pkg/config/checks/httpRouteCrossNamespaceBackendRef.yaml b/pkg/config/checks/httpRouteCrossNamespaceBackendRef.yaml new file mode 100644 index 00000000..3edff9bd --- /dev/null +++ b/pkg/config/checks/httpRouteCrossNamespaceBackendRef.yaml @@ -0,0 +1,6 @@ +successMessage: HTTPRoute cross-namespace backend references are authorized +failureMessage: HTTPRoute cross-namespace backend references should have a matching ReferenceGrant +category: Security +target: gateway.networking.k8s.io/HTTPRoute +relatedKinds: + - gateway.networking.k8s.io/ReferenceGrant diff --git a/pkg/config/checks/httpRouteInsecureListener.yaml b/pkg/config/checks/httpRouteInsecureListener.yaml new file mode 100644 index 00000000..f2e0c4ab --- /dev/null +++ b/pkg/config/checks/httpRouteInsecureListener.yaml @@ -0,0 +1,6 @@ +successMessage: HTTPRoute uses secure listeners or redirects HTTP to HTTPS +failureMessage: HTTPRoute should not serve application traffic over an HTTP listener +category: Security +target: gateway.networking.k8s.io/HTTPRoute +relatedKinds: + - gateway.networking.k8s.io/Gateway diff --git a/pkg/config/checks/httpRouteWildcardOrEmptyHost.yaml b/pkg/config/checks/httpRouteWildcardOrEmptyHost.yaml new file mode 100644 index 00000000..bee4ed9b --- /dev/null +++ b/pkg/config/checks/httpRouteWildcardOrEmptyHost.yaml @@ -0,0 +1,19 @@ +successMessage: HTTPRoute uses explicit hostnames +failureMessage: HTTPRoute should use explicit hostnames instead of matching every hostname +category: Security +target: gateway.networking.k8s.io/HTTPRoute +schema: + '$schema': https://json-schema.org/draft/2019-09/schema + type: object + required: [spec] + properties: + spec: + type: object + required: [hostnames] + properties: + hostnames: + type: array + minItems: 1 + items: + type: string + pattern: '^[^*]+$' diff --git a/pkg/config/checks/kgatewayBackendTLSVerificationDisabled.yaml b/pkg/config/checks/kgatewayBackendTLSVerificationDisabled.yaml new file mode 100644 index 00000000..07794b58 --- /dev/null +++ b/pkg/config/checks/kgatewayBackendTLSVerificationDisabled.yaml @@ -0,0 +1,17 @@ +successMessage: kgateway backend TLS certificate verification is enabled +failureMessage: kgateway BackendConfigPolicy should not disable TLS certificate verification +category: Security +target: gateway.kgateway.dev/BackendConfigPolicy +schema: + '$schema': https://json-schema.org/draft/2019-09/schema + type: object + properties: + spec: + type: object + properties: + tls: + type: object + properties: + insecureSkipVerify: + not: + const: true diff --git a/pkg/config/default.yaml b/pkg/config/default.yaml index b62b358b..d25f39d3 100644 --- a/pkg/config/default.yaml +++ b/pkg/config/default.yaml @@ -38,6 +38,15 @@ checks: hostNetworkSet: danger hostPortSet: warning tlsSettingsMissing: warning + gatewayTLSMissing: warning + gatewayAllowedRoutesAll: warning + gatewayInsecureFrontendValidation: warning + gatewayCrossNamespaceCertificateRef: warning + httpRouteWildcardOrEmptyHost: warning + httpRouteInsecureListener: warning + httpRouteCrossNamespaceBackendRef: warning + httpRouteBackendTLSMissing: warning + kgatewayBackendTLSVerificationDisabled: warning sensitiveContainerEnvVar: danger sensitiveConfigmapContent: danger clusterrolePodExecAttach: danger diff --git a/pkg/config/examples/config-full.yaml b/pkg/config/examples/config-full.yaml index b19e4d4c..d9e7eba0 100644 --- a/pkg/config/examples/config-full.yaml +++ b/pkg/config/examples/config-full.yaml @@ -38,6 +38,15 @@ checks: hostNetworkSet: danger hostPortSet: warning tlsSettingsMissing: warning + gatewayTLSMissing: warning + gatewayAllowedRoutesAll: warning + gatewayInsecureFrontendValidation: warning + gatewayCrossNamespaceCertificateRef: warning + httpRouteWildcardOrEmptyHost: warning + httpRouteInsecureListener: warning + httpRouteCrossNamespaceBackendRef: warning + httpRouteBackendTLSMissing: warning + kgatewayBackendTLSVerificationDisabled: warning sensitiveContainerEnvVar: danger sensitiveConfigmapContent: danger clusterrolePodExecAttach: danger diff --git a/pkg/config/schema.go b/pkg/config/schema.go index 20c727cf..f1a0bcc2 100644 --- a/pkg/config/schema.go +++ b/pkg/config/schema.go @@ -80,6 +80,7 @@ type SchemaCheck struct { AdditionalSchemas map[string]map[string]any `yaml:"additionalSchemas" json:"additionalSchemas"` AdditionalSchemaStrings map[string]string `yaml:"additionalSchemaStrings" json:"additionalSchemaStrings"` AdditionalValidators map[string]jsonschema.Schema `yaml:"-" json:"-"` + RelatedKinds []TargetKind `yaml:"relatedKinds" json:"relatedKinds"` Mutations []Mutation `yaml:"mutations" json:"mutations"` } diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 40486650..e00823ff 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -322,16 +322,22 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac } restMapper := restmapper.NewDiscoveryRESTMapper(resources) allChecks := []conf.SchemaCheck{} - for _, check := range c.CustomChecks { - allChecks = append(allChecks, check) + for checkID, check := range c.CustomChecks { + severity, enabled := c.Checks[checkID] + if enabled && severity.IsActionable() { + allChecks = append(allChecks, check) + } } - for _, check := range conf.BuiltInChecks { - allChecks = append(allChecks, check) + for checkID, check := range conf.BuiltInChecks { + severity, enabled := c.Checks[checkID] + if enabled && severity.IsActionable() { + allChecks = append(allChecks, check) + } } var additionalKinds []conf.TargetKind for _, check := range allChecks { - neededKinds := []conf.TargetKind{check.Target} + neededKinds := append([]conf.TargetKind{check.Target}, check.RelatedKinds...) for key := range check.AdditionalSchemas { neededKinds = append(neededKinds, conf.TargetKind(key)) } @@ -350,6 +356,10 @@ func CreateResourceProviderFromAPI(ctx context.Context, kube kubernetes.Interfac groupKind := parseGroupKind(maybeTransformKindIntoGroupKind(string(kind))) mapping, err := restMapper.RESTMapping(groupKind) if err != nil { + if meta.IsNoMatchError(err) { + logrus.Infof("Skipping unavailable Kind %s", kind) + continue + } logrus.Warnf("error retrieving mapping of Kind %s because of error: %v", kind, err) return nil, err } diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index 341babb9..1321fad0 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -176,3 +176,45 @@ func TestGetResourceFromAPI(t *testing.T) { }) } } + +func TestAdditionalKindLoading(t *testing.T) { + ingress := test.MockIngress() + k8s, dynamicInterface := test.SetupTestAPI(append(test.GetMockControllers("test"), &ingress)...) + + enabled := conf.Configuration{ + Checks: map[string]conf.Severity{ + "customIngress": conf.SeverityWarning, + }, + CustomChecks: map[string]conf.SchemaCheck{ + "customIngress": {Target: "networking.k8s.io/Ingress"}, + }, + } + resources, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, enabled) + if assert.NoError(t, err) { + assert.Len(t, resources.Resources["networking.k8s.io/Ingress"], 1) + } + + ignored := enabled + ignored.Checks = map[string]conf.Severity{ + "customIngress": conf.SeverityIgnore, + } + resources, err = CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, ignored) + if assert.NoError(t, err) { + assert.Empty(t, resources.Resources["networking.k8s.io/Ingress"]) + } +} + +func TestUnavailableAdditionalKindDoesNotFailAudit(t *testing.T) { + k8s, dynamicInterface := test.SetupTestAPI(test.GetMockControllers("test")...) + config := conf.Configuration{ + Checks: map[string]conf.Severity{ + "optionalCRD": conf.SeverityWarning, + }, + CustomChecks: map[string]conf.SchemaCheck{ + "optionalCRD": {Target: "example.com/OptionalResource"}, + }, + } + + _, err := CreateResourceProviderFromAPI(context.Background(), k8s, "test", dynamicInterface, config) + assert.NoError(t, err) +} diff --git a/pkg/validator/gateway_validator.go b/pkg/validator/gateway_validator.go new file mode 100644 index 00000000..509ffa14 --- /dev/null +++ b/pkg/validator/gateway_validator.go @@ -0,0 +1,485 @@ +package validator + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/fairwindsops/polaris/pkg/kube" + "github.com/qri-io/jsonschema" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +const ( + gatewayAPIGroup = "gateway.networking.k8s.io" + kgatewayAPIGroup = "gateway.kgateway.dev" +) + +type objectReference struct { + group string + kind string + name string + namespace string + sectionName string + port int64 +} + +func init() { + registerCustomChecks("httpRouteInsecureListener", httpRouteInsecureListener) + registerCustomChecks("gatewayCrossNamespaceCertificateRef", gatewayCrossNamespaceCertificateRef) + registerCustomChecks("httpRouteCrossNamespaceBackendRef", httpRouteCrossNamespaceBackendRef) + registerCustomChecks("httpRouteBackendTLSMissing", httpRouteBackendTLSMissing) +} + +func httpRouteInsecureListener(test schemaTestCase) (bool, []jsonschema.KeyError, error) { + if isFullHTTPSRedirect(test.Resource.Resource.Object) || test.ResourceProvider == nil { + return true, nil, nil + } + + routeNamespace := test.Resource.ObjectMeta.GetNamespace() + for _, parent := range referencesAt(test.Resource.Resource.Object, "spec", "parentRefs") { + parent = withDefaults(parent, gatewayAPIGroup, "Gateway", routeNamespace) + if parent.group != gatewayAPIGroup || parent.kind != "Gateway" { + continue + } + + gateway := findResource(test.ResourceProvider.Resources[gatewayAPIGroup+"/Gateway"], parent.namespace, parent.name) + if gateway == nil { + continue + } + listeners := nestedSlice(gateway.Resource.Object, "spec", "listeners") + for _, rawListener := range listeners { + listener, ok := rawListener.(map[string]any) + if !ok || (parent.sectionName != "" && stringValue(listener["name"]) != parent.sectionName) { + continue + } + if stringValue(listener["protocol"]) == "HTTP" && listenerAcceptsHTTPRoute(listener, parent.namespace, test.Resource, test.ResourceProvider) { + return gatewayFailure("spec.parentRefs", fmt.Sprintf("HTTPRoute references HTTP listener %q on Gateway %s/%s without a full HTTPS redirect", stringValue(listener["name"]), parent.namespace, parent.name)) + } + } + } + return true, nil, nil +} + +func gatewayCrossNamespaceCertificateRef(test schemaTestCase) (bool, []jsonschema.KeyError, error) { + if test.ResourceProvider == nil { + return true, nil, nil + } + + sourceNamespace := test.Resource.ObjectMeta.GetNamespace() + listeners := nestedSlice(test.Resource.Resource.Object, "spec", "listeners") + for _, rawListener := range listeners { + listener, ok := rawListener.(map[string]any) + if !ok { + continue + } + for _, ref := range referencesAt(listener, "tls", "certificateRefs") { + ref = withDefaults(ref, "", "Secret", sourceNamespace) + if ref.namespace != sourceNamespace && !hasReferenceGrant(test.ResourceProvider, sourceNamespace, "Gateway", ref) { + return gatewayFailure("spec.listeners.tls.certificateRefs", fmt.Sprintf("Gateway %s/%s references %s %s/%s without a matching ReferenceGrant", sourceNamespace, test.Resource.ObjectMeta.GetName(), ref.kind, ref.namespace, ref.name)) + } + } + } + return true, nil, nil +} + +func httpRouteCrossNamespaceBackendRef(test schemaTestCase) (bool, []jsonschema.KeyError, error) { + if test.ResourceProvider == nil { + return true, nil, nil + } + + sourceNamespace := test.Resource.ObjectMeta.GetNamespace() + for _, ref := range httpRouteBackendRefs(test.Resource.Resource.Object) { + ref = withDefaults(ref, "", "Service", sourceNamespace) + if ref.namespace != sourceNamespace && !hasReferenceGrant(test.ResourceProvider, sourceNamespace, "HTTPRoute", ref) { + return gatewayFailure("spec.rules.backendRefs", fmt.Sprintf("HTTPRoute %s/%s references %s %s/%s without a matching ReferenceGrant", sourceNamespace, test.Resource.ObjectMeta.GetName(), ref.kind, ref.namespace, ref.name)) + } + } + return true, nil, nil +} + +func httpRouteBackendTLSMissing(test schemaTestCase) (bool, []jsonschema.KeyError, error) { + if test.ResourceProvider == nil { + return true, nil, nil + } + + routeNamespace := test.Resource.ObjectMeta.GetNamespace() + for _, ref := range httpRouteBackendRefs(test.Resource.Resource.Object) { + ref = withDefaults(ref, "", "Service", routeNamespace) + if !backendUsesTLS(test.ResourceProvider, ref) { + continue + } + if hasBackendTLSPolicy(test.ResourceProvider, ref) || hasKgatewayBackendTLSPolicy(test.ResourceProvider, ref) { + continue + } + return gatewayFailure("spec.rules.backendRefs", fmt.Sprintf("HTTPRoute backend %s %s/%s appears to use TLS but has no BackendTLSPolicy or kgateway BackendConfigPolicy", ref.kind, ref.namespace, ref.name)) + } + return true, nil, nil +} + +func isFullHTTPSRedirect(object map[string]any) bool { + rules := nestedSlice(object, "spec", "rules") + if len(rules) == 0 { + return false + } + for _, rawRule := range rules { + rule, ok := rawRule.(map[string]any) + if !ok || len(referencesAt(rule, "backendRefs")) > 0 || !ruleMatchesAllTraffic(rule) || !hasHTTPSRedirect(rule) { + return false + } + } + return true +} + +func ruleMatchesAllTraffic(rule map[string]any) bool { + matches := nestedSlice(rule, "matches") + if len(matches) == 0 { + return true + } + for _, rawMatch := range matches { + match, ok := rawMatch.(map[string]any) + if !ok || len(match) != 1 { + continue + } + path, ok := match["path"].(map[string]any) + if ok && (stringValue(path["type"]) == "" || stringValue(path["type"]) == "PathPrefix") && stringValue(path["value"]) == "/" { + return true + } + } + return false +} + +func hasHTTPSRedirect(rule map[string]any) bool { + filters := nestedSlice(rule, "filters") + for _, rawFilter := range filters { + filter, ok := rawFilter.(map[string]any) + if !ok || stringValue(filter["type"]) != "RequestRedirect" { + continue + } + redirect, ok := filter["requestRedirect"].(map[string]any) + if ok && strings.EqualFold(stringValue(redirect["scheme"]), "https") { + return true + } + } + return false +} + +func listenerAcceptsHTTPRoute(listener map[string]any, gatewayNamespace string, route kube.GenericResource, provider *kube.ResourceProvider) bool { + if !listenerHostnameIntersectsRoute(listener, route.Resource.Object) { + return false + } + + allowedRoutes, ok := listener["allowedRoutes"].(map[string]any) + if !ok { + return route.ObjectMeta.GetNamespace() == gatewayNamespace + } + if kinds := nestedSlice(allowedRoutes, "kinds"); len(kinds) > 0 { + allowsHTTPRoute := false + for _, rawKind := range kinds { + kind, ok := rawKind.(map[string]any) + if ok && withDefaultString(stringValue(kind["group"]), gatewayAPIGroup) == gatewayAPIGroup && stringValue(kind["kind"]) == "HTTPRoute" { + allowsHTTPRoute = true + break + } + } + if !allowsHTTPRoute { + return false + } + } + + namespaces, ok := allowedRoutes["namespaces"].(map[string]any) + if !ok || stringValue(namespaces["from"]) == "" || stringValue(namespaces["from"]) == "Same" { + return route.ObjectMeta.GetNamespace() == gatewayNamespace + } + if stringValue(namespaces["from"]) == "All" { + return true + } + if stringValue(namespaces["from"]) != "Selector" { + return false + } + selectorMap, ok := namespaces["selector"].(map[string]any) + if !ok { + return false + } + selector := &metav1.LabelSelector{} + selectorJSON, err := json.Marshal(selectorMap) + if err != nil { + return false + } + if err := json.Unmarshal(selectorJSON, selector); err != nil { + return false + } + compiled, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + return false + } + for _, namespace := range provider.Namespaces { + if namespace.Name == route.ObjectMeta.GetNamespace() { + return compiled.Matches(labels.Set(namespace.Labels)) + } + } + return false +} + +func listenerHostnameIntersectsRoute(listener, route map[string]any) bool { + listenerHostname := stringValue(listener["hostname"]) + routeHostnames := nestedSlice(route, "spec", "hostnames") + if len(routeHostnames) == 0 || listenerHostname == "" { + return true + } + for _, routeHostname := range routeHostnames { + if hostnamesIntersect(listenerHostname, stringValue(routeHostname)) { + return true + } + } + return false +} + +func hostnamesIntersect(left, right string) bool { + if left == "" || right == "" || left == "*" || right == "*" || strings.EqualFold(left, right) { + return true + } + leftSuffix, leftWildcard := strings.CutPrefix(strings.ToLower(left), "*.") + rightSuffix, rightWildcard := strings.CutPrefix(strings.ToLower(right), "*.") + switch { + case leftWildcard && rightWildcard: + return leftSuffix == rightSuffix || strings.HasSuffix(leftSuffix, "."+rightSuffix) || strings.HasSuffix(rightSuffix, "."+leftSuffix) + case leftWildcard: + return strings.HasSuffix(strings.ToLower(right), "."+leftSuffix) + case rightWildcard: + return strings.HasSuffix(strings.ToLower(left), "."+rightSuffix) + default: + return false + } +} + +func httpRouteBackendRefs(object map[string]any) []objectReference { + var refs []objectReference + rules := nestedSlice(object, "spec", "rules") + for _, rawRule := range rules { + rule, ok := rawRule.(map[string]any) + if !ok { + continue + } + refs = append(refs, referencesAt(rule, "backendRefs")...) + filters := nestedSlice(rule, "filters") + for _, rawFilter := range filters { + filter, ok := rawFilter.(map[string]any) + if !ok || stringValue(filter["type"]) != "RequestMirror" { + continue + } + mirror, ok := filter["requestMirror"].(map[string]any) + if !ok { + continue + } + if backend, ok := mirror["backendRef"].(map[string]any); ok { + refs = append(refs, referenceFromMap(backend)) + } + } + } + return refs +} + +func referencesAt(object map[string]any, fields ...string) []objectReference { + items := nestedSlice(object, fields...) + if len(items) == 0 { + return nil + } + refs := make([]objectReference, 0, len(items)) + for _, item := range items { + if ref, ok := item.(map[string]any); ok { + refs = append(refs, referenceFromMap(ref)) + } + } + return refs +} + +func referenceFromMap(ref map[string]any) objectReference { + return objectReference{ + group: stringValue(ref["group"]), + kind: stringValue(ref["kind"]), + name: stringValue(ref["name"]), + namespace: stringValue(ref["namespace"]), + sectionName: stringValue(ref["sectionName"]), + port: int64Value(ref["port"]), + } +} + +func withDefaults(ref objectReference, group, kind, namespace string) objectReference { + if ref.group == "" { + ref.group = group + } + if ref.kind == "" { + ref.kind = kind + } + if ref.namespace == "" { + ref.namespace = namespace + } + return ref +} + +func withDefaultString(value, defaultValue string) string { + if value == "" { + return defaultValue + } + return value +} + +func hasReferenceGrant(provider *kube.ResourceProvider, sourceNamespace, sourceKind string, target objectReference) bool { + for _, grant := range provider.Resources[gatewayAPIGroup+"/ReferenceGrant"] { + if grant.ObjectMeta.GetNamespace() != target.namespace { + continue + } + fromMatches := false + for _, from := range referencesAt(grant.Resource.Object, "spec", "from") { + if from.group == gatewayAPIGroup && from.kind == sourceKind && from.namespace == sourceNamespace { + fromMatches = true + break + } + } + if !fromMatches { + continue + } + for _, to := range referencesAt(grant.Resource.Object, "spec", "to") { + if to.group == target.group && to.kind == target.kind && (to.name == "" || to.name == target.name) { + return true + } + } + } + return false +} + +func backendUsesTLS(provider *kube.ResourceProvider, ref objectReference) bool { + // ponytail: infer TLS from conventional ports and backend metadata; replace + // this with controller status or an implementation graph when Polaris has one. + if ref.port == 443 || ref.port == 8443 { + return true + } + groupKind := ref.kind + if ref.group != "" { + groupKind = ref.group + "/" + ref.kind + } + backend := findResource(provider.Resources[groupKind], ref.namespace, ref.name) + if backend == nil { + return false + } + if ref.group == "" && ref.kind == "Service" { + ports := nestedSlice(backend.Resource.Object, "spec", "ports") + for _, rawPort := range ports { + port, ok := rawPort.(map[string]any) + if !ok || (ref.port != 0 && int64Value(port["port"]) != ref.port) { + continue + } + name := strings.ToLower(stringValue(port["name"])) + appProtocol := strings.ToLower(stringValue(port["appProtocol"])) + if name == "https" || strings.HasPrefix(name, "https-") || appProtocol == "https" || strings.HasSuffix(appProtocol, "/https") { + return true + } + } + } + if ref.group == kgatewayAPIGroup && ref.kind == "Backend" { + hosts := nestedSlice(backend.Resource.Object, "spec", "static", "hosts") + for _, rawHost := range hosts { + host, ok := rawHost.(map[string]any) + if ok && (int64Value(host["port"]) == 443 || int64Value(host["port"]) == 8443) { + return true + } + } + } + return false +} + +func hasBackendTLSPolicy(provider *kube.ResourceProvider, ref objectReference) bool { + if ref.group != "" || ref.kind != "Service" { + return false + } + for _, policy := range provider.Resources[gatewayAPIGroup+"/BackendTLSPolicy"] { + if policy.ObjectMeta.GetNamespace() == ref.namespace && policyTargets(policy, ref) { + return true + } + } + return false +} + +func hasKgatewayBackendTLSPolicy(provider *kube.ResourceProvider, ref objectReference) bool { + for _, policy := range provider.Resources[kgatewayAPIGroup+"/BackendConfigPolicy"] { + if policy.ObjectMeta.GetNamespace() != ref.namespace { + continue + } + if _, found := nestedValue(policy.Resource.Object, "spec", "tls"); found && policyTargets(policy, ref) { + return true + } + } + return false +} + +func policyTargets(policy kube.GenericResource, target objectReference) bool { + for _, ref := range referencesAt(policy.Resource.Object, "spec", "targetRefs") { + ref = withDefaults(ref, "", "Service", policy.ObjectMeta.GetNamespace()) + if ref.group == target.group && ref.kind == target.kind && ref.name == target.name { + return true + } + } + return false +} + +func findResource(resources []kube.GenericResource, namespace, name string) *kube.GenericResource { + for i := range resources { + if resources[i].ObjectMeta.GetNamespace() == namespace && resources[i].ObjectMeta.GetName() == name { + return &resources[i] + } + } + return nil +} + +func stringValue(value any) string { + valueString, _ := value.(string) + return valueString +} + +func int64Value(value any) int64 { + switch number := value.(type) { + case int: + return int64(number) + case int32: + return int64(number) + case int64: + return number + case float64: + return int64(number) + default: + return 0 + } +} + +func nestedSlice(object map[string]any, fields ...string) []any { + value, found := nestedValue(object, fields...) + if !found { + return nil + } + items, _ := value.([]any) + return items +} + +func nestedValue(object map[string]any, fields ...string) (any, bool) { + var current any = object + for _, field := range fields { + currentMap, ok := current.(map[string]any) + if !ok { + return nil, false + } + current, ok = currentMap[field] + if !ok { + return nil, false + } + } + return current, true +} + +func gatewayFailure(path, message string) (bool, []jsonschema.KeyError, error) { + return false, []jsonschema.KeyError{{ + PropertyPath: path, + Message: message, + }}, nil +} diff --git a/test/checks/gatewayAllowedRoutesAll/failure.yaml b/test/checks/gatewayAllowedRoutesAll/failure.yaml new file mode 100644 index 00000000..e3a61324 --- /dev/null +++ b/test/checks/gatewayAllowedRoutesAll/failure.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: unrestricted +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 + allowedRoutes: + namespaces: + from: All diff --git a/test/checks/gatewayAllowedRoutesAll/success.yaml b/test/checks/gatewayAllowedRoutesAll/success.yaml new file mode 100644 index 00000000..72ae15e6 --- /dev/null +++ b/test/checks/gatewayAllowedRoutesAll/success.yaml @@ -0,0 +1,16 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: restricted +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 + allowedRoutes: + namespaces: + from: Selector + selector: + matchLabels: + gateway-access: "true" diff --git a/test/checks/gatewayCrossNamespaceCertificateRef/failure.yaml b/test/checks/gatewayCrossNamespaceCertificateRef/failure.yaml new file mode 100644 index 00000000..576e5ce2 --- /dev/null +++ b/test/checks/gatewayCrossNamespaceCertificateRef/failure.yaml @@ -0,0 +1,15 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: public + namespace: infra +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 + tls: + certificateRefs: + - name: wildcard + namespace: certificates diff --git a/test/checks/gatewayCrossNamespaceCertificateRef/success.yaml b/test/checks/gatewayCrossNamespaceCertificateRef/success.yaml new file mode 100644 index 00000000..eddffb1e --- /dev/null +++ b/test/checks/gatewayCrossNamespaceCertificateRef/success.yaml @@ -0,0 +1,30 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: public + namespace: infra +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 + tls: + certificateRefs: + - name: wildcard + namespace: certificates +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: allow-infra-gateway + namespace: certificates +spec: + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: infra + to: + - group: "" + kind: Secret + name: wildcard diff --git a/test/checks/gatewayInsecureFrontendValidation/failure.yaml b/test/checks/gatewayInsecureFrontendValidation/failure.yaml new file mode 100644 index 00000000..996a8be0 --- /dev/null +++ b/test/checks/gatewayInsecureFrontendValidation/failure.yaml @@ -0,0 +1,15 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: fail-open +spec: + gatewayClassName: kgateway + tls: + frontend: + default: + validation: + mode: AllowInsecureFallback + listeners: + - name: https + protocol: HTTPS + port: 443 diff --git a/test/checks/gatewayInsecureFrontendValidation/success.yaml b/test/checks/gatewayInsecureFrontendValidation/success.yaml new file mode 100644 index 00000000..ffc92e46 --- /dev/null +++ b/test/checks/gatewayInsecureFrontendValidation/success.yaml @@ -0,0 +1,15 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: fail-closed +spec: + gatewayClassName: kgateway + tls: + frontend: + default: + validation: + mode: AllowValidOnly + listeners: + - name: https + protocol: HTTPS + port: 443 diff --git a/test/checks/gatewayTLSMissing/failure.missing-tls.yaml b/test/checks/gatewayTLSMissing/failure.missing-tls.yaml new file mode 100644 index 00000000..623eec87 --- /dev/null +++ b/test/checks/gatewayTLSMissing/failure.missing-tls.yaml @@ -0,0 +1,10 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: missing-tls +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 diff --git a/test/checks/gatewayTLSMissing/failure.yaml b/test/checks/gatewayTLSMissing/failure.yaml new file mode 100644 index 00000000..93597c9a --- /dev/null +++ b/test/checks/gatewayTLSMissing/failure.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: insecure +spec: + gatewayClassName: kgateway + listeners: + - name: https + protocol: HTTPS + port: 443 + tls: + mode: Terminate + certificateRefs: [] diff --git a/test/checks/gatewayTLSMissing/success.yaml b/test/checks/gatewayTLSMissing/success.yaml new file mode 100644 index 00000000..8e5e2739 --- /dev/null +++ b/test/checks/gatewayTLSMissing/success.yaml @@ -0,0 +1,22 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: secure +spec: + gatewayClassName: kgateway + listeners: + - name: http + protocol: HTTP + port: 80 + - name: https + protocol: HTTPS + port: 443 + tls: + mode: Terminate + certificateRefs: + - name: example-tls + - name: passthrough + protocol: TLS + port: 8443 + tls: + mode: Passthrough diff --git a/test/checks/httpRouteBackendTLSMissing/failure.yaml b/test/checks/httpRouteBackendTLSMissing/failure.yaml new file mode 100644 index 00000000..420c7f14 --- /dev/null +++ b/test/checks/httpRouteBackendTLSMissing/failure.yaml @@ -0,0 +1,12 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: plaintext-to-tls-backend + namespace: app +spec: + hostnames: + - app.example.com + rules: + - backendRefs: + - name: api + port: 443 diff --git a/test/checks/httpRouteBackendTLSMissing/success.kgateway.yaml b/test/checks/httpRouteBackendTLSMissing/success.kgateway.yaml new file mode 100644 index 00000000..acd807eb --- /dev/null +++ b/test/checks/httpRouteBackendTLSMissing/success.kgateway.yaml @@ -0,0 +1,39 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: secure-kgateway-backend + namespace: app +spec: + hostnames: + - app.example.com + rules: + - backendRefs: + - group: gateway.kgateway.dev + kind: Backend + name: external-api +--- +apiVersion: gateway.kgateway.dev/v1alpha1 +kind: Backend +metadata: + name: external-api + namespace: app +spec: + type: Static + static: + hosts: + - host: api.example.com + port: 443 +--- +apiVersion: gateway.kgateway.dev/v1alpha1 +kind: BackendConfigPolicy +metadata: + name: external-api-tls + namespace: app +spec: + targetRefs: + - group: gateway.kgateway.dev + kind: Backend + name: external-api + tls: + sni: api.example.com + wellKnownCACertificates: System diff --git a/test/checks/httpRouteBackendTLSMissing/success.yaml b/test/checks/httpRouteBackendTLSMissing/success.yaml new file mode 100644 index 00000000..98cc2626 --- /dev/null +++ b/test/checks/httpRouteBackendTLSMissing/success.yaml @@ -0,0 +1,26 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: secure-backend + namespace: app +spec: + hostnames: + - app.example.com + rules: + - backendRefs: + - name: api + port: 443 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: BackendTLSPolicy +metadata: + name: api-tls + namespace: app +spec: + targetRefs: + - group: "" + kind: Service + name: api + validation: + hostname: api.app.svc.cluster.local + wellKnownCACertificates: System diff --git a/test/checks/httpRouteCrossNamespaceBackendRef/failure.yaml b/test/checks/httpRouteCrossNamespaceBackendRef/failure.yaml new file mode 100644 index 00000000..31f6cc6a --- /dev/null +++ b/test/checks/httpRouteCrossNamespaceBackendRef/failure.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: shared-api + namespace: app +spec: + hostnames: + - app.example.com + rules: + - backendRefs: + - name: api + namespace: shared + port: 8080 diff --git a/test/checks/httpRouteCrossNamespaceBackendRef/success.yaml b/test/checks/httpRouteCrossNamespaceBackendRef/success.yaml new file mode 100644 index 00000000..7a588fc9 --- /dev/null +++ b/test/checks/httpRouteCrossNamespaceBackendRef/success.yaml @@ -0,0 +1,28 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: shared-api + namespace: app +spec: + hostnames: + - app.example.com + rules: + - backendRefs: + - name: api + namespace: shared + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: allow-app-route + namespace: shared +spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: app + to: + - group: "" + kind: Service + name: api diff --git a/test/checks/httpRouteInsecureListener/failure.yaml b/test/checks/httpRouteInsecureListener/failure.yaml new file mode 100644 index 00000000..d7a5546c --- /dev/null +++ b/test/checks/httpRouteInsecureListener/failure.yaml @@ -0,0 +1,31 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: plaintext + namespace: app +spec: + parentRefs: + - name: public + namespace: infra + sectionName: http + hostnames: + - app.example.com + rules: + - backendRefs: + - name: app + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: public + namespace: infra +spec: + gatewayClassName: kgateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All diff --git a/test/checks/httpRouteInsecureListener/success.unattached-http.yaml b/test/checks/httpRouteInsecureListener/success.unattached-http.yaml new file mode 100644 index 00000000..8b66f99c --- /dev/null +++ b/test/checks/httpRouteInsecureListener/success.unattached-http.yaml @@ -0,0 +1,31 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: secure-host-only + namespace: app +spec: + parentRefs: + - name: public + hostnames: + - app.example.com + rules: + - backendRefs: + - name: app + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: public + namespace: app +spec: + gatewayClassName: kgateway + listeners: + - name: unrelated-http + hostname: other.example.com + protocol: HTTP + port: 80 + - name: app-https + hostname: app.example.com + protocol: HTTPS + port: 443 diff --git a/test/checks/httpRouteInsecureListener/success.yaml b/test/checks/httpRouteInsecureListener/success.yaml new file mode 100644 index 00000000..48892e29 --- /dev/null +++ b/test/checks/httpRouteInsecureListener/success.yaml @@ -0,0 +1,37 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: redirect + namespace: app +spec: + parentRefs: + - name: public + namespace: infra + sectionName: http + hostnames: + - app.example.com + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: public + namespace: infra +spec: + gatewayClassName: kgateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All diff --git a/test/checks/httpRouteWildcardOrEmptyHost/failure.empty.yaml b/test/checks/httpRouteWildcardOrEmptyHost/failure.empty.yaml new file mode 100644 index 00000000..5afaa2a6 --- /dev/null +++ b/test/checks/httpRouteWildcardOrEmptyHost/failure.empty.yaml @@ -0,0 +1,9 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: every-host +spec: + rules: + - backendRefs: + - name: api + port: 8080 diff --git a/test/checks/httpRouteWildcardOrEmptyHost/failure.yaml b/test/checks/httpRouteWildcardOrEmptyHost/failure.yaml new file mode 100644 index 00000000..1edb5aee --- /dev/null +++ b/test/checks/httpRouteWildcardOrEmptyHost/failure.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: wildcard-host +spec: + hostnames: + - "*.example.com" + rules: + - backendRefs: + - name: api + port: 8080 diff --git a/test/checks/httpRouteWildcardOrEmptyHost/success.yaml b/test/checks/httpRouteWildcardOrEmptyHost/success.yaml new file mode 100644 index 00000000..5070ee43 --- /dev/null +++ b/test/checks/httpRouteWildcardOrEmptyHost/success.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: explicit-host +spec: + hostnames: + - api.example.com + rules: + - backendRefs: + - name: api + port: 8080 diff --git a/test/checks/kgatewayBackendTLSVerificationDisabled/failure.yaml b/test/checks/kgatewayBackendTLSVerificationDisabled/failure.yaml new file mode 100644 index 00000000..234a0f64 --- /dev/null +++ b/test/checks/kgatewayBackendTLSVerificationDisabled/failure.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.kgateway.dev/v1alpha1 +kind: BackendConfigPolicy +metadata: + name: unverified +spec: + targetRefs: + - group: "" + kind: Service + name: api + tls: + insecureSkipVerify: true diff --git a/test/checks/kgatewayBackendTLSVerificationDisabled/success.yaml b/test/checks/kgatewayBackendTLSVerificationDisabled/success.yaml new file mode 100644 index 00000000..4b1a1100 --- /dev/null +++ b/test/checks/kgatewayBackendTLSVerificationDisabled/success.yaml @@ -0,0 +1,12 @@ +apiVersion: gateway.kgateway.dev/v1alpha1 +kind: BackendConfigPolicy +metadata: + name: verified +spec: + targetRefs: + - group: "" + kind: Service + name: api + tls: + sni: api.example.com + wellKnownCACertificates: System