diff --git a/.github/codecov.yml b/.github/codecov.yml index 0b409847f6..d8de57d94a 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -15,3 +15,4 @@ ignore: - "test" - "**/*.pb.go" - "**/zz_generated.*.go" + - "api/**/*_types.go" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0b7b02748a..c08126cb79 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,14 +36,14 @@ jobs: - uses: ./tools/github-actions/setup-deps - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/latest_release.yaml b/.github/workflows/latest_release.yaml index 4fc7230e8f..50c6c546b5 100644 --- a/.github/workflows/latest_release.yaml +++ b/.github/workflows/latest_release.yaml @@ -81,7 +81,6 @@ jobs: tar -zcvf egctl_latest_darwin_amd64.tar.gz bin/darwin/amd64/egctl tar -zcvf egctl_latest_darwin_arm64.tar.gz bin/darwin/arm64/egctl zip -r egctl_latest_windows_amd64.zip bin/windows/amd64/egctl - zip -r egctl_latest_windows_arm64.zip bin/windows/arm64/egctl # Ignore the error when we delete the latest release, it might not exist. @@ -127,7 +126,6 @@ jobs: egctl_latest_darwin_amd64.tar.gz egctl_latest_darwin_arm64.tar.gz egctl_latest_windows_amd64.zip - egctl_latest_windows_arm64.zip body: | This is the "latest" release of **Envoy Gateway**, which contains the most recent commits from the main branch. diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml index 874fc3d18f..3c9a01e0df 100644 --- a/.github/workflows/license-scan.yml +++ b/.github/workflows/license-scan.yml @@ -18,7 +18,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run scanner - uses: google/osv-scanner-action/osv-scanner-action@6fc714450122bda9d00e4ad5d639ad6a39eedb1f # v2.0.1 + uses: google/osv-scanner-action/osv-scanner-action@e69cc6c86b31f1e7e23935bbe7031b50e51082de # v2.0.2 continue-on-error: true # remove this after https://github.com/google/deps.dev/issues/146 has been resolved with: scan-args: |- diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 50e4936365..2784d3b64a 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -19,7 +19,7 @@ permissions: jobs: scan-scheduled: if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }} - uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@6fc714450122bda9d00e4ad5d639ad6a39eedb1f" # v2.0.1 + uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2 with: scan-args: |- --recursive @@ -32,7 +32,7 @@ jobs: scan-pr: if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} - uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@6fc714450122bda9d00e4ad5d639ad6a39eedb1f" # v2.0.1 + uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2 with: scan-args: |- --recursive diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a74d516df4..a79633f768 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -95,7 +95,6 @@ jobs: tar -zcvf egctl_${{ env.release_tag }}_darwin_amd64.tar.gz bin/darwin/amd64/egctl tar -zcvf egctl_${{ env.release_tag }}_darwin_arm64.tar.gz bin/darwin/arm64/egctl zip -r egctl_${{ env.release_tag }}_windows_amd64.zip bin/windows/amd64/egctl - zip -r egctl_${{ env.release_tag }}_windows_arm64.zip bin/windows/arm64/egctl - name: Upload Release Manifests uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 @@ -115,4 +114,3 @@ jobs: egctl_${{ env.release_tag }}_darwin_amd64.tar.gz egctl_${{ env.release_tag }}_darwin_arm64.tar.gz egctl_${{ env.release_tag }}_windows_amd64.zip - egctl_${{ env.release_tag }}_windows_arm64.zip diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 426aa2c4ca..00bfdec63b 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -40,6 +40,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif diff --git a/OWNERS b/OWNERS index a4201d8f86..bd29b3425a 100644 --- a/OWNERS +++ b/OWNERS @@ -33,9 +33,9 @@ reviewers: - cnvergence - liorokman - rudrakhp -- sanposhiho emeritus-reviewers: - chauhanshubham - tmsnan +- sanposhiho diff --git a/VERSION b/VERSION index 4820f25e27..d5915d3687 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.4.0-rc.1 +v1.4.0-rc.2 diff --git a/api/v1alpha1/backend_types.go b/api/v1alpha1/backend_types.go index e0c626a9c4..544ff79944 100644 --- a/api/v1alpha1/backend_types.go +++ b/api/v1alpha1/backend_types.go @@ -115,6 +115,7 @@ type UnixSocket struct { // BackendSpec describes the desired state of BackendSpec. // +kubebuilder:validation:XValidation:rule="self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols)",message="DynamicResolver type cannot have endpoints and appProtocols specified" +// +kubebuilder:validation:XValidation:rule="has(self.tls) ? self.type == 'DynamicResolver' : true",message="TLS settings can only be specified for DynamicResolver backends" type BackendSpec struct { // Type defines the type of the backend. Defaults to "Endpoints" // @@ -148,12 +149,13 @@ type BackendSpec struct { // Only supported for DynamicResolver backends. // // +optional - // +notImplementedHide TLS *BackendTLSSettings `json:"tls,omitempty"` } // BackendTLSSettings holds the TLS settings for the backend. // Only used for DynamicResolver backends. +// +kubebuilder:validation:XValidation:message="must not contain both CACertificateRefs and WellKnownCACertificates",rule="!(has(self.caCertificateRefs) && size(self.caCertificateRefs) > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates != \"\")" +// +kubebuilder:validation:XValidation:message="must specify either CACertificateRefs or WellKnownCACertificates",rule="(has(self.caCertificateRefs) && size(self.caCertificateRefs) > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates != \"\")" type BackendTLSSettings struct { // CACertificateRefs contains one or more references to Kubernetes objects that // contain TLS certificates of the Certificate Authorities that can be used diff --git a/api/v1alpha1/envoygateway_helpers.go b/api/v1alpha1/envoygateway_helpers.go index 3efa42eca6..63b7266914 100644 --- a/api/v1alpha1/envoygateway_helpers.go +++ b/api/v1alpha1/envoygateway_helpers.go @@ -33,11 +33,11 @@ func DefaultEnvoyGateway() *EnvoyGateway { // SetEnvoyGatewayDefaults sets default EnvoyGateway configuration parameters. func (e *EnvoyGateway) SetEnvoyGatewayDefaults() { - if e.TypeMeta.Kind == "" { - e.TypeMeta.Kind = KindEnvoyGateway + if e.Kind == "" { + e.Kind = KindEnvoyGateway } - if e.TypeMeta.APIVersion == "" { - e.TypeMeta.APIVersion = GroupVersion.String() + if e.APIVersion == "" { + e.APIVersion = GroupVersion.String() } if e.Provider == nil { e.Provider = DefaultEnvoyGatewayProvider() diff --git a/api/v1alpha1/kubernetes_helpers.go b/api/v1alpha1/kubernetes_helpers.go index 6dd6b5fbfc..e944df1c22 100644 --- a/api/v1alpha1/kubernetes_helpers.go +++ b/api/v1alpha1/kubernetes_helpers.go @@ -11,7 +11,6 @@ import ( jsonpatch "github.com/evanphx/json-patch" appsv1 "k8s.io/api/apps/v1" - autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -158,150 +157,6 @@ func (hpa *KubernetesHorizontalPodAutoscalerSpec) setDefault() { } } -// ApplyMergePatch applies a merge patch to a deployment based on the merge type -func (deployment *KubernetesDeploymentSpec) ApplyMergePatch(old *appsv1.Deployment) (*appsv1.Deployment, error) { - if deployment.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current deployment to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original deployment: %w", err) - } - - switch { - case deployment.Patch.Type == nil || *deployment.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, deployment.Patch.Value.Raw, appsv1.Deployment{}) - case *deployment.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, deployment.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *deployment.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new deployment object - var patchedDeployment appsv1.Deployment - if err := json.Unmarshal(patchedJSON, &patchedDeployment); err != nil { - return nil, fmt.Errorf("error unmarshaling patched deployment: %w", err) - } - - return &patchedDeployment, nil -} - -// ApplyMergePatch applies a merge patch to a daemonset based on the merge type -func (daemonset *KubernetesDaemonSetSpec) ApplyMergePatch(old *appsv1.DaemonSet) (*appsv1.DaemonSet, error) { - if daemonset.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current daemonset to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original daemonset: %w", err) - } - - switch { - case daemonset.Patch.Type == nil || *daemonset.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, daemonset.Patch.Value.Raw, appsv1.DaemonSet{}) - case *daemonset.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, daemonset.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *daemonset.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new daemonset object - var patchedDaemonSet appsv1.DaemonSet - if err := json.Unmarshal(patchedJSON, &patchedDaemonSet); err != nil { - return nil, fmt.Errorf("error unmarshaling patched daemonset: %w", err) - } - - return &patchedDaemonSet, nil -} - -// ApplyMergePatch applies a merge patch to a service based on the merge type -func (service *KubernetesServiceSpec) ApplyMergePatch(old *corev1.Service) (*corev1.Service, error) { - if service.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current service to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original service: %w", err) - } - - switch { - case service.Patch.Type == nil || *service.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, service.Patch.Value.Raw, corev1.Service{}) - case *service.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, service.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *service.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new service object - var patchedService corev1.Service - if err := json.Unmarshal(patchedJSON, &patchedService); err != nil { - return nil, fmt.Errorf("error unmarshaling patched service: %w", err) - } - - return &patchedService, nil -} - -// ApplyMergePatch applies a merge patch to a HorizontalPodAutoscaler based on the merge type -func (hpa *KubernetesHorizontalPodAutoscalerSpec) ApplyMergePatch(old *autoscalingv2.HorizontalPodAutoscaler) (*autoscalingv2.HorizontalPodAutoscaler, error) { - if hpa.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current HPA to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original HorizontalPodAutoscaler: %w", err) - } - - switch { - case hpa.Patch.Type == nil || *hpa.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, hpa.Patch.Value.Raw, autoscalingv2.HorizontalPodAutoscaler{}) - case *hpa.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, hpa.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *hpa.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new HorizontalPodAutoscaler object - var patchedHpa autoscalingv2.HorizontalPodAutoscaler - if err := json.Unmarshal(patchedJSON, &patchedHpa); err != nil { - return nil, fmt.Errorf("error unmarshaling patched HorizontalPodAutoscaler: %w", err) - } - - return &patchedHpa, nil -} - // ApplyMergePatch applies a merge patch to a PodDisruptionBudget based on the merge type func (pdb *KubernetesPodDisruptionBudgetSpec) ApplyMergePatch(old *policyv1.PodDisruptionBudget) (*policyv1.PodDisruptionBudget, error) { if pdb.Patch == nil { diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index 7ecca9e904..21727bb057 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -49,8 +49,10 @@ type GlobalRateLimit struct { // matches two rules, one rate limited and one not, the final decision will be // to rate limit the request. // + // +patchMergeKey:"name" + // +patchStrategy:"merge" // +kubebuilder:validation:MaxItems=64 - Rules []RateLimitRule `json:"rules"` + Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` // Shared determines whether the rate limit rules apply across all the policy targets. // If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). @@ -69,15 +71,23 @@ type LocalRateLimit struct { // matches two rules, one with 10rps and one with 20rps, the final limit will // be based on the rule with 10rps. // + // +patchMergeKey:"name" + // +patchStrategy:"merge" + // // +optional // +kubebuilder:validation:MaxItems=16 // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.cost) || !has(foo.cost.response))", message="response cost is not supported for Local Rate Limits" - Rules []RateLimitRule `json:"rules"` + Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` } // RateLimitRule defines the semantics for matching attributes // from the incoming requests, and setting limits for them. type RateLimitRule struct { + // Name is the name of the rule. This is used to identify the rule + // in the Envoy configuration and as a unique identifier for merging. + // + // +optional + Name string `json:"name,omitempty"` // ClientSelectors holds the list of select conditions to select // specific clients using attributes from the traffic flow. // All individual select conditions must hold True for this rule diff --git a/charts/gateway-crds-helm/README.md b/charts/gateway-crds-helm/README.md index 6a31f8d358..87d5388c74 100644 --- a/charts/gateway-crds-helm/README.md +++ b/charts/gateway-crds-helm/README.md @@ -32,6 +32,6 @@ To uninstall the chart: | Key | Type | Default | Description | |-----|------|---------|-------------| -| crds.envoyGateway.enabled | bool | `true` | | -| crds.gatewayAPI.enabled | bool | `true` | | +| crds.envoyGateway.enabled | bool | `false` | | +| crds.gatewayAPI.enabled | bool | `false` | | diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml index 560e58147f..6cf3e37a38 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml @@ -213,6 +213,15 @@ spec: - System type: string type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -225,6 +234,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index b58c751757..66c01520e6 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -948,6 +948,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -1198,6 +1203,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object diff --git a/charts/gateway-crds-helm/values.tmpl.yaml b/charts/gateway-crds-helm/values.tmpl.yaml index 80cebdc4d3..d1433de319 100644 --- a/charts/gateway-crds-helm/values.tmpl.yaml +++ b/charts/gateway-crds-helm/values.tmpl.yaml @@ -1,5 +1,5 @@ crds: gatewayAPI: - enabled: true + enabled: false envoyGateway: - enabled: true + enabled: false diff --git a/charts/gateway-crds-helm/values.yaml b/charts/gateway-crds-helm/values.yaml index 80cebdc4d3..d1433de319 100644 --- a/charts/gateway-crds-helm/values.yaml +++ b/charts/gateway-crds-helm/values.yaml @@ -1,5 +1,5 @@ crds: gatewayAPI: - enabled: true + enabled: false envoyGateway: - enabled: true + enabled: false diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml index 1b631d38a3..cf4a6eb50b 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml @@ -212,6 +212,15 @@ spec: - System type: string type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -224,6 +233,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index fb186d33d6..af436290d9 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -947,6 +947,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -1197,6 +1202,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object diff --git a/charts/gateway-helm/templates/envoy-gateway-rbac.yaml b/charts/gateway-helm/templates/envoy-gateway-rbac.yaml index 5d975b8aaa..e07c25f9a3 100644 --- a/charts/gateway-helm/templates/envoy-gateway-rbac.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-rbac.yaml @@ -19,7 +19,7 @@ metadata: name: {{ include "eg.fullname" $ }}-envoy-gateway-role namespace: {{ $ns | quote }} rules: -{{ include "eg.rbac.namespaced" . }} +{{ include "eg.rbac.namespaced" $ }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/examples/preserve-case-backend/go.mod b/examples/preserve-case-backend/go.mod index dd1a2ec677..d383593957 100644 --- a/examples/preserve-case-backend/go.mod +++ b/examples/preserve-case-backend/go.mod @@ -2,7 +2,7 @@ module github.com/envoyproxy/gateway-preserve-case-backend go 1.24.2 -require github.com/valyala/fasthttp v1.60.0 +require github.com/valyala/fasthttp v1.61.0 require ( github.com/andybalholm/brotli v1.1.1 // indirect diff --git a/examples/preserve-case-backend/go.sum b/examples/preserve-case-backend/go.sum index 149c4e4cd7..3281225005 100644 --- a/examples/preserve-case-backend/go.sum +++ b/examples/preserve-case-backend/go.sum @@ -4,7 +4,7 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= -github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= +github.com/valyala/fasthttp v1.61.0 h1:VV08V0AfoRaFurP1EWKvQQdPTZHiUzaVoulX1aBDgzU= +github.com/valyala/fasthttp v1.61.0/go.mod h1:wRIV/4cMwUPWnRcDno9hGnYZGh78QzODFfo1LTUhBog= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= diff --git a/go.mod b/go.mod index 2b551d0329..03803c6c91 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.2 github.com/go-logr/zapr v1.3.0 - github.com/go-openapi/jsonpointer v0.21.1 github.com/go-openapi/spec v0.21.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/validate v0.24.0 @@ -62,6 +61,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/net v0.39.0 + gomodules.xyz/jsonpatch/v2 v2.4.0 google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a google.golang.org/grpc v1.72.0 google.golang.org/grpc/security/advancedtls v1.0.0 @@ -221,6 +221,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect @@ -489,7 +490,6 @@ require ( golang.org/x/time v0.10.0 // indirect golang.org/x/tools v0.31.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect diff --git a/internal/cmd/certgen.go b/internal/cmd/certgen.go index 99950f5cf0..79b11f7dea 100644 --- a/internal/cmd/certgen.go +++ b/internal/cmd/certgen.go @@ -15,6 +15,8 @@ import ( "github.com/spf13/cobra" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" clicfg "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -80,7 +82,7 @@ func certGen(ctx context.Context, logOut io.Writer, local bool) error { if err = outputCertsForKubernetes(ctx, cli, cfg, overwriteControlPlaneCerts, certs); err != nil { return fmt.Errorf("failed to output certificates: %w", err) } - if err = patchTopologyInjectorWebhook(ctx, cli, cfg, certs.CACertificate); err != nil { + if err = patchTopologyInjectorWebhook(ctx, cli, cfg); err != nil { return fmt.Errorf("failed to patch webhook: %w", err) } } else { @@ -116,7 +118,7 @@ func outputCertsForKubernetes(ctx context.Context, cli client.Client, cfg *confi return nil } -func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *config.Server, caBundle []byte) error { +func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *config.Server) error { if disableTopologyInjector { return nil } @@ -127,10 +129,17 @@ func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *c return fmt.Errorf("failed to get mutating webhook configuration: %w", err) } + secretName := types.NamespacedName{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace} + current := &corev1.Secret{} + if err := cli.Get(ctx, secretName, current); err != nil { + return fmt.Errorf("failed to get secret %s/%s: %w", current.Namespace, current.Name, err) + } + var updated bool + desiredBundle := current.Data["ca.crt"] for i, webhook := range webhookCfg.Webhooks { - if !bytes.Equal(caBundle, webhook.ClientConfig.CABundle) { - webhookCfg.Webhooks[i].ClientConfig.CABundle = caBundle + if !bytes.Equal(desiredBundle, webhook.ClientConfig.CABundle) { + webhookCfg.Webhooks[i].ClientConfig.CABundle = desiredBundle updated = true } } diff --git a/internal/cmd/certgen_test.go b/internal/cmd/certgen_test.go index 27e7791310..b360a19bbe 100644 --- a/internal/cmd/certgen_test.go +++ b/internal/cmd/certgen_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -57,7 +58,7 @@ func TestPatchTopologyWebhook(t *testing.T) { cases := []struct { caseName string webhook *admissionregistrationv1.MutatingWebhookConfiguration - caBundle []byte + secret *corev1.Secret wantErr error wantPatch bool }{ @@ -69,7 +70,10 @@ func TestPatchTopologyWebhook(t *testing.T) { }, Webhooks: []admissionregistrationv1.MutatingWebhook{{ClientConfig: admissionregistrationv1.WebhookClientConfig{}}}, }, - caBundle: []byte("foo"), + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace}, + Data: map[string][]byte{"ca.crt": []byte("foo")}, + }, wantErr: nil, wantPatch: true, }, @@ -81,25 +85,28 @@ func TestPatchTopologyWebhook(t *testing.T) { }, Webhooks: []admissionregistrationv1.MutatingWebhook{{ClientConfig: admissionregistrationv1.WebhookClientConfig{CABundle: []byte("foo")}}}, }, - caBundle: []byte("foo"), + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace}, + Data: map[string][]byte{"ca.crt": []byte("foo")}, + }, wantPatch: false, }, } for _, tc := range cases { t.Run(tc.caseName, func(t *testing.T) { fakeClient := fake.NewClientBuilder(). - WithRuntimeObjects(tc.webhook). + WithRuntimeObjects(tc.webhook, tc.secret). Build() beforeWebhook := &admissionregistrationv1.MutatingWebhookConfiguration{} require.NoError(t, fakeClient.Get(context.Background(), client.ObjectKey{Name: tc.webhook.Name}, beforeWebhook)) - err = patchTopologyInjectorWebhook(context.Background(), fakeClient, cfg, tc.caBundle) + err = patchTopologyInjectorWebhook(context.Background(), fakeClient, cfg) require.NoError(t, err) afterWebhook := &admissionregistrationv1.MutatingWebhookConfiguration{} require.NoError(t, fakeClient.Get(context.Background(), client.ObjectKey{Name: tc.webhook.Name}, afterWebhook)) - require.Equal(t, afterWebhook.Webhooks[0].ClientConfig.CABundle, tc.caBundle) + require.Equal(t, afterWebhook.Webhooks[0].ClientConfig.CABundle, tc.secret.Data["ca.crt"]) assert.Equal(t, tc.wantPatch, beforeWebhook.GetResourceVersion() != afterWebhook.GetResourceVersion()) }) } diff --git a/internal/cmd/egctl/config_test.go b/internal/cmd/egctl/config_test.go index e7fb062f9d..7d54072538 100644 --- a/internal/cmd/egctl/config_test.go +++ b/internal/cmd/egctl/config_test.go @@ -29,6 +29,7 @@ import ( kube "github.com/envoyproxy/gateway/internal/kubernetes" "github.com/envoyproxy/gateway/internal/utils/file" netutil "github.com/envoyproxy/gateway/internal/utils/net" + "github.com/envoyproxy/gateway/internal/utils/test" ) const ( @@ -118,7 +119,7 @@ func TestExtractAllConfigDump(t *testing.T) { aggregated := sampleAggregatedConfigDump(configDump) got, err := marshalEnvoyProxyConfig(aggregated, tc.output) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(got), filepath.Join("testdata", "config", "out", tc.expected))) } out, err := readOutputConfig(tc.expected) @@ -206,7 +207,7 @@ func TestExtractSubResourcesConfigDump(t *testing.T) { aggregated := sampleAggregatedConfigDump(configDump) got, err := marshalEnvoyProxyConfig(aggregated, tc.output) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(got), filepath.Join("testdata", "config", "out", tc.expected))) } out, err := readOutputConfig(tc.expected) diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index be802ef2d4..f7c401a9a0 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -25,10 +25,9 @@ import ( "github.com/envoyproxy/gateway/internal/gatewayapi/resource" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestTranslate(t *testing.T) { testCases := []struct { name string @@ -320,9 +319,10 @@ func TestTranslate(t *testing.T) { "testdata/translate/in/" + tc.name + ".yaml", } - if tc.output == yamlOutput { + switch tc.output { + case yamlOutput: args = append(args, "--output", yamlOutput) - } else if tc.output == jsonOutput { + case jsonOutput: args = append(args, "--output", jsonOutput) } @@ -362,7 +362,7 @@ func TestTranslate(t *testing.T) { out, err = yaml.Marshal(got) require.NoError(t, err) } - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(out), filepath.Join("testdata", "translate", "out", fn))) } want := &TranslationResult{} diff --git a/internal/gatewayapi/backendtlspolicy.go b/internal/gatewayapi/backendtlspolicy.go index e39fc962b3..143bc749fd 100644 --- a/internal/gatewayapi/backendtlspolicy.go +++ b/internal/gatewayapi/backendtlspolicy.go @@ -21,12 +21,72 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func (t *Translator) applyBackendTLSSetting(backendRef gwapiv1.BackendObjectReference, backendNamespace string, parent gwapiv1a2.ParentReference, resources *resource.Resources, envoyProxy *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { - upstreamConfig, policy, err := t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources) +func (t *Translator) applyBackendTLSSetting( + backendRef gwapiv1.BackendObjectReference, + backendNamespace string, + parent gwapiv1a2.ParentReference, + resources *resource.Resources, + envoyProxy *egv1a1.EnvoyProxy, + isDynamicResolver bool, +) (*ir.TLSUpstreamConfig, error) { + var ( + err error + tlsBundle *ir.TLSUpstreamConfig + ) + + // If the destination is a dynamic resolver, we need to use the CACertificateRefs from the backend object + // and not from the BackendTLSPolicy. This is because the BackendTLSPolicy requires a valid hostname, and + // dynamic resolvers's hostname is not fixed. + if isDynamicResolver { + if tlsBundle, err = t.processBackendTLSConfig(backendRef, backendNamespace, resources); err != nil { + return nil, err + } + return t.applyEnvoyProxyBackendTLSSetting(tlsBundle, resources, envoyProxy) + } + + upstreamConfig, err := t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources) if err != nil { return nil, err } - return t.applyEnvoyProxyBackendTLSSetting(policy, upstreamConfig, resources, parent, envoyProxy) + + if tlsBundle, err = t.applyEnvoyProxyBackendTLSSetting(upstreamConfig, resources, envoyProxy); err != nil { + return nil, err + } + return tlsBundle, nil +} + +func (t *Translator) processBackendTLSConfig( + backendRef gwapiv1.BackendObjectReference, + backendNamespace string, + resources *resource.Resources, +) (*ir.TLSUpstreamConfig, error) { + backend := resources.GetBackend(backendNamespace, string(backendRef.Name)) + if backend == nil { + return nil, fmt.Errorf("backend %s not found", backendRef.Name) + } + if backend.Spec.TLS == nil { + return nil, nil + } + + tlsBundle := &ir.TLSUpstreamConfig{ + UseSystemTrustStore: ptr.Deref(backend.Spec.TLS.WellKnownCACertificates, "") == gwapiv1a3.WellKnownCACertificatesSystem, + } + if tlsBundle.UseSystemTrustStore { + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace), + } + return tlsBundle, nil + } + + caCert, err := getCaCertsFromCARefs(backend.Spec.TLS.CACertificateRefs, resources) + if err != nil { + return nil, err + } + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Certificate: caCert, + Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace), + } + return tlsBundle, nil } func (t *Translator) processBackendTLSPolicy( @@ -34,10 +94,10 @@ func (t *Translator) processBackendTLSPolicy( backendNamespace string, parent gwapiv1a2.ParentReference, resources *resource.Resources, -) (*ir.TLSUpstreamConfig, *gwapiv1a3.BackendTLSPolicy, error) { +) (*ir.TLSUpstreamConfig, error) { policy := getBackendTLSPolicy(resources.BackendTLSPolicies, backendRef, backendNamespace, resources) if policy == nil { - return nil, nil, nil + return nil, nil } tlsBundle, err := getBackendTLSBundle(policy, resources) @@ -51,14 +111,14 @@ func (t *Translator) processBackendTLSPolicy( policy.Generation, status.Error2ConditionMsg(err), ) - return nil, nil, err + return nil, err } status.SetAcceptedForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName) - return tlsBundle, policy, nil + return tlsBundle, nil } -func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendTLSPolicy, tlsConfig *ir.TLSUpstreamConfig, resources *resource.Resources, parent gwapiv1a2.ParentReference, ep *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { +func (t *Translator) applyEnvoyProxyBackendTLSSetting(tlsConfig *ir.TLSUpstreamConfig, resources *resource.Resources, ep *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { if ep == nil || ep.Spec.BackendTLS == nil || tlsConfig == nil { return tlsConfig, nil } @@ -86,17 +146,10 @@ func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendT } if ep.Spec.BackendTLS != nil && ep.Spec.BackendTLS.ClientCertificateRef != nil { ns := string(ptr.Deref(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, "")) - ancestorRefs := []gwapiv1a2.ParentReference{ - parent, - } + var err error if ns != ep.Namespace { err = fmt.Errorf("ClientCertificateRef Secret is not located in the same namespace as Envoyproxy. Secret namespace: %s does not match Envoyproxy namespace: %s", ns, ep.Namespace) - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, - t.GatewayControllerName, - policy.Generation, - status.Error2ConditionMsg(err)) return tlsConfig, err } secret := resources.GetSecret(ns, string(ep.Spec.BackendTLS.ClientCertificateRef.Name)) @@ -112,12 +165,6 @@ func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendT Name: ep.Name, }.String(), ) - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, - t.GatewayControllerName, - policy.Generation, - status.Error2ConditionMsg(err), - ) return tlsConfig, err } tlsConf := irTLSConfigs(secret) @@ -161,7 +208,7 @@ func getBackendTLSPolicy( func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources *resource.Resources) (*ir.TLSUpstreamConfig, error) { tlsBundle := &ir.TLSUpstreamConfig{ - SNI: string(backendTLSPolicy.Spec.Validation.Hostname), + SNI: ptr.To(string(backendTLSPolicy.Spec.Validation.Hostname)), UseSystemTrustStore: ptr.Deref(backendTLSPolicy.Spec.Validation.WellKnownCACertificates, "") == gwapiv1a3.WellKnownCACertificatesSystem, } if tlsBundle.UseSystemTrustStore { @@ -171,8 +218,20 @@ func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources return tlsBundle, nil } + caCert, err := getCaCertsFromCARefs(backendTLSPolicy.Spec.Validation.CACertificateRefs, resources) + if err != nil { + return nil, err + } + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Certificate: caCert, + Name: fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace), + } + return tlsBundle, nil +} + +func getCaCertsFromCARefs(caCertificates []gwapiv1.LocalObjectReference, resources *resource.Resources) ([]byte, error) { ca := "" - for _, caRef := range backendTLSPolicy.Spec.Validation.CACertificateRefs { + for _, caRef := range caCertificates { kind := string(caRef.Kind) switch kind { @@ -208,12 +267,7 @@ func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources if ca == "" { return nil, fmt.Errorf("no ca found in referred configmaps") } - tlsBundle.CACertificate = &ir.TLSCACertificate{ - Certificate: []byte(ca), - Name: fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace), - } - - return tlsBundle, nil + return []byte(ca), nil } func getAncestorRefs(policy *gwapiv1a3.BackendTLSPolicy) []gwapiv1a2.ParentReference { diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 5253118685..6a6565cd81 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -494,7 +494,7 @@ func applyTrafficFeatureToRoute(route RouteContext, } } -func mergeBackendTrafficPolicy(routePolicy *egv1a1.BackendTrafficPolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, error) { +func mergeBackendTrafficPolicy(routePolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, error) { if routePolicy.Spec.MergeType == nil || gwPolicy == nil { return routePolicy.DeepCopy(), nil } diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index f2585a7dd1..843109838f 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -952,7 +952,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(addHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '%q'", string(addHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(addHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with an invalid value. Header: '%q'", string(addHeader.Name))) continue } // Check if the header is a duplicate @@ -992,7 +997,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(setHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '%q'", string(setHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(setHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with an invalid value. Header: '%q'", string(setHeader.Name))) continue } diff --git a/internal/gatewayapi/contexts_test.go b/internal/gatewayapi/contexts_test.go index e4f45d12bb..ddaa0b8a09 100644 --- a/internal/gatewayapi/contexts_test.go +++ b/internal/gatewayapi/contexts_test.go @@ -161,7 +161,7 @@ func TestContextsStaleListener(t *testing.T) { // Ensure the listener status has been updated and the stale listener has been // removed. expectedListenerStatus := []gwapiv1.ListenerStatus{{Name: "https"}} - require.Equal(t, expectedListenerStatus, gCtx.Gateway.Status.Listeners) + require.Equal(t, expectedListenerStatus, gCtx.Status.Listeners) // Ensure that the listeners within GatewayContext have been properly updated. expectedGCtxListeners := []*ListenerContext{httpsListenerCtx} diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 320d54120c..7b27adbf06 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -532,7 +532,7 @@ func (t *Translator) buildExtProc( NamespaceDerefOr(extProc.BackendRefs[0].Namespace, policy.Namespace)) } - traffic, err := translateTrafficFeatures(extProc.BackendCluster.BackendSettings) + traffic, err := translateTrafficFeatures(extProc.BackendSettings) if err != nil { return nil, err } diff --git a/internal/gatewayapi/ext_service.go b/internal/gatewayapi/ext_service.go index 899e84eebe..2665497d32 100644 --- a/internal/gatewayapi/ext_service.go +++ b/internal/gatewayapi/ext_service.go @@ -108,6 +108,10 @@ func (t *Translator) processExtServiceDestination( return nil, fmt.Errorf("resource %s of type Backend cannot be used since Backend is disabled in Envoy Gateway configuration", string(backendRef.Name)) } ds = t.processBackendDestinationSetting(settingName, backendRef.BackendObjectReference, backendNamespace, protocol, resources) + // Dynamic resolver destinations are not supported for none-route destinations + if ds.IsDynamicResolver { + return nil, errors.New("dynamic resolver destinations are not supported") + } } if ds == nil { @@ -137,6 +141,7 @@ func (t *Translator) processExtServiceDestination( }, resources, envoyProxy, + false, ) if err != nil { return nil, err diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 05ec2772bf..27c4a93ead 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -66,6 +66,9 @@ type HTTPFilterIR struct { ExtensionRefs []*ir.UnstructuredRef } +// Header value pattern according to RFC 7230 +var HeaderValueRegexp = regexp.MustCompile(`^[!-~]+([\t ]?[!-~]+)*$`) + // ProcessHTTPFilters translates gateway api http filters to IRs. func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, route RouteContext, @@ -376,6 +379,7 @@ func (t *Translator) processRequestHeaderModifierFilter( emptyFilterConfig = false } for _, addHeader := range headersToAdd { + emptyFilterConfig = false if addHeader.Name == "" { updateRouteStatusForFilter( @@ -384,16 +388,27 @@ func (t *Translator) processRequestHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, fmt.Sprintf( - "Header: %q. The RequestHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "Header: %q. The RequestHeaderModifier filter cannot add the Host header or headers with a '/' "+ "or ':' character in them. To modify the Host header use the URLRewrite or the HTTPRouteFilter filter.", string(addHeader.Name)), ) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -443,6 +458,15 @@ func (t *Translator) processRequestHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true @@ -556,6 +580,7 @@ func (t *Translator) processResponseHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, @@ -565,6 +590,16 @@ func (t *Translator) processResponseHeaderModifierFilter( string(addHeader.Name))) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -613,6 +648,15 @@ func (t *Translator) processResponseHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true @@ -730,14 +774,14 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution, } - if filterContext.HTTPFilterIR.URLRewrite != nil { - if filterContext.HTTPFilterIR.URLRewrite.Path == nil { - filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ + if filterContext.URLRewrite != nil { + if filterContext.URLRewrite.Path == nil { + filterContext.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ RegexMatchReplace: rmr, } } } else { // no url rewrite - filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + filterContext.URLRewrite = &ir.URLRewrite{ Path: &ir.ExtendedHTTPPathModifier{ RegexMatchReplace: rmr, }, @@ -748,7 +792,8 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if hrf.Spec.URLRewrite.Hostname != nil { var hm *ir.HTTPHostModifier - if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.HeaderHTTPHostnameModifier { + switch hrf.Spec.URLRewrite.Hostname.Type { + case egv1a1.HeaderHTTPHostnameModifier: if hrf.Spec.URLRewrite.Hostname.Header == nil { updateRouteStatusForFilter( filterContext, @@ -758,18 +803,18 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec hm = &ir.HTTPHostModifier{ Header: hrf.Spec.URLRewrite.Hostname.Header, } - } else if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.BackendHTTPHostnameModifier { + case egv1a1.BackendHTTPHostnameModifier: hm = &ir.HTTPHostModifier{ Backend: ptr.To(true), } } - if filterContext.HTTPFilterIR.URLRewrite != nil { - if filterContext.HTTPFilterIR.URLRewrite.Host == nil { - filterContext.HTTPFilterIR.URLRewrite.Host = hm + if filterContext.URLRewrite != nil { + if filterContext.URLRewrite.Host == nil { + filterContext.URLRewrite.Host = hm } } else { // no url rewrite - filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + filterContext.URLRewrite = &ir.URLRewrite{ Host: hm, } } @@ -801,7 +846,7 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec filterContext.AddResponseHeaders = append(filterContext.AddResponseHeaders, newHeader) } - filterContext.HTTPFilterIR.DirectResponse = dr + filterContext.DirectResponse = dr } if hrf.Spec.CredentialInjection != nil { @@ -834,7 +879,7 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec Overwrite: hrf.Spec.CredentialInjection.Overwrite, Credential: secretBytes, } - filterContext.HTTPFilterIR.CredentialInjection = injection + filterContext.CredentialInjection = injection } } } diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index a99ae629d3..20d9a5663f 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -209,8 +209,8 @@ func ValidateGRPCRouteFilter(filter *gwapiv1.GRPCRouteFilter, extGKs ...schema.G filter.Type == gwapiv1.GRPCRouteFilterResponseHeaderModifier: return nil case filter.Type == gwapiv1.GRPCRouteFilterExtensionRef: - switch { - case filter.ExtensionRef == nil: + switch filter.ExtensionRef { + case nil: return errors.New("extensionRef field must be specified for an extended filter") default: for _, gk := range extGKs { diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index da0ad0def5..9f2fb5de26 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -6,6 +6,7 @@ package gatewayapi import ( + "errors" "fmt" "math" "strings" @@ -753,6 +754,10 @@ func (t *Translator) processBackendRefs(name string, backendCluster egv1a1.Backe return nil, nil, err } ds := t.processBackendDestinationSetting(name, ref.BackendObjectReference, ns, ir.TCP, resources) + // Dynamic resolver destinations are not supported for none-route destinations + if ds.IsDynamicResolver { + return nil, nil, errors.New("dynamic resolver destinations are not supported") + } result = append(result, ds) default: return nil, nil, fmt.Errorf("unsupported kind for backendRefs: %s", kind) diff --git a/internal/gatewayapi/listener_test.go b/internal/gatewayapi/listener_test.go index af56d06289..cfdae67f37 100644 --- a/internal/gatewayapi/listener_test.go +++ b/internal/gatewayapi/listener_test.go @@ -369,8 +369,8 @@ func TestCheckOverlappingHostnames(t *testing.T) { } for i := range tt.gateway.listeners { tt.gateway.listeners[i].listenerStatusIdx = i - tt.gateway.Gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ - Name: tt.gateway.listeners[i].Listener.Name, + tt.gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ + Name: tt.gateway.listeners[i].Name, Conditions: []metav1.Condition{}, } } @@ -406,8 +406,8 @@ func TestCheckOverlappingHostnames(t *testing.T) { } if len(tt.expected) == 0 { - if len(tt.gateway.Gateway.Status.Listeners) != 0 { - for idx, listener := range tt.gateway.Gateway.Status.Listeners { + if len(tt.gateway.Status.Listeners) != 0 { + for idx, listener := range tt.gateway.Status.Listeners { if len(listener.Conditions) != 0 { t.Errorf("expected 0 conditions for listener %d, got %d", idx, len(listener.Conditions)) } @@ -604,9 +604,9 @@ func TestCheckOverlappingCertificates(t *testing.T) { } // Initialize listener status indices - for i := range gateway.Gateway.Status.Listeners { - gateway.Gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ - Name: tt.listeners[i].Listener.Name, + for i := range gateway.Status.Listeners { + gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ + Name: tt.listeners[i].Name, Conditions: []metav1.Condition{}, } } @@ -618,7 +618,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { for _, expected := range tt.expectedStatus { found := false for _, listener := range gateway.listeners { - if string(listener.Listener.Name) != expected.listenerName { + if string(listener.Name) != expected.listenerName { continue } @@ -648,7 +648,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { if condition.Type == string(gwapiv1.ListenerConditionOverlappingTLSConfig) { found := false for _, expected := range tt.expectedStatus { - if string(listener.Listener.Name) == expected.listenerName && + if string(listener.Name) == expected.listenerName && condition.Status == expected.status && condition.Reason == string(expected.reason) && condition.Message == expected.message { @@ -657,7 +657,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { } } if !found { - t.Errorf("Unexpected status condition found for listener %s: %+v", listener.Listener.Name, condition) + t.Errorf("Unexpected status condition found for listener %s: %+v", listener.Name, condition) } } } diff --git a/internal/gatewayapi/resource/fs.go b/internal/gatewayapi/resource/fs.go index 09fdcb1ab2..d2385fcaad 100644 --- a/internal/gatewayapi/resource/fs.go +++ b/internal/gatewayapi/resource/fs.go @@ -11,7 +11,7 @@ import ( "io/fs" "time" - "github.com/envoyproxy/gateway" // nolint:goimports + envoygateway "github.com/envoyproxy/gateway" ) var ( diff --git a/internal/gatewayapi/resource/fs_test.go b/internal/gatewayapi/resource/fs_test.go index c8742a02c8..f0198e6d2f 100644 --- a/internal/gatewayapi/resource/fs_test.go +++ b/internal/gatewayapi/resource/fs_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/envoyproxy/gateway" // nolint:goimports + envoygateway "github.com/envoyproxy/gateway" ) func TestOpenAndReadGatewayCRDsFS(t *testing.T) { diff --git a/internal/gatewayapi/resource/load_test.go b/internal/gatewayapi/resource/load_test.go index fcfbfe1cf9..3fe0083661 100644 --- a/internal/gatewayapi/resource/load_test.go +++ b/internal/gatewayapi/resource/load_test.go @@ -6,7 +6,6 @@ package resource import ( - "flag" "fmt" "os" "path/filepath" @@ -18,10 +17,9 @@ import ( "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestIterYAMLBytes(t *testing.T) { inputs := `test: foo1 --- @@ -53,7 +51,7 @@ func TestLoadAllSupportedResourcesFromYAMLBytes(t *testing.T) { got, err := LoadResourcesFromYAMLBytes(inFile, true) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { out, err := yaml.Marshal(got) require.NoError(t, err) require.NoError(t, file.Write(string(out), filepath.Join("testdata", "all-resources.out.yaml"))) diff --git a/internal/gatewayapi/resource/resource_test.go b/internal/gatewayapi/resource/resource_test.go index 00e39d5db4..f5758b4319 100644 --- a/internal/gatewayapi/resource/resource_test.go +++ b/internal/gatewayapi/resource/resource_test.go @@ -177,9 +177,10 @@ func TestGetEndpointSlicesForBackendDualStack(t *testing.T) { var ipv4Slice, ipv6Slice *discoveryv1.EndpointSlice for _, slice := range result { - if slice.AddressType == discoveryv1.AddressTypeIPv4 { + switch slice.AddressType { + case discoveryv1.AddressTypeIPv4: ipv4Slice = slice - } else if slice.AddressType == discoveryv1.AddressTypeIPv6 { + case discoveryv1.AddressTypeIPv6: ipv6Slice = slice } } diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 0d9d2a3ed5..b98e81a465 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -1418,6 +1418,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe }, resources, envoyProxy, + ds.IsDynamicResolver, ) if tlsErr != nil { return nil, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS) diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 6febd8e581..1a8e3b00e3 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -102,7 +102,7 @@ func (r *Runner) startWasmCache(ctx context.Context) { return } cacheOption := wasm.CacheOptions{} - if r.Config.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { + if r.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { cacheOption.CacheDir = "/var/lib/eg/wasm" } else { h, _ := os.UserHomeDir() // Assume we always get the home directory. @@ -152,7 +152,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re for _, resources := range *val { // Translate and publish IRs. t := &gatewayapi.Translator{ - GatewayControllerName: r.Server.EnvoyGateway.Gateway.ControllerName, + GatewayControllerName: r.EnvoyGateway.Gateway.ControllerName, GatewayClassName: gwapiv1.ObjectName(resources.GatewayClass.Name), GlobalRateLimitEnabled: r.EnvoyGateway.RateLimit != nil, EnvoyPatchPolicyEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableEnvoyPatchPolicy, diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index dd7523cade..3bc82d8907 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -1218,7 +1218,7 @@ func (t *Translator) buildExtAuth( backendSettings = http.BackendSettings switch { case len(http.BackendRefs) > 0: - backendRefs = http.BackendCluster.BackendRefs + backendRefs = http.BackendRefs case http.BackendRef != nil: backendRefs = []egv1a1.BackendRef{ { @@ -1233,7 +1233,7 @@ func (t *Translator) buildExtAuth( protocol = ir.GRPC backendSettings = grpc.BackendSettings switch { - case len(grpc.BackendCluster.BackendRefs) > 0: + case len(grpc.BackendRefs) > 0: backendRefs = grpc.BackendRefs case grpc.BackendRef != nil: backendRefs = []egv1a1.BackendRef{ diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index e8042c85e3..bd859d6c0b 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -63,6 +63,7 @@ func (x XdsIRRoutes) Less(i, j int) bool { // Equal case // 3. Sort based on the number of Header matches. + // When the number is same, sort based on number of Exact Header matches. hCountI := len(x[i].HeaderMatches) hCountJ := len(x[j].HeaderMatches) if hCountI < hCountJ { @@ -71,12 +72,31 @@ func (x XdsIRRoutes) Less(i, j int) bool { if hCountI > hCountJ { return false } + + hExtNumberI := numberOfExactMatches(x[i].HeaderMatches) + hExtNumberJ := numberOfExactMatches(x[j].HeaderMatches) + if hExtNumberI < hExtNumberJ { + return true + } + if hExtNumberI > hExtNumberJ { + return false + } // Equal case // 4. Sort based on the number of Query param matches. + // When the number is same, sort based on number of Exact Query param matches. qCountI := len(x[i].QueryParamMatches) qCountJ := len(x[j].QueryParamMatches) - return qCountI < qCountJ + if qCountI < qCountJ { + return true + } + if qCountI > qCountJ { + return false + } + + qExtNumberI := numberOfExactMatches(x[i].QueryParamMatches) + qExtNumberJ := numberOfExactMatches(x[j].QueryParamMatches) + return qExtNumberI < qExtNumberJ } // sortXdsIR sorts the xdsIR based on the match precedence @@ -107,3 +127,13 @@ func pathMatchCount(pathMatch *ir.StringMatch) int { } return 0 } + +func numberOfExactMatches(stringMatches []*ir.StringMatch) int { + var cnt int + for _, stringMatch := range stringMatches { + if stringMatch != nil && stringMatch.Exact != nil { + cnt++ + } + } + return cnt +} diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml index 3b2331bba7..5fb1efb474 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml @@ -13,13 +13,18 @@ clientTrafficPolicies: add: - name: "" value: "empty" - - name: "invalid" + - name: "Example/Header/1" value: ":/" + - name: "example-header-2" + value: | + multi-line-header-value set: - name: "" value: "empty" - - name: "invalid" + - name: "Example:Header:3" value: ":/" + - name: "example-header-4" + value: " invalid" remove: - "" targetRef: diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml index abab24926f..4a12476ea7 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml @@ -11,15 +11,20 @@ clientTrafficPolicies: add: - name: "" value: empty - - name: invalid + - name: Example/Header/1 value: :/ + - name: example-header-2 + value: | + multi-line-header-value remove: - "" set: - name: "" value: empty - - name: invalid + - name: Example:Header:3 value: :/ + - name: example-header-4 + value: ' invalid' enableEnvoyHeaders: true preserveXRequestID: true withUnderscoresAction: Allow @@ -38,8 +43,13 @@ clientTrafficPolicies: - lastTransitionTime: null message: |- Headers: EarlyRequestHeaders cannot add a header with an empty name + EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '"Example/Header/1"' + EarlyRequestHeaders cannot add a header with an invalid value. Header: '"example-header-2"' EarlyRequestHeaders cannot set a header with an empty name - EarlyRequestHeaders cannot remove a header with an empty name. + EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '"Example:Header:3"' + EarlyRequestHeaders cannot set a header with an invalid value. Header: '"example-header-4"' + EarlyRequestHeaders cannot remove a header with an empty name + EarlyRequestHeaders did not provide valid configuration to add/set/remove any headers. reason: Invalid status: "False" type: Accepted diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml index 7c872f644e..98a08206af 100644 --- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml @@ -20,11 +20,9 @@ backendTLSPolicies: namespace: envoy-gateway conditions: - lastTransitionTime: null - message: 'ClientCertificateRef Secret is not located in the same namespace - as Envoyproxy. Secret namespace: envoy-gateway-user-ns does not match Envoyproxy - namespace: envoy-gateway-system.' - reason: Invalid - status: "False" + message: Policy has been accepted. + reason: Accepted + status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml index d4224abd76..775ff25869 100644 --- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml @@ -20,10 +20,9 @@ backendTLSPolicies: namespace: envoy-gateway conditions: - lastTransitionTime: null - message: 'Failed to locate TLS secret for client auth: envoy-gateway-system/client-auth-not-found - specified in EnvoyProxy envoy-gateway-system/test.' - reason: Invalid - status: "False" + message: Policy has been accepted. + reason: Accepted + status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml index 3cd1bbb8ef..03f9e16bf9 100644 --- a/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml +++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml @@ -30,12 +30,70 @@ httpRoutes: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend-1 + name: backend-with-custom-ca +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-system-ca backends: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: Backend metadata: - name: backend-1 + name: backend-with-custom-ca namespace: default spec: type: DynamicResolver + tls: + caCertificateRefs: + - name: ca-cmap + group: "" + kind: ConfigMap +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-with-system-ca + namespace: default + spec: + type: DynamicResolver + tls: + wellKnownCACertificates: "System" +configMaps: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: ca-cmap + namespace: backends + data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDJzCCAg+gAwIBAgIUAl6UKIuKmzte81cllz5PfdN2IlIwDQYJKoZIhvcNAQEL + BQAwIzEQMA4GA1UEAwwHbXljaWVudDEPMA0GA1UECgwGa3ViZWRiMB4XDTIzMTAw + MjA1NDE1N1oXDTI0MTAwMTA1NDE1N1owIzEQMA4GA1UEAwwHbXljaWVudDEPMA0G + A1UECgwGa3ViZWRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSTc + 1yj8HW62nynkFbXo4VXKv2jC0PM7dPVky87FweZcTKLoWQVPQE2p2kLDK6OEszmM + yyr+xxWtyiveremrWqnKkNTYhLfYPhgQkczib7eUalmFjUbhWdLvHakbEgCodn3b + kz57mInX2VpiDOKg4kyHfiuXWpiBqrCx0KNLpxo3DEQcFcsQTeTHzh4752GV04RU + Ti/GEWyzIsl4Rg7tGtAwmcIPgUNUfY2Q390FGqdH4ahn+mw/6aFbW31W63d9YJVq + ioyOVcaMIpM5B/c7Qc8SuhCI1YGhUyg4cRHLEw5VtikioyE3X04kna3jQAj54YbR + bpEhc35apKLB21HOUQIDAQABo1MwUTAdBgNVHQ4EFgQUyvl0VI5vJVSuYFXu7B48 + 6PbMEAowHwYDVR0jBBgwFoAUyvl0VI5vJVSuYFXu7B486PbMEAowDwYDVR0TAQH/ + BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMLxrgFVMuNRq2wAwcBt7SnNR5Cfz + 2MvXq5EUmuawIUi9kaYjwdViDREGSjk7JW17vl576HjDkdfRwi4E28SydRInZf6J + i8HZcZ7caH6DxR335fgHVzLi5NiTce/OjNBQzQ2MJXVDd8DBmG5fyatJiOJQ4bWE + A7FlP0RdP3CO3GWE0M5iXOB2m1qWkE2eyO4UHvwTqNQLdrdAXgDQlbam9e4BG3Gg + d/6thAkWDbt/QNT+EJHDCvhDRKh1RuGHyg+Y+/nebTWWrFWsktRrbOoHCZiCpXI1 + 3eXE6nt0YkgtDxG22KqnhpAg9gUSs2hlhoxyvkzyF0mu6NhPlwAgnq7+/Q== + -----END CERTIFICATE----- diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml index 2fa2a8b335..8164485596 100644 --- a/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml +++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml @@ -3,9 +3,31 @@ backends: kind: Backend metadata: creationTimestamp: null - name: backend-1 + name: backend-with-custom-ca namespace: default spec: + tls: + caCertificateRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + type: DynamicResolver + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + creationTimestamp: null + name: backend-with-system-ca + namespace: default + spec: + tls: + wellKnownCACertificates: System type: DynamicResolver status: conditions: @@ -32,7 +54,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 1 + - attachedRoutes: 2 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -73,7 +95,43 @@ httpRoutes: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend-1 + name: backend-with-custom-ca + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-system-ca status: parents: - conditions: @@ -135,6 +193,11 @@ xdsIR: settings: - isDynamicResolver: true name: httproute/default/httproute-1/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: backend-with-custom-ca/default-ca weight: 1 hostname: gateway.envoyproxy.io isHTTP2: false @@ -143,6 +206,24 @@ xdsIR: name: httproute-1 namespace: default name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - isDynamicResolver: true + name: httproute/default/httproute-2/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + name: backend-with-system-ca/default-ca + useSystemTrustStore: true + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/-1/gateway_envoyproxy_io readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml index c965d32968..0037ee5543 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml @@ -39,7 +39,7 @@ httpRoutes: requestHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml index 98c6a40af9..ba290a7ff0 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: RequestHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". RequestHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -125,37 +126,6 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addRequestHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - name: httproute/default/httproute-1/rule/0/backend/0 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..75d1c1669d --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,47 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" + diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..fe02e7a8c4 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,134 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - requestHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: RequestHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". RequestHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml new file mode 100644 index 0000000000..7223aef583 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + - type: RegularExpression + name: hostname + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml new file mode 100644 index 0000000000..8952792385 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml @@ -0,0 +1,228 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + - name: hostname + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + - distinct: false + name: hostname + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml new file mode 100644 index 0000000000..06ae961aee --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml new file mode 100644 index 0000000000..454c35bc9a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml @@ -0,0 +1,222 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml new file mode 100644 index 0000000000..adc40a7ed7 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + - type: RegularExpression + name: name + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml new file mode 100644 index 0000000000..2929ac417a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml @@ -0,0 +1,228 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + - name: name + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* + - distinct: false + name: name + safeRegex: .* + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml new file mode 100644 index 0000000000..128cefe8cd --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml new file mode 100644 index 0000000000..b1dee44820 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml @@ -0,0 +1,222 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml index 1c0016800d..b84f703b65 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml @@ -39,8 +39,7 @@ httpRoutes: responseHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" - diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml index f89eab6f3f..94dae9d730 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: ResponseHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". ResponseHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -125,37 +126,6 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addResponseHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - name: httproute/default/httproute-1/rule/0/backend/0 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..802391150f --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,46 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..2ceb5619c2 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,134 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - responseHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: ResponseHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". ResponseHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 5b03a95606..7666322d76 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -307,7 +307,7 @@ func (t *Translator) InitIRs(gateways []*GatewayContext) (map[string]*ir.Xds, ma gwInfraIR.Proxy.Name = irKey gwInfraIR.Proxy.Namespace = t.ControllerNamespace if t.GatewayNamespaceMode { - gwInfraIR.Proxy.Namespace = gateway.Gateway.Namespace + gwInfraIR.Proxy.Namespace = gateway.Namespace } // save the IR references in the map before the translation starts xdsIR[irKey] = gwXdsIR diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index af8349df75..5193944295 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -10,7 +10,6 @@ import ( "context" "crypto/sha256" "encoding/hex" - "flag" "fmt" "os" "path/filepath" @@ -38,11 +37,10 @@ import ( "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" "github.com/envoyproxy/gateway/internal/wasm" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func mustUnmarshal(t *testing.T, val []byte, out interface{}) { require.NoError(t, yaml.UnmarshalStrict(val, out, yaml.DisallowUnknownFields)) } @@ -326,7 +324,7 @@ func TestTranslate(t *testing.T) { out, err := yaml.Marshal(got) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { overrideOutputConfig(t, string(out), outputFilePath) } @@ -527,7 +525,7 @@ func TestTranslateWithExtensionKinds(t *testing.T) { out, err := yaml.Marshal(got) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(out), outputFilePath)) } diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go index 33bae735ab..d97ee80713 100644 --- a/internal/gatewayapi/validate.go +++ b/internal/gatewayapi/validate.go @@ -287,7 +287,7 @@ func (t *Translator) validateListenerConditions(listener *ListenerContext) (isRe } // Any condition on the listener apart from Programmed=true indicates an error. - if !(lConditions[0].Type == string(gwapiv1.ListenerConditionProgrammed) && lConditions[0].Status == metav1.ConditionTrue) { + if lConditions[0].Type != string(gwapiv1.ListenerConditionProgrammed) || lConditions[0].Status != metav1.ConditionTrue { hasProgrammedCond := false hasRefsCond := false for _, existing := range lConditions { @@ -969,7 +969,7 @@ func (t *Translator) validateExtServiceBackendReference( return errors.New("kind is invalid, only Service (specified by omitting " + "the kind field or setting it to 'Service') and Backend are supported") } - if backendRef.Port == nil && !(backendRef.Kind != nil && *backendRef.Kind == egv1a1.KindBackend) { + if backendRef.Port == nil && (backendRef.Kind == nil || *backendRef.Kind != egv1a1.KindBackend) { return errors.New("a valid port number corresponding to a port on the Service must be specified") } diff --git a/internal/infrastructure/kubernetes/infra_client.go b/internal/infrastructure/kubernetes/infra_client.go index 2cee236044..7840b6413c 100644 --- a/internal/infrastructure/kubernetes/infra_client.go +++ b/internal/infrastructure/kubernetes/infra_client.go @@ -27,7 +27,7 @@ func New(cli client.Client) *InfraClient { func (cli *InfraClient) ServerSideApply(ctx context.Context, obj client.Object) error { opts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("envoy-gateway")} - if err := cli.Client.Patch(ctx, obj, client.Apply, opts...); err != nil { + if err := cli.Patch(ctx, obj, client.Apply, opts...); err != nil { return fmt.Errorf("failed to create/update resource with server-side apply for obj %v: %w", obj, err) } @@ -82,7 +82,7 @@ func (cli *InfraClient) Delete(ctx context.Context, object client.Object) error // GetUID retrieves the uid of one resource. func (cli *InfraClient) GetUID(ctx context.Context, key client.ObjectKey, current client.Object) (types.UID, error) { - if err := cli.Client.Get(ctx, key, current); err != nil { + if err := cli.Get(ctx, key, current); err != nil { return "", err } return current.GetUID(), nil diff --git a/internal/infrastructure/kubernetes/proxy/resource.go b/internal/infrastructure/kubernetes/proxy/resource.go index d98e68cf4d..6dba6e525f 100644 --- a/internal/infrastructure/kubernetes/proxy/resource.go +++ b/internal/infrastructure/kubernetes/proxy/resource.go @@ -402,7 +402,7 @@ func expectedContainerEnv(containerSpec *egv1a1.KubernetesContainerSpec, gateway ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ APIVersion: "v1", - FieldPath: fmt.Sprintf("metadata.labels['%s']", corev1.LabelTopologyZone), + FieldPath: fmt.Sprintf("metadata.annotations['%s']", corev1.LabelTopologyZone), }, }, }, diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index 0ec2e5a972..ca4543d072 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -24,6 +24,7 @@ import ( "github.com/envoyproxy/gateway/internal/infrastructure/common" "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource" "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/utils" "github.com/envoyproxy/gateway/internal/xds/bootstrap" ) @@ -38,7 +39,7 @@ const ( // trusted CA certificate. XdsTLSCaFilepath = "/certs/ca.crt" - // XdsTLSCertFileName is the file name of the xDS server TLS certificate. + // XdsTLSCaFileName is the file name of the xDS server TLS certificate. XdsTLSCaFileName = "ca.crt" ) @@ -204,14 +205,14 @@ func (r *ResourceRender) Service() (*corev1.Service, error) { // set name if envoyServiceConfig.Name != nil { - svc.ObjectMeta.Name = *envoyServiceConfig.Name + svc.Name = *envoyServiceConfig.Name } else { - svc.ObjectMeta.Name = r.Name() + svc.Name = r.Name() } // apply merge patch to service var err error - if svc, err = envoyServiceConfig.ApplyMergePatch(svc); err != nil { + if svc, err = utils.MergeWithPatch(svc, envoyServiceConfig.Patch); err != nil { return nil, err } @@ -338,13 +339,13 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // set name if deploymentConfig.Name != nil { - deployment.ObjectMeta.Name = *deploymentConfig.Name + deployment.Name = *deploymentConfig.Name } else { - deployment.ObjectMeta.Name = r.Name() + deployment.Name = r.Name() } // apply merge patch to deployment - if deployment, err = deploymentConfig.ApplyMergePatch(deployment); err != nil { + if deployment, err = utils.MergeWithPatch(deployment, deploymentConfig.Patch); err != nil { return nil, err } @@ -408,13 +409,13 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { // set name if daemonSetConfig.Name != nil { - daemonSet.ObjectMeta.Name = *daemonSetConfig.Name + daemonSet.Name = *daemonSetConfig.Name } else { - daemonSet.ObjectMeta.Name = r.Name() + daemonSet.Name = r.Name() } - // apply merge patch to daemonset - if daemonSet, err = daemonSetConfig.ApplyMergePatch(daemonSet); err != nil { + // apply merge patch to DaemonSet + if daemonSet, err = utils.MergeWithPatch(daemonSet, daemonSetConfig.Patch); err != nil { return nil, err } @@ -516,8 +517,8 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod hpa.Spec.ScaleTargetRef.Name = r.Name() } - hpa, err := hpaConfig.ApplyMergePatch(hpa) - if err != nil { + var err error + if hpa, err = utils.MergeWithPatch(hpa, hpaConfig.Patch); err != nil { return nil, err } diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index ea622b987e..0651a433af 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -6,7 +6,6 @@ package proxy import ( - "flag" "fmt" "os" "sort" @@ -30,10 +29,9 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - const ( // envoyHTTPPort is the container port number of Envoy's HTTP endpoint. envoyHTTPPort = int32(8080) @@ -634,7 +632,7 @@ func TestDeployment(t *testing.T) { dp, err := r.Deployment() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(dp) require.NoError(t, err) // nolint: gosec @@ -1072,7 +1070,7 @@ func TestDaemonSet(t *testing.T) { }) } - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(ds) require.NoError(t, err) // nolint: gosec diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml index 0c22050ba9..a0fad44a2b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml @@ -55,7 +55,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -142,7 +142,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml index 782cda71d5..46a6b4dbed 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml @@ -258,7 +258,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -339,7 +339,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml index e5ac89e0c6..c8b7570501 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml index cf7e858983..3ec8474847 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml index cde29e6d1f..db37e5b125 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml @@ -191,7 +191,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -275,7 +275,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml index 76ed4ea1e9..344b3d3229 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml index 8a1c95589b..930a2479db 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml @@ -251,7 +251,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml index d001a3e8b2..f82852a48e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml index 04f5e7095d..b827f5f744 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml index 5573bbfef2..eb22669380 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml index bdfe300a9e..2f38ba0f4a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml index 0b845fd2fc..0da38072c0 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml @@ -55,7 +55,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -142,7 +142,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml index e9a06e02e9..f1062ae8ce 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml @@ -244,7 +244,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -331,7 +331,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml index 2e1a4cdb1c..9ae0454eca 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml index 9b5d9a55e3..a2bcc35f64 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml index 72b567b531..01e66f1617 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml index 51e964b1cf..6c9ecead69 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml index 79f27c337f..c61fd73c7c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml @@ -58,7 +58,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -145,7 +145,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml index 34b5b07f0f..ef916e860a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml @@ -59,7 +59,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -146,7 +146,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml index 8b4f231166..60676d3903 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml @@ -263,7 +263,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -344,7 +344,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml index 8f95d6668a..ed1b655c87 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml @@ -263,7 +263,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -346,7 +346,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml index ae2a8c316a..fa2b709e16 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -343,7 +343,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml index 34372d0425..fa4c0813f4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml index 14ebc8037b..fdc09427c2 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml @@ -195,7 +195,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -279,7 +279,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml index 9b67b49d98..26939bb66e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml index 1aab161d96..cc8e5d1c6c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -347,7 +347,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml index 1ce08cd0f4..fe1c755c1e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml index e06d0de726..08b6c4ef97 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml @@ -255,7 +255,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml index d49584f934..915089768e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml index 2e23606989..e1e7e66510 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml index 212663a71b..896a680709 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -347,7 +347,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml index 8035a52f06..7b560c4bb9 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml @@ -251,7 +251,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml index 5e39371b7c..e3a5264aa1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml @@ -59,7 +59,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -146,7 +146,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml index 5c359ada53..2ffc32972a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -332,7 +332,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml index 6c5f093aaa..7b959d1875 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml @@ -248,7 +248,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -335,7 +335,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml index 283becbbca..cc8709e115 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml index bfed80666e..c211264000 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml index f720b77c89..02534164b1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml index 96d28ca815..0d81bed55b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go index 884eedb39c..66219636ce 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go @@ -22,6 +22,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource" + "github.com/envoyproxy/gateway/internal/utils" ) // ResourceKind indicates the main resources of envoy-ratelimit, @@ -262,9 +263,9 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // set name if r.rateLimitDeployment.Name != nil { - deployment.ObjectMeta.Name = *r.rateLimitDeployment.Name + deployment.Name = *r.rateLimitDeployment.Name } else { - deployment.ObjectMeta.Name = r.Name() + deployment.Name = r.Name() } if r.ownerReferenceUID != nil { @@ -282,7 +283,8 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // apply merge patch to deployment var err error - if deployment, err = r.rateLimitDeployment.ApplyMergePatch(deployment); err != nil { + deploymentConfig := r.rateLimitDeployment + if deployment, err = utils.MergeWithPatch(deployment, deploymentConfig.Patch); err != nil { return nil, err } @@ -335,7 +337,7 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod hpa.Spec.ScaleTargetRef.Name = r.Name() } - if hpa, err = hpaConfig.ApplyMergePatch(hpa); err != nil { + if hpa, err = utils.MergeWithPatch(hpa, hpaConfig.Patch); err != nil { return nil, err } diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go index a7bc1479e5..f6b9b3a914 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go @@ -6,7 +6,6 @@ package ratelimit import ( - "flag" "fmt" "os" "strconv" @@ -27,10 +26,9 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - const ( // RedisAuthEnvVar is the redis auth. RedisAuthEnvVar = "REDIS_AUTH" @@ -200,7 +198,7 @@ func TestConfigmap(t *testing.T) { cm, err := r.ConfigMap("") require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { cmYAML, err := yaml.Marshal(cm) require.NoError(t, err) // nolint:gosec @@ -770,7 +768,7 @@ func TestDeployment(t *testing.T) { dp, err := r.Deployment() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(dp) require.NoError(t, err) // nolint:gosec @@ -886,7 +884,7 @@ func TestHorizontalPodAutoscaler(t *testing.T) { hpa, err := r.HorizontalPodAutoscaler() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { hpaYAML, err := yaml.Marshal(hpa) require.NoError(t, err) // nolint:gosec diff --git a/internal/infrastructure/runner/runner.go b/internal/infrastructure/runner/runner.go index 4de124caf8..86c9f2d4f3 100644 --- a/internal/infrastructure/runner/runner.go +++ b/internal/infrastructure/runner/runner.go @@ -51,7 +51,7 @@ func (r *Runner) Start(ctx context.Context) (err error) { return nil } - r.mgr, err = infrastructure.NewManager(ctx, &r.Config.Server, r.Logger) + r.mgr, err = infrastructure.NewManager(ctx, &r.Server, r.Logger) if err != nil { r.Logger.Error(err, "failed to create new manager") return err diff --git a/internal/ir/xds.go b/internal/ir/xds.go index db9dfc4e02..777e33eb9c 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -2848,7 +2848,7 @@ type BackOffPolicy struct { // TLSUpstreamConfig contains sni and ca file in []byte format. // +k8s:deepcopy-gen=true type TLSUpstreamConfig struct { - SNI string `json:"sni,omitempty" yaml:"sni,omitempty"` + SNI *string `json:"sni,omitempty" yaml:"sni,omitempty"` UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty" yaml:"useSystemTrustStore,omitempty"` CACertificate *TLSCACertificate `json:"caCertificate,omitempty" yaml:"caCertificate,omitempty"` TLSConfig `json:",inline"` @@ -2856,8 +2856,9 @@ type TLSUpstreamConfig struct { func (t *TLSUpstreamConfig) ToTLSConfig() (*tls.Config, error) { // nolint:gosec - tlsConfig := &tls.Config{ - ServerName: t.SNI, + tlsConfig := &tls.Config{} + if t.SNI != nil { + tlsConfig.ServerName = *t.SNI } if t.MinVersion != nil { tlsConfig.MinVersion = t.MinVersion.Int() diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 39f4de5435..5b27f1e48f 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -3577,6 +3577,11 @@ func (in *TLSInspectorConfig) DeepCopy() *TLSInspectorConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSUpstreamConfig) DeepCopyInto(out *TLSUpstreamConfig) { *out = *in + if in.SNI != nil { + in, out := &in.SNI, &out.SNI + *out = new(string) + **out = **in + } if in.CACertificate != nil { in, out := &in.CACertificate, &out.CACertificate *out = new(TLSCACertificate) diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 55b682aff7..fcd05c37d4 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -10,7 +10,6 @@ import ( "context" "encoding/json" "errors" - "flag" "fmt" "io" "os" @@ -28,9 +27,9 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" -) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") + "github.com/envoyproxy/gateway/internal/utils/test" +) func TestCounter(t *testing.T) { name := "counter_metric" @@ -213,7 +212,7 @@ func newTestMetricsProvider(metricType string, writer io.Writer) (*metric.MeterP } func loadMetricsFile(t *testing.T, name string, reader io.Reader) { - if !*overrideTestData { + if !test.OverrideTestData() { fname := fmt.Sprintf("testdata/%s.json", name) // nolint:gosec @@ -229,7 +228,7 @@ func loadMetricsFile(t *testing.T, name string, reader io.Reader) { } func exporterWriter(name string, origin io.ReadWriter) (io.ReadWriter, error) { - if *overrideTestData { + if test.OverrideTestData() { fname := fmt.Sprintf("testdata/%s.json", name) // nolint:gosec diff --git a/internal/provider/file/file.go b/internal/provider/file/file.go index 1cacb8e602..94bbf2b37c 100644 --- a/internal/provider/file/file.go +++ b/internal/provider/file/file.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strings" + "sync" "sync/atomic" "time" @@ -25,33 +26,45 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/filewatcher" "github.com/envoyproxy/gateway/internal/message" + "github.com/envoyproxy/gateway/internal/provider/kubernetes" "github.com/envoyproxy/gateway/internal/utils/path" ) type Provider struct { - paths []string - logger logr.Logger - watcher filewatcher.FileWatcher - resourcesStore *resourcesStore - extensionManagerEnabled bool + paths []string + logger logr.Logger + watcher filewatcher.FileWatcher + resources *message.ProviderResources + reconciler *kubernetes.OfflineGatewayAPIReconciler + store *resourcesStore + status *StatusHandler // ready indicates whether the provider can start watching filesystem events. ready atomic.Bool } -func New(svr *config.Server, resources *message.ProviderResources) (*Provider, error) { +func New(ctx context.Context, svr *config.Server, resources *message.ProviderResources) (*Provider, error) { logger := svr.Logger.Logger paths := sets.New[string]() if svr.EnvoyGateway.Provider.Custom.Resource.File != nil { paths.Insert(svr.EnvoyGateway.Provider.Custom.Resource.File.Paths...) } + // Create gateway-api offline reconciler. + statusHandler := NewStatusHandler(logger) + reconciler, err := kubernetes.NewOfflineGatewayAPIController(ctx, svr, statusHandler.Writer(), resources) + if err != nil { + return nil, fmt.Errorf("failed to create offline gateway-api controller") + } + return &Provider{ - paths: paths.UnsortedList(), - logger: logger, - watcher: filewatcher.NewWatcher(), - resourcesStore: newResourcesStore(svr.EnvoyGateway.Gateway.ControllerName, resources, logger), - extensionManagerEnabled: svr.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil, + paths: paths.UnsortedList(), + logger: logger, + watcher: filewatcher.NewWatcher(), + resources: resources, + reconciler: reconciler, + store: newResourcesStore(svr.EnvoyGateway.Gateway.ControllerName, reconciler.Client, resources, logger), + status: statusHandler, }, nil } @@ -73,13 +86,18 @@ func (p *Provider) Start(ctx context.Context) error { } go p.startHealthProbeServer(ctx, readyzChecker) - // Subscribe resources status. - p.subscribeAndUpdateStatus(ctx) + // Offline controller should be started before initial resources load. + // Nor we may lose some messages from controller. + wg := new(sync.WaitGroup) + wg.Add(2) + go p.startReconciling(ctx, wg) + go p.status.Start(ctx, wg) + wg.Wait() initDirs, initFiles := path.ListDirsAndFiles(p.paths) - // Initially load resources from paths on host. - if err := p.resourcesStore.LoadAndStore(initFiles.UnsortedList(), initDirs.UnsortedList()); err != nil { - return fmt.Errorf("failed to load resources into store: %w", err) + // Initially load resources. + if err := p.store.ReloadAll(ctx, initFiles.UnsortedList(), initDirs.UnsortedList()); err != nil { + p.logger.Error(err, "failed to reload resources initially") } // Add paths to the watcher, and aggregate all path channels into one. @@ -150,18 +168,31 @@ func (p *Provider) Start(ctx context.Context) error { } p.logger.Info("file changed", "op", event.Op, "name", event.Name, "dir", filepath.Dir(event.Name)) - switch event.Op { - case fsnotify.Create, fsnotify.Write, fsnotify.Remove: - // Since we do not watch any events in the subdirectories, any events involving files - // modifications in current directory will trigger the event handling. - goto handle - default: - // do nothing - continue + handle: + if err := p.store.ReloadAll(ctx, curFiles.UnsortedList(), curDirs.UnsortedList()); err != nil { + p.logger.Error(err, "error when reload resources", "op", event.Op, "name", event.Name) } + } + } +} - handle: - p.resourcesStore.HandleEvent(curFiles.UnsortedList(), curDirs.UnsortedList()) +// startReconciling starts reconcile on offline controller when receiving signal from resources store. +func (p *Provider) startReconciling(ctx context.Context, ready *sync.WaitGroup) { + p.logger.Info("start reconciling") + defer p.logger.Info("stop reconciling") + ready.Done() + + for { + select { + case rid := <-p.store.reconcile: + p.logger.Info("start reconcile", "id", rid, "time", time.Now()) + if err := p.reconciler.Reconcile(ctx); err != nil { + p.logger.Error(err, "failed to reconcile", "id", rid) + } + p.logger.Info("reconcile finished", "id", rid, "time", time.Now()) + + case <-ctx.Done(): + return } } } diff --git a/internal/provider/file/file_test.go b/internal/provider/file/file_test.go index 57909ea696..89db93f763 100644 --- a/internal/provider/file/file_test.go +++ b/internal/provider/file/file_test.go @@ -6,6 +6,7 @@ package file import ( + "bytes" "context" "html/template" "io" @@ -18,6 +19,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -36,16 +38,32 @@ type resourcesParam struct { GatewayName string GatewayListenerPort string HTTPRouteName string + HTTPRouteHostname string BackendName string + EndpointPort string } -func newDefaultResourcesParam() *resourcesParam { +func newResourcesParam1() *resourcesParam { return &resourcesParam{ - GatewayClassName: "eg", - GatewayName: "eg", - GatewayListenerPort: "8888", - HTTPRouteName: "backend", - BackendName: "backend", + GatewayClassName: "eg-1", + GatewayName: "eg-1", + GatewayListenerPort: "8801", + HTTPRouteName: "backend-1", + HTTPRouteHostname: "www.test1.com", + BackendName: "backend-1", + EndpointPort: "3001", + } +} + +func newResourcesParam2() *resourcesParam { + return &resourcesParam{ + GatewayClassName: "eg-2", + GatewayName: "eg-2", + GatewayListenerPort: "8802", + HTTPRouteName: "backend-2", + HTTPRouteHostname: "www.test2.com", + BackendName: "backend-2", + EndpointPort: "3002", } } @@ -70,22 +88,24 @@ func newFileProviderConfig(paths []string) (*config.Server, error) { } func TestFileProvider(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + watchFileBase, _ := os.MkdirTemp(os.TempDir(), "test-files-*") watchFilePath := filepath.Join(watchFileBase, "test.yaml") watchDirPath, _ := os.MkdirTemp(os.TempDir(), "test-dir-*") // Prepare the watched test file. - writeResourcesFile(t, "testdata/resources.tmpl", watchFilePath, newDefaultResourcesParam()) + writeResourcesFile(t, watchFilePath, newResourcesParam1()) require.FileExists(t, watchFilePath) require.DirExists(t, watchDirPath) cfg, err := newFileProviderConfig([]string{watchFilePath, watchDirPath}) require.NoError(t, err) pResources := new(message.ProviderResources) - fp, err := New(cfg, pResources) + fp, err := New(ctx, cfg, pResources) require.NoError(t, err) // Start file provider. go func() { - if err := fp.Start(context.Background()); err != nil { + if err := fp.Start(ctx); err != nil { t.Errorf("failed to start file provider: %v", err) } }() @@ -93,104 +113,148 @@ func TestFileProvider(t *testing.T) { // Wait for file provider to be ready. waitFileProviderReady(t) - require.Equal(t, "gateway.envoyproxy.io/gatewayclass-controller", fp.resourcesStore.name) + require.Equal(t, "gateway.envoyproxy.io/gatewayclass-controller", fp.store.name) t.Run("initial resource load", func(t *testing.T) { - require.NotZero(t, pResources.GatewayAPIResources.Len()) - resources := pResources.GetResourcesByGatewayClass("eg") + // Wait for the first reconcile to kick in. + require.Eventually(t, func() bool { + return pResources.GatewayAPIResources.Len() > 0 + }, resourcesUpdateTimeout, resourcesUpdateTick) + resources := pResources.GetResourcesByGatewayClass("eg-1") require.NotNil(t, resources) want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) - - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) t.Run("rename the watched file then rename it back", func(t *testing.T) { - // Rename it + // Rename it first, the watched file is losed. renameFilePath := filepath.Join(watchFileBase, "foobar.yaml") err := os.Rename(watchFilePath, renameFilePath) require.NoError(t, err) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return pResources.GetResourcesByGatewayClass("eg-1") == nil }, resourcesUpdateTimeout, resourcesUpdateTick) - // Rename it back + // Rename it back, the watched file is resumed. err = os.Rename(renameFilePath, watchFilePath) require.NoError(t, err) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") != nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil }, resourcesUpdateTimeout, resourcesUpdateTick) - resources := pResources.GetResourcesByGatewayClass("eg") + resources := pResources.GetResourcesByGatewayClass("eg-1") want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) - - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) t.Run("remove the watched file", func(t *testing.T) { - err := os.Remove(watchFilePath) - require.NoError(t, err) + require.NoError(t, os.Remove(watchFilePath)) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return len(pResources.GetResources()) == 0 }, resourcesUpdateTimeout, resourcesUpdateTick) }) - t.Run("add a file in watched dir", func(t *testing.T) { - // Write a new file under watched directory. + t.Run("add a new file in watched dir", func(t *testing.T) { + // Write a new file under empty watched directory. newFilePath := filepath.Join(watchDirPath, "test.yaml") - writeResourcesFile(t, "testdata/resources.tmpl", newFilePath, newDefaultResourcesParam()) + writeResourcesFile(t, newFilePath, newResourcesParam1()) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") != nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil }, resourcesUpdateTimeout, resourcesUpdateTick) - resources := pResources.GetResourcesByGatewayClass("eg") + resources := pResources.GetResourcesByGatewayClass("eg-1") want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) + }) - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + t.Run("rename the file then rename it back in watched dir", func(t *testing.T) { + // Rename it first. + srcFilePath := filepath.Join(watchDirPath, "test.yaml") + dstFilePath := filepath.Join(watchDirPath, "foobar.yaml") + err := os.Rename(srcFilePath, dstFilePath) + require.NoError(t, err) + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + // Rename it back. + err = os.Rename(dstFilePath, srcFilePath) + require.NoError(t, err) + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + resources := pResources.GetResourcesByGatewayClass("eg-1") + want := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) - t.Run("remove a file in watched dir", func(t *testing.T) { + t.Run("update file content in watched dir", func(t *testing.T) { + // Rewrite the file under watched directory. newFilePath := filepath.Join(watchDirPath, "test.yaml") - err := os.Remove(newFilePath) - require.NoError(t, err) + writeResourcesFile(t, newFilePath, newResourcesParam2()) + + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") == nil && + pResources.GetResourcesByGatewayClass("eg-2") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + }) + + t.Run("add another file with new gatewayclass in watched dir", func(t *testing.T) { + // The test.yaml was changed by previous case, safe to use resources param 1 here. + newFilePath := filepath.Join(watchDirPath, "another.yaml") + writeResourcesFile(t, newFilePath, newResourcesParam1()) + require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil && + pResources.GetResourcesByGatewayClass("eg-2") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + resources1 := pResources.GetResourcesByGatewayClass("eg-1") + want1 := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.1.yaml", want1) + cmpResources(t, want1, resources1) + + resources2 := pResources.GetResourcesByGatewayClass("eg-2") + want2 := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.2.yaml", want2) + cmpResources(t, want2, resources2) + }) + + t.Run("remove all files in watched dir", func(t *testing.T) { + fp1 := filepath.Join(watchDirPath, "test.yaml") + fp2 := filepath.Join(watchDirPath, "another.yaml") + require.NoError(t, os.Remove(fp1)) + require.NoError(t, os.Remove(fp2)) + require.Eventually(t, func() bool { + return len(pResources.GetResources()) == 0 }, resourcesUpdateTimeout, resourcesUpdateTick) }) t.Cleanup(func() { + cancel() _ = os.RemoveAll(watchFileBase) _ = os.RemoveAll(watchDirPath) }) } -func writeResourcesFile(t *testing.T, tmpl, dst string, params *resourcesParam) { - dstFile, err := os.Create(dst) - require.NoError(t, err) +func writeResourcesFile(t *testing.T, dst string, params *resourcesParam) { + var buf bytes.Buffer // Write parameters into target file. - tmplFile, err := template.ParseFiles(tmpl) + tmplFile, err := template.ParseFiles("testdata/resources.tmpl") require.NoError(t, err) - err = tmplFile.Execute(dstFile, params) + err = tmplFile.Execute(&buf, params) require.NoError(t, err) - require.NoError(t, dstFile.Close()) + // Write file in an atomic way, prevent unnecessary reconcile. + require.NoError(t, os.WriteFile(dst, buf.Bytes(), 0o600)) } func waitFileProviderReady(t *testing.T) { @@ -223,3 +287,12 @@ func mustUnmarshal(t *testing.T, path string, out interface{}) { require.NoError(t, err) require.NoError(t, yaml.UnmarshalStrict(content, out, yaml.DisallowUnknownFields)) } + +func cmpResources(t *testing.T, x, y interface{}) { + opts := []cmp.Option{ + cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), + cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion"), + cmpopts.EquateEmpty(), + } + require.Empty(t, cmp.Diff(x, y, opts...)) +} diff --git a/internal/provider/file/status.go b/internal/provider/file/status.go index 83d86086bd..0e50c5fbb2 100644 --- a/internal/provider/file/status.go +++ b/internal/provider/file/status.go @@ -8,287 +8,99 @@ package file import ( "context" "fmt" + "sync" - "k8s.io/apimachinery/pkg/types" - gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" - egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/envoyproxy/gateway/internal/gatewayapi/resource" - "github.com/envoyproxy/gateway/internal/gatewayapi/status" - "github.com/envoyproxy/gateway/internal/message" + "github.com/envoyproxy/gateway/internal/provider/kubernetes" ) -func (p *Provider) subscribeAndUpdateStatus(ctx context.Context) { - // TODO: trigger gatewayclass status update in file-provider - // GatewayClass object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "gatewayclass-status"}, - p.resourcesStore.resources.GatewayClassStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GatewayClassStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindGateway) - }, - ) - p.logger.Info("gatewayClass status subscriber shutting down") - }() - - // Gateway object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "gateway-status"}, - p.resourcesStore.resources.GatewayStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GatewayStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - // Update Gateway conditions, ignore addresses - gtw := new(gwapiv1.Gateway) - gtw.Status = *update.Value - status.UpdateGatewayStatusAccepted(gtw) - - p.logStatus(gtw.Status, resource.KindGateway) - }, - ) - p.logger.Info("gateway status subscriber shutting down") - }() - - // HTTPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "httproute-status"}, - p.resourcesStore.resources.HTTPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.HTTPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindHTTPRoute) - }, - ) - p.logger.Info("httpRoute status subscriber shutting down") - }() - - // GRPCRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "grpcroute-status"}, - p.resourcesStore.resources.GRPCRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GRPCRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindGRPCRoute) - }, - ) - p.logger.Info("grpcRoute status subscriber shutting down") - }() - - // TLSRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "tlsroute-status"}, - p.resourcesStore.resources.TLSRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.TLSRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindTLSRoute) - }, - ) - p.logger.Info("tlsRoute status subscriber shutting down") - }() - - // TCPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "tcproute-status"}, - p.resourcesStore.resources.TCPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.TCPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindTCPRoute) - }, - ) - p.logger.Info("tcpRoute status subscriber shutting down") - }() - - // UDPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "udproute-status"}, - p.resourcesStore.resources.UDPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.UDPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindUDPRoute) - }, - ) - p.logger.Info("udpRoute status subscriber shutting down") - }() - - // EnvoyPatchPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "envoypatchpolicy-status"}, - p.resourcesStore.resources.EnvoyPatchPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindEnvoyPatchPolicy) - }, - ) - p.logger.Info("envoyPatchPolicy status subscriber shutting down") - }() - - // ClientTrafficPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "clienttrafficpolicy-status"}, - p.resourcesStore.resources.ClientTrafficPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindClientTrafficPolicy) - }, - ) - p.logger.Info("clientTrafficPolicy status subscriber shutting down") - }() - - // BackendTrafficPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backendtrafficpolicy-status"}, - p.resourcesStore.resources.BackendTrafficPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindBackendTrafficPolicy) - }, - ) - p.logger.Info("backendTrafficPolicy status subscriber shutting down") - }() +type StatusHandler struct { + logger logr.Logger + updateChannel chan kubernetes.Update + wg *sync.WaitGroup +} - // SecurityPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "securitypolicy-status"}, - p.resourcesStore.resources.SecurityPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } +func NewStatusHandler(log logr.Logger) *StatusHandler { + u := &StatusHandler{ + logger: log, + updateChannel: make(chan kubernetes.Update, 1000), + wg: new(sync.WaitGroup), + } - p.logStatus(*update.Value, resource.KindSecurityPolicy) - }, - ) - p.logger.Info("securityPolicy status subscriber shutting down") - }() + u.wg.Add(1) - // BackendTLSPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backendtlspolicy-status"}, - p.resourcesStore.resources.BackendTLSPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + return u +} - p.logStatus(*update.Value, resource.KindBackendTLSPolicy) - }, - ) - p.logger.Info("backendTLSPolicy status subscriber shutting down") - }() +// Start runs the goroutine to perform status writes. +func (u *StatusHandler) Start(ctx context.Context, ready *sync.WaitGroup) { + u.logger.Info("started status update handler") + defer u.logger.Info("stopped status update handler") + + // Enable Updaters to start sending updates to this handler. + u.wg.Done() + ready.Done() + + for { + select { + case <-ctx.Done(): + return + case update := <-u.updateChannel: + u.logger.Info("received a status update", "namespace", update.NamespacedName.Namespace, + "name", update.NamespacedName.Name) + + u.logStatus(update) + } + } +} - // EnvoyExtensionPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "envoyextensionpolicy-status"}, - p.resourcesStore.resources.EnvoyExtensionPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } +func (u *StatusHandler) logStatus(update kubernetes.Update) { + obj := update.Resource + newObj := update.Mutator.Mutate(obj) + log := u.logger.WithValues("key", update.NamespacedName.String()) - p.logStatus(*update.Value, resource.KindEnvoyExtensionPolicy) - }, - ) - p.logger.Info("envoyExtensionPolicy status subscriber shutting down") - }() + // Log the resource status. + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(newObj) + if err != nil { + log.Error(err, "failed to convert object") + return + } - // Backend object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backend-status"}, - p.resourcesStore.resources.BackendStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *egv1a1.BackendStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + rawStatus, ok := raw["status"] + if !ok { + log.Error(fmt.Errorf("no status field"), "failed to log status") + return + } - p.logStatus(*update.Value, resource.KindBackend) - }, - ) - p.logger.Info("backend status subscriber shutting down") - }() + byteStatus, err := yaml.Marshal(rawStatus) + if err != nil { + log.Error(err, "failed to marshal object") + return + } - if p.extensionManagerEnabled { - // ExtensionServerPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "extensionserverpolicies-status"}, - p.resourcesStore.resources.ExtensionPolicyStatuses.Subscribe(ctx), - func(update message.Update[message.NamespacedNameAndGVK, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + log.Info(fmt.Sprintf("Got new status for %s\n%s", kubernetes.KindOf(obj), string(byteStatus))) +} - p.logStatus(*update.Value, "ExtensionServerPolicy") - }, - ) - p.logger.Info("extensionServerPolicies status subscriber shutting down") - }() +// Writer retrieves the interface that should be used to write to the StatusHandler. +func (u *StatusHandler) Writer() kubernetes.Updater { + return &StatusWriter{ + updateChannel: u.updateChannel, + wg: u.wg, } } -func (p *Provider) logStatus(obj interface{}, statusType string) { - if status, err := yaml.Marshal(obj); err == nil { - p.logger.Info(fmt.Sprintf("Got new status for %s \n%s", statusType, string(status))) - } else { - p.logger.Error(err, "failed to log status", "type", statusType) - } +// StatusWriter takes status updates and sends these to the StatusHandler via a channel. +type StatusWriter struct { + updateChannel chan<- kubernetes.Update + wg *sync.WaitGroup +} + +// Send sends the given Update off to the update channel for writing by the StatusHandler. +func (u *StatusWriter) Send(update kubernetes.Update) { + // Wait until updater is ready + u.wg.Wait() + u.updateChannel <- update } diff --git a/internal/provider/file/store.go b/internal/provider/file/store.go index 448f1807cf..cf70c78b28 100644 --- a/internal/provider/file/store.go +++ b/internal/provider/file/store.go @@ -6,68 +6,349 @@ package file import ( + "context" + "crypto/rand" + "errors" + "fmt" + "math" + "math/big" + "reflect" + "sort" + "time" + "github.com/go-logr/logr" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" "github.com/envoyproxy/gateway/internal/message" ) +const ( + GatewayDeletionOrder = 3 +) + type resourcesStore struct { name string + keys sets.Set[storeKey] + client client.Client resources *message.ProviderResources + reconcile chan int64 logger logr.Logger } -func newResourcesStore(name string, resources *message.ProviderResources, logger logr.Logger) *resourcesStore { +type storeKey struct { + schema.GroupVersionKind + types.NamespacedName + + // deletionOrder is used to determine the order in which a resource is deleted. + // The larger the value, the earlier it is deleted. + deletionOrder int +} + +func (s storeKey) String() string { + return fmt.Sprintf("%s/%s/%d", + s.GroupVersionKind.String(), s.NamespacedName.String(), s.deletionOrder) +} + +func newResourcesStore(name string, client client.Client, resources *message.ProviderResources, logger logr.Logger) *resourcesStore { return &resourcesStore{ name: name, + keys: sets.New[storeKey](), + client: client, resources: resources, + reconcile: make(chan int64), logger: logger, } } -// HandleEvent simply removes all the resources and triggers a resources reload from files -// and directories despite of the event type. -// TODO: Enhance this method by respecting the event type, and add support for multiple GatewayClass. -func (r *resourcesStore) HandleEvent(files, dirs []string) { - r.logger.Info("reload all resources") - - r.resources.GatewayAPIResources.Delete(r.name) - if err := r.LoadAndStore(files, dirs); err != nil { - r.logger.Error(err, "failed to load and store resources") +func newStoreKey(obj client.Object) storeKey { + return storeKey{ + GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), + NamespacedName: client.ObjectKeyFromObject(obj), } } -// LoadAndStore loads and stores all resources from files and directories. -func (r *resourcesStore) LoadAndStore(files, dirs []string) error { +// ReloadAll loads and stores all resources from all given files and directories. +func (r *resourcesStore) ReloadAll(ctx context.Context, files, dirs []string) error { + // TODO(sh2): add arbitrary number of resources support for load function. resources, err := loadFromFilesAndDirs(files, dirs) if err != nil { return err } - // TODO(sh2): For now, we assume that one file only contains one GatewayClass and all its other - // related resources, like Gateway, HTTPRoute, etc. If we managed to extend Resources structure, - // we also need to process all the resources and its relationship, like what is done in - // Kubernetes provider. However, this will cause us to maintain two places of the same logic - // in each provider. The ideal case is two different providers share the same resources process logic. - // - // - This issue is tracked by https://github.com/envoyproxy/gateway/issues/3213 - - // We cannot make sure by the time the Write event was triggered, whether the GatewayClass exist, - // so here we just simply Store the first gatewayapi.Resources that has GatewayClass. - gwcResources := make(resource.ControllerResources, 0, 1) + var errList error + currentKeys := sets.New[storeKey]() for _, res := range resources { - if res.GatewayClass != nil { - gwcResources = append(gwcResources, res) + collectKeys, err := r.storeResources(ctx, res) + if err != nil { + errList = errors.Join(errList, err) + } + currentKeys = currentKeys.Union(collectKeys) + } + + // If no resources were created or updated, stop reconciling. + if errList != nil && len(currentKeys) == 0 { + return errList + } + + // Remove the resources that no longer exist. + rn := 0 + deletedKeys := r.keys.Difference(currentKeys) + for _, k := range deletionOrderKeyList(deletedKeys) { + delObj := makeUnstructuredObjectFromKey(k) + if err := r.client.Delete(ctx, delObj); err != nil { + errList = errors.Join(errList, err) + // Insert back if the object is not be removed. + currentKeys.Insert(k) + } else if k.deletionOrder <= GatewayDeletionOrder { + // Reconcile once if gateway got deleted, this may be able to + // remove the finalizer on gatewayclass. + r.reconcile <- generateReconcileID() + rn++ } } - if len(gwcResources) == 0 { - return nil + + r.keys = currentKeys + r.reconcile <- generateReconcileID() + rn++ + + r.logger.Info("reload resources finished", + "reload_resources_num", len(r.keys), "reconcile_times", rn, "time", time.Now()) + return errList +} + +// storeResources stores resources via offline gateway-api client. +// For file provider, all gateway-api resources will be stored except: +// - Service +// - ServiceImport +// - EndpointSlices +// Becasues these resources has no effects on the host infra layer. +func (r *resourcesStore) storeResources(ctx context.Context, re *resource.Resources) (sets.Set[storeKey], error) { + if re == nil { + return nil, nil + } + + var ( + errs error + collectKeys = sets.New[storeKey]() + ) + + if err := r.stroeObjectWithKeys(ctx, re.EnvoyProxyForGatewayClass, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + + if err := r.stroeObjectWithKeys(ctx, re.GatewayClass, collectKeys); err != nil { + errs = errors.Join(errs, err) } - r.resources.GatewayAPIResources.Store(r.name, &gwcResources) - r.logger.Info("loaded and stored resources successfully") + for _, obj := range re.EnvoyProxiesForGateways { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Gateways { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.HTTPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.GRPCRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.TLSRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.TCPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.UDPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ReferenceGrants { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Namespaces { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Secrets { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ConfigMaps { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.EnvoyPatchPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ClientTrafficPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.BackendTrafficPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.SecurityPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.BackendTLSPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.EnvoyExtensionPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Backends { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.HTTPRouteFilters { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + return collectKeys, errs +} + +// stroeObjectWithKeys stores object while collecting its key. +func (r *resourcesStore) stroeObjectWithKeys(ctx context.Context, obj client.Object, keys sets.Set[storeKey]) error { + key, err := r.storeObject(ctx, obj) + if err != nil && key != nil { + return fmt.Errorf("failed to store %s %s: %w", key.Kind, key.NamespacedName.String(), err) + } else if err != nil { + return fmt.Errorf("failed to store object: %w", err) + } + + if key != nil { + keys.Insert(*key) + } return nil } + +// storeObject will do create for non-exist object and update for existing object. +func (r *resourcesStore) storeObject(ctx context.Context, obj client.Object) (*storeKey, error) { + if obj == nil || reflect.ValueOf(obj).IsNil() { + return nil, nil + } + + var ( + err error + key = newStoreKey(obj) + oldObj = makeUnstructuredObjectFromKey(key) + ) + + if err = r.client.Get(ctx, key.NamespacedName, oldObj); err == nil { + return &key, r.client.Patch(ctx, obj, client.Merge) + } + if kerrors.IsNotFound(err) { + return &key, r.client.Create(ctx, obj) + } + + return nil, err +} + +func makeUnstructuredObjectFromKey(key storeKey) *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(key.GroupVersionKind) + obj.SetNamespace(key.Namespace) + obj.SetName(key.Name) + return obj +} + +// deletionOrderKeyList returns a list sorted in descending order by deletionOrder in its key. +func deletionOrderKeyList(keys sets.Set[storeKey]) []storeKey { + out := keys.UnsortedList() + for i, k := range out { + switch k.Kind { + case resource.KindNamespace, resource.KindReferenceGrant, + resource.KindConfigMap, resource.KindSecret: + out[i].deletionOrder = GatewayDeletionOrder - 3 + + case resource.KindEnvoyProxy: + out[i].deletionOrder = GatewayDeletionOrder - 2 + + case resource.KindGatewayClass: + out[i].deletionOrder = GatewayDeletionOrder - 1 + + case resource.KindGateway: + out[i].deletionOrder = GatewayDeletionOrder + + case resource.KindHTTPRoute, resource.KindGRPCRoute, + resource.KindTLSRoute, resource.KindTCPRoute, resource.KindUDPRoute, + resource.KindSecurityPolicy, resource.KindClientTrafficPolicy, resource.KindBackendTrafficPolicy, + resource.KindEnvoyPatchPolicy, resource.KindEnvoyExtensionPolicy, resource.KindBackendTLSPolicy: + out[i].deletionOrder = GatewayDeletionOrder + 1 + + case resource.KindBackend, resource.KindHTTPRouteFilter: + out[i].deletionOrder = GatewayDeletionOrder + 2 + + default: + out[i].deletionOrder = GatewayDeletionOrder + 3 + } + } + + sort.Slice(out, func(i, j int) bool { + return out[i].deletionOrder > out[j].deletionOrder + }) + return out +} + +func generateReconcileID() int64 { + n, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) + return n.Int64() +} diff --git a/internal/provider/file/testdata/resources.all.yaml b/internal/provider/file/testdata/resources.1.yaml similarity index 83% rename from internal/provider/file/testdata/resources.all.yaml rename to internal/provider/file/testdata/resources.1.yaml index 989ae8025a..fef92caaa7 100644 --- a/internal/provider/file/testdata/resources.all.yaml +++ b/internal/provider/file/testdata/resources.1.yaml @@ -3,21 +3,23 @@ backends: apiVersion: gateway.envoyproxy.io/v1alpha1 metadata: creationTimestamp: null - name: backend + name: backend-1 namespace: envoy-gateway-system spec: type: Endpoints endpoints: - ip: address: 0.0.0.0 - port: 3000 + port: 3001 status: {} gatewayClass: kind: GatewayClass apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: eg + name: eg-1 + finalizers: + - gateway-exists-finalizer.gateway.networking.k8s.io spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller status: {} @@ -26,16 +28,16 @@ gateways: apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: eg + name: eg-1 namespace: envoy-gateway-system spec: - gatewayClassName: eg + gatewayClassName: eg-1 listeners: - allowedRoutes: namespaces: from: Same name: http - port: 8888 + port: 8801 protocol: HTTP status: {} httpRoutes: @@ -43,20 +45,20 @@ httpRoutes: apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: backend + name: backend-1 namespace: envoy-gateway-system spec: hostnames: - - www.example.com + - www.test1.com parentRefs: - group: gateway.networking.k8s.io kind: Gateway - name: eg + name: eg-1 rules: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend + name: backend-1 weight: 1 matches: - path: diff --git a/internal/provider/file/testdata/resources.2.yaml b/internal/provider/file/testdata/resources.2.yaml new file mode 100644 index 0000000000..938a7845b4 --- /dev/null +++ b/internal/provider/file/testdata/resources.2.yaml @@ -0,0 +1,76 @@ +backends: +- kind: Backend + apiVersion: gateway.envoyproxy.io/v1alpha1 + metadata: + creationTimestamp: null + name: backend-2 + namespace: envoy-gateway-system + spec: + type: Endpoints + endpoints: + - ip: + address: 0.0.0.0 + port: 3002 + status: {} +gatewayClass: + kind: GatewayClass + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: eg-2 + finalizers: + - gateway-exists-finalizer.gateway.networking.k8s.io + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + status: {} +gateways: +- kind: Gateway + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: eg-2 + namespace: envoy-gateway-system + spec: + gatewayClassName: eg-2 + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http + port: 8802 + protocol: HTTP + status: {} +httpRoutes: +- kind: HTTPRoute + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: backend-2 + namespace: envoy-gateway-system + spec: + hostnames: + - www.test2.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg-2 + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-2 + weight: 1 + matches: + - path: + type: PathPrefix + value: / + status: + parents: null +namespaces: +- kind: Namespace + apiVersion: v1 + metadata: + creationTimestamp: null + name: envoy-gateway-system + spec: {} + status: {} diff --git a/internal/provider/file/testdata/resources.tmpl b/internal/provider/file/testdata/resources.tmpl index f34bf1e0c3..d472be52b0 100644 --- a/internal/provider/file/testdata/resources.tmpl +++ b/internal/provider/file/testdata/resources.tmpl @@ -24,7 +24,7 @@ spec: parentRefs: - name: {{.GatewayName}} hostnames: - - "www.example.com" + - {{.HTTPRouteHostname}} rules: - backendRefs: - group: "gateway.envoyproxy.io" @@ -43,4 +43,4 @@ spec: endpoints: - ip: address: 0.0.0.0 - port: 3000 + port: {{.EndpointPort}} diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index e84963337f..897b030dcf 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -153,11 +153,11 @@ func newGatewayAPIController(ctx context.Context, mgr manager.Manager, cfg *conf case <-ctx.Done(): return case <-cfg.Elected: - r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil) + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) } }() } else { - r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil) + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) } return nil } @@ -407,6 +407,7 @@ func (r *gatewayAPIReconciler) managedGatewayClasses(ctx context.Context) ([]*gw // - ServiceImports // - EndpointSlices // - Backends +// - CACertificateRefs in the Backends func (r *gatewayAPIReconciler) processBackendRefs(ctx context.Context, gwcResource *resource.Resources, resourceMappings *resourceMappings) { for backendRef := range resourceMappings.allAssociatedBackendRefs { backendRefKind := gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService) @@ -463,6 +464,49 @@ func (r *gatewayAPIReconciler) processBackendRefs(ctx context.Context, gwcResour "name", string(backendRef.Name)) } } + + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + // if kind is not Secret or ConfigMap, we skip early to avoid further calculation overhead + if string(caCertRef.Kind) == resource.KindConfigMap || + string(caCertRef.Kind) == resource.KindSecret { + + var err error + caRefNew := gwapiv1.SecretObjectReference{ + Group: gatewayapi.GroupPtr(string(caCertRef.Group)), + Kind: gatewayapi.KindPtr(string(caCertRef.Kind)), + Name: caCertRef.Name, + Namespace: gatewayapi.NamespacePtr(backend.Namespace), + } + switch string(caCertRef.Kind) { + case resource.KindConfigMap: + err = r.processConfigMapRef( + ctx, + resourceMappings, + gwcResource, + resource.KindBackendTLSPolicy, + backend.Namespace, + backend.Name, + caRefNew) + + case resource.KindSecret: + err = r.processSecretRef( + ctx, + resourceMappings, + gwcResource, + resource.KindBackendTLSPolicy, + backend.Namespace, + backend.Name, + caRefNew) + } + if err != nil { + r.log.Error(err, + "failed to process CACertificateRef for Backend", + "backend", backend, "caCertificateRef", caCertRef.Name) + } + } + } + } } // Retrieve the EndpointSlices associated with the Service and ServiceImport @@ -565,57 +609,37 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing extAuth := policy.Spec.ExtAuth if extAuth != nil { - var backendRef *gwapiv1.BackendObjectReference + var backendRef gwapiv1.BackendObjectReference if extAuth.GRPC != nil { - backendRef = extAuth.GRPC.BackendRef + if extAuth.GRPC.BackendRef != nil { + backendRef = *extAuth.GRPC.BackendRef + } if len(extAuth.GRPC.BackendRefs) > 0 { if len(extAuth.GRPC.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.GRPC.BackendRefs[0]) + backendRef = extAuth.GRPC.BackendRefs[0].BackendObjectReference } } - } else { - backendRef = extAuth.HTTP.BackendRef + } else if extAuth.HTTP != nil { + if extAuth.HTTP.BackendRef != nil { + backendRef = *extAuth.HTTP.BackendRef + } if len(extAuth.HTTP.BackendRefs) > 0 { if len(extAuth.HTTP.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.HTTP.BackendRefs[0]) + backendRef = extAuth.HTTP.BackendRefs[0].BackendObjectReference } } } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindSecurityPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindSecurityPolicy, + policy.Namespace, + policy.Name, + backendRef); err != nil { + r.log.Error(err, + "failed to process ExtAuth BackendRef for SecurityPolicy", + "policy", policy, "backendRef", backendRef) } } @@ -638,12 +662,78 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs( }); err != nil { r.log.Error(err, "failed to process LocalJWKS ConfigMap", "policy", policy, "localJWKS", provider.LocalJWKS) } + } else if provider.RemoteJWKS != nil { + for _, br := range provider.RemoteJWKS.BackendRefs { + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindSecurityPolicy, + policy.Namespace, + policy.Name, + br.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process RemoteJWKS BackendRef for SecurityPolicy", + "policy", policy, "backendRef", br.BackendObjectReference) + } + } } } } } } +// processBackendRef adds the referenced BackendRef to the resourceMap for later processBackendRefs. +// If BackendRef exists in a different namespace and there is a ReferenceGrant, adds ReferenceGrant to the resourceTree. +func (r *gatewayAPIReconciler) processBackendRef( + ctx context.Context, + resourceMap *resourceMappings, + resourceTree *resource.Resources, + ownerKind string, + ownerNS string, + ownerName string, + backendRef gwapiv1.BackendObjectReference, +) error { + backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, ownerNS) + resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ + Group: backendRef.Group, + Kind: backendRef.Kind, + Namespace: gatewayapi.NamespacePtr(backendNamespace), + Name: backendRef.Name, + }) + + if backendNamespace != ownerNS { + from := ObjectKindNamespacedName{ + kind: ownerKind, + namespace: ownerNS, + name: ownerName, + } + to := ObjectKindNamespacedName{ + kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), + namespace: backendNamespace, + name: string(backendRef.Name), + } + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + return fmt.Errorf("failed to find ReferenceGrant: %w", err) + case refGrant == nil: + return fmt.Errorf( + "no matching ReferenceGrants found: from %s/%s to %s/%s", + from.kind, from.namespace, to.kind, to.namespace) + default: + // RefGrant found + if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { + resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) + } + } + } + return nil +} + // processOIDCHMACSecret adds the OIDC HMAC Secret to the resourceTree. // The OIDC HMAC Secret is created by the CertGen job and is used by SecurityPolicy // to configure OAuth2 filters. @@ -776,7 +866,7 @@ func (r *gatewayAPIReconciler) processCtpConfigMapRefs( policy.Name, caCertRef); err != nil { r.log.Error(err, - "failed to process CACertificateRef for SecurityPolicy", + "failed to process CACertificateRef for ClientTrafficPolicy", "policy", policy, "caCertificateRef", caCertRef.Name) } } @@ -1483,6 +1573,10 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M backendPredicates...)); err != nil { return err } + + if err := addBackendIndexers(ctx, mgr); err != nil { + return err + } } // Watch Node CRUDs to update Gateway Address exposed by Service of type NodePort. @@ -1857,9 +1951,9 @@ func (r *gatewayAPIReconciler) processGatewayParamsRef(ctx context.Context, gtw } ref := gtw.Spec.Infrastructure.ParametersRef - if !(string(ref.Group) == egv1a1.GroupVersion.Group && - ref.Kind == egv1a1.KindEnvoyProxy && - len(ref.Name) > 0) { + if string(ref.Group) != egv1a1.GroupVersion.Group || + ref.Kind != egv1a1.KindEnvoyProxy || + len(ref.Name) == 0 { return fmt.Errorf("unsupported parametersRef for gateway %s/%s", gtw.Namespace, gtw.Name) } @@ -1964,8 +2058,8 @@ func (r *gatewayAPIReconciler) processEnvoyProxy(ep *egv1a1.EnvoyProxy, resource for _, backendRef := range backendRefs { backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, ep.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -2104,7 +2198,7 @@ func (r *gatewayAPIReconciler) processExtensionServerPolicies( } _, foundTargetRef := policySpec["targetRef"] _, foundTargetRefs := policySpec["targetRefs"] - if !(foundTargetRef || foundTargetRefs) { + if !foundTargetRef && !foundTargetRefs { return fmt.Errorf("not a policy object - no targetRef or targetRefs found in %s.%s %s", policy.GetAPIVersion(), policy.GetKind(), policy.GetName()) } @@ -2136,42 +2230,17 @@ func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing for _, ep := range policy.Spec.ExtProc { for _, br := range ep.BackendRefs { - backendRef := br.BackendObjectReference - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindEnvoyExtensionPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindEnvoyExtensionPolicy, + policy.Namespace, + policy.Name, + br.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process ExtProc BackendRef for EnvoyExtensionPolicy", + "policy", policy, "backendRef", br.BackendObjectReference) } } } diff --git a/internal/provider/kubernetes/controller_offline.go b/internal/provider/kubernetes/controller_offline.go new file mode 100644 index 0000000000..e6d96bbf9f --- /dev/null +++ b/internal/provider/kubernetes/controller_offline.go @@ -0,0 +1,131 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package kubernetes + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1a3 "sigs.k8s.io/gateway-api/apis/v1alpha3" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/envoygateway" + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/message" +) + +// OfflineGatewayAPIReconciler can be used for non-kuberetes provider. +// It can let other providers to have the same reconcile logic without rely on apiserver. +type OfflineGatewayAPIReconciler struct { + gatewayAPIReconciler + + Client client.Client +} + +func NewOfflineGatewayAPIController( + ctx context.Context, cfg *config.Server, su Updater, resources *message.ProviderResources, +) (*OfflineGatewayAPIReconciler, error) { + if cfg == nil || resources == nil { + return nil, fmt.Errorf("missing config or resources that offline controller requires") + } + + // Check provider type. + if cfg.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { + return nil, fmt.Errorf("offline controller cannot work with kubernetes provider") + } + + // Gather additional resources to watch from registered extensions. + var ( + extGVKs []schema.GroupVersionKind + extServerPoliciesGVKs []schema.GroupVersionKind + ) + + if cfg.EnvoyGateway.ExtensionManager != nil { + for _, rsrc := range cfg.EnvoyGateway.ExtensionManager.Resources { + gvk := schema.GroupVersionKind(rsrc) + extGVKs = append(extGVKs, gvk) + } + for _, rsrc := range cfg.EnvoyGateway.ExtensionManager.PolicyResources { + gvk := schema.GroupVersionKind(rsrc) + extServerPoliciesGVKs = append(extServerPoliciesGVKs, gvk) + } + } + + cli := newOfflineGatewayAPIClient() + r := gatewayAPIReconciler{ + client: cli, + log: cfg.Logger, + classController: gwapiv1.GatewayController(cfg.EnvoyGateway.Gateway.ControllerName), + namespace: cfg.ControllerNamespace, + statusUpdater: su, + resources: resources, + extGVKs: extGVKs, + store: newProviderStore(), + envoyGateway: cfg.EnvoyGateway, + mergeGateways: sets.New[string](), + extServerPolicies: extServerPoliciesGVKs, + } + + r.log.Info("created offline gatewayapi controller") + if su != nil { + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) + } + + return &OfflineGatewayAPIReconciler{ + gatewayAPIReconciler: r, + Client: cli, + }, nil +} + +// Reconcile calls reconcile method in gateway-api controller, this method +// should be called manually. +func (r *OfflineGatewayAPIReconciler) Reconcile(ctx context.Context) error { + _, err := r.gatewayAPIReconciler.Reconcile(ctx, reconcile.Request{}) + return err +} + +// newOfflineGatewayAPIClient returns a offline client with gateway-api schemas and indexes. +func newOfflineGatewayAPIClient() client.Client { + return fake.NewClientBuilder(). + WithScheme(envoygateway.GetScheme()). + WithIndex(&gwapiv1.Gateway{}, classGatewayIndex, gatewayIndexFunc). + WithIndex(&gwapiv1.Gateway{}, secretGatewayIndex, secretGatewayIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, gatewayHTTPRouteIndex, gatewayHTTPRouteIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, backendHTTPRouteIndex, backendHTTPRouteIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, httpRouteFilterHTTPRouteIndex, httpRouteFilterHTTPRouteIndexFunc). + WithIndex(&gwapiv1.GRPCRoute{}, gatewayGRPCRouteIndex, gatewayGRPCRouteIndexFunc). + WithIndex(&gwapiv1.GRPCRoute{}, backendGRPCRouteIndex, backendGRPCRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, gatewayTCPRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, backendTCPRouteIndex, backendTCPRouteIndexFunc). + WithIndex(&gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, gatewayUDPRouteIndexFunc). + WithIndex(&gwapiv1a2.UDPRoute{}, backendUDPRouteIndex, backendUDPRouteIndexFunc). + WithIndex(&gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, gatewayTLSRouteIndexFunc). + WithIndex(&gwapiv1a2.TLSRoute{}, backendTLSRouteIndex, backendTLSRouteIndexFunc). + WithIndex(&egv1a1.EnvoyProxy{}, backendEnvoyProxyTelemetryIndex, backendEnvoyProxyTelemetryIndexFunc). + WithIndex(&egv1a1.EnvoyProxy{}, secretEnvoyProxyIndex, secretEnvoyProxyIndexFunc). + WithIndex(&egv1a1.BackendTrafficPolicy{}, configMapBtpIndex, configMapBtpIndexFunc). + WithIndex(&egv1a1.ClientTrafficPolicy{}, configMapCtpIndex, configMapCtpIndexFunc). + WithIndex(&egv1a1.ClientTrafficPolicy{}, secretCtpIndex, secretCtpIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, secretSecurityPolicyIndex, secretSecurityPolicyIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, backendSecurityPolicyIndex, backendSecurityPolicyIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, configMapSecurityPolicyIndex, configMapSecurityPolicyIndexFunc). + WithIndex(&egv1a1.EnvoyExtensionPolicy{}, backendEnvoyExtensionPolicyIndex, backendEnvoyExtensionPolicyIndexFunc). + WithIndex(&egv1a1.EnvoyExtensionPolicy{}, secretEnvoyExtensionPolicyIndex, secretEnvoyExtensionPolicyIndexFunc). + WithIndex(&gwapiv1a3.BackendTLSPolicy{}, configMapBtlsIndex, configMapBtlsIndexFunc). + WithIndex(&gwapiv1a3.BackendTLSPolicy{}, secretBtlsIndex, secretBtlsIndexFunc). + WithIndex(&egv1a1.HTTPRouteFilter{}, configMapHTTPRouteFilterIndex, configMapRouteFilterIndexFunc). + WithIndex(&egv1a1.HTTPRouteFilter{}, secretHTTPRouteFilterIndex, secretRouteFilterIndexFunc). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). + Build() +} diff --git a/internal/provider/kubernetes/controller_offline_test.go b/internal/provider/kubernetes/controller_offline_test.go new file mode 100644 index 0000000000..a8b3296a26 --- /dev/null +++ b/internal/provider/kubernetes/controller_offline_test.go @@ -0,0 +1,49 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package kubernetes + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/message" +) + +func TestNewOfflineGatewayAPIController(t *testing.T) { + t.Run("offline controller requires config and resources", func(t *testing.T) { + _, err := NewOfflineGatewayAPIController(context.Background(), nil, nil, nil) + require.Error(t, err) + }) + + t.Run("offline controller does not support k8s provider type", func(t *testing.T) { + cfg, err := config.New(os.Stdout) + require.NoError(t, err) + + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeKubernetes, + } + pResources := new(message.ProviderResources) + _, err = NewOfflineGatewayAPIController(context.Background(), cfg, nil, pResources) + require.Error(t, err) + }) + + t.Run("offline controller creation success", func(t *testing.T) { + cfg, err := config.New(os.Stdout) + require.NoError(t, err) + + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeCustom, + } + pResources := new(message.ProviderResources) + _, err = NewOfflineGatewayAPIController(context.Background(), cfg, nil, pResources) + require.NoError(t, err) + }) +} diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index b2d6cdaf79..3914071992 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -420,27 +420,494 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Add objects referenced by test cases. objs := []client.Object{tc.envoyExtensionPolicy, tc.backend, tc.referenceGrant} - - // Create the reconciler. - logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo) + r := setupReferenceGrantReconciler(objs) ctx := context.Background() + resourceTree := resource.NewResources() + resourceMap := newResourceMapping() - r := &gatewayAPIReconciler{ - log: logger, - classController: "some-gateway-class", + err := r.processEnvoyExtensionPolicies(ctx, resourceTree, resourceMap) + require.NoError(t, err) + if tc.shouldBeAdded { + require.Contains(t, resourceTree.ReferenceGrants, tc.referenceGrant) + } else { + require.NotContains(t, resourceTree.ReferenceGrants, tc.referenceGrant) } + }) + } +} - r.client = fakeclient.NewClientBuilder(). - WithScheme(envoygateway.GetScheme()). - WithObjects(objs...). - WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc()). - Build() +func TestProcessSecurityPolicyObjectRefs(t *testing.T) { + testCases := []struct { + name string + securityPolicy *egv1a1.SecurityPolicy + backend *egv1a1.Backend + referenceGrant *gwapiv1b1.ReferenceGrant + shouldBeAdded bool + }{ + { + name: "valid security policy with remote jwks proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + RemoteJWKS: &egv1a1.RemoteJWKS{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with remote jwks wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + RemoteJWKS: &egv1a1.RemoteJWKS{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + { + name: "valid security policy with extAuth grpc proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + } + + for i := range testCases { + tc := testCases[i] + // Run the test cases. + t.Run(tc.name, func(t *testing.T) { + // Add objects referenced by test cases. + objs := []client.Object{tc.securityPolicy, tc.backend, tc.referenceGrant} + r := setupReferenceGrantReconciler(objs) + ctx := context.Background() resourceTree := resource.NewResources() resourceMap := newResourceMapping() - err := r.processEnvoyExtensionPolicies(ctx, resourceTree, resourceMap) + err := r.processSecurityPolicies(ctx, resourceTree, resourceMap) require.NoError(t, err) if tc.shouldBeAdded { require.Contains(t, resourceTree.ReferenceGrants, tc.referenceGrant) @@ -450,3 +917,19 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { }) } } + +func setupReferenceGrantReconciler(objs []client.Object) *gatewayAPIReconciler { + logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo) + + r := &gatewayAPIReconciler{ + log: logger, + classController: "some-gateway-class", + } + + r.client = fakeclient.NewClientBuilder(). + WithScheme(envoygateway.GetScheme()). + WithObjects(objs...). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). + Build() + return r +} diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index 0069aba5c3..634f64aef9 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -158,7 +158,7 @@ func validateBackendRef(ref *gwapiv1.BackendRef) error { return fmt.Errorf("invalid group; must be nil, empty string %q or %q", mcsapiv1a1.GroupName, egv1a1.GroupName) case gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != resource.KindService && gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != resource.KindServiceImport && gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != egv1a1.KindBackend: return fmt.Errorf("invalid kind %q; must be %q, %q or %q", - *ref.BackendObjectReference.Kind, resource.KindService, resource.KindServiceImport, egv1a1.KindBackend) + *ref.Kind, resource.KindService, resource.KindServiceImport, egv1a1.KindBackend) } return nil diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 7f90d87628..17fcb85968 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -42,6 +42,8 @@ const ( configMapSecurityPolicyIndex = "configMapSecurityPolicyIndex" configMapCtpIndex = "configMapCtpIndex" secretCtpIndex = "secretCtpIndex" + configMapBackendIndex = "configMapBackendIndex" + secretBackendIndex = "secretBackendIndex" secretBtlsIndex = "secretBtlsIndex" configMapBtlsIndex = "configMapBtlsIndex" backendEnvoyExtensionPolicyIndex = "backendEnvoyExtensionPolicyIndex" @@ -55,21 +57,19 @@ const ( ) func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc()); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc); err != nil { return err } return nil } -func getReferenceGrantIndexerFunc() func(rawObj client.Object) []string { - return func(rawObj client.Object) []string { - refGrant := rawObj.(*gwapiv1b1.ReferenceGrant) - var referredServices []string - for _, target := range refGrant.Spec.To { - referredServices = append(referredServices, string(target.Kind)) - } - return referredServices +func getReferenceGrantIndexerFunc(rawObj client.Object) []string { + refGrant := rawObj.(*gwapiv1b1.ReferenceGrant) + var referredServices []string + for _, target := range refGrant.Spec.To { + referredServices = append(referredServices, string(target.Kind)) } + return referredServices } // addHTTPRouteIndexers adds indexing on HTTPRoute. @@ -174,23 +174,6 @@ func httpRouteFilterHTTPRouteIndexFunc(rawObj client.Object) []string { return refs } -func secretEnvoyProxyIndexFunc(rawObj client.Object) []string { - ep := rawObj.(*egv1a1.EnvoyProxy) - var secretReferences []string - if ep.Spec.BackendTLS != nil { - if ep.Spec.BackendTLS.ClientCertificateRef != nil { - if *ep.Spec.BackendTLS.ClientCertificateRef.Kind == resource.KindSecret { - secretReferences = append(secretReferences, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, ep.Namespace), - Name: string(ep.Spec.BackendTLS.ClientCertificateRef.Name), - }.String()) - } - } - } - return secretReferences -} - func addEnvoyProxyIndexers(ctx context.Context, mgr manager.Manager) error { if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.EnvoyProxy{}, backendEnvoyProxyTelemetryIndex, backendEnvoyProxyTelemetryIndexFunc); err != nil { return err @@ -214,6 +197,23 @@ func backendEnvoyProxyTelemetryIndexFunc(rawObj client.Object) []string { return refs.UnsortedList() } +func secretEnvoyProxyIndexFunc(rawObj client.Object) []string { + ep := rawObj.(*egv1a1.EnvoyProxy) + var secretReferences []string + if ep.Spec.BackendTLS != nil { + if ep.Spec.BackendTLS.ClientCertificateRef != nil { + if *ep.Spec.BackendTLS.ClientCertificateRef.Kind == resource.KindSecret { + secretReferences = append(secretReferences, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, ep.Namespace), + Name: string(ep.Spec.BackendTLS.ClientCertificateRef.Name), + }.String()) + } + } + } + return secretReferences +} + func accessLogRefs(ep *egv1a1.EnvoyProxy) []string { var refs []string @@ -351,23 +351,7 @@ func backendGRPCRouteIndexFunc(rawObj client.Object) []string { // referenced in TLSRoute objects via `.spec.rules.backendRefs`. This helps in // querying for TLSRoutes that are affected by a particular Service CRUD. func addTLSRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, func(rawObj client.Object) []string { - tlsRoute := rawObj.(*gwapiv1a2.TLSRoute) - var gateways []string - for _, parent := range tlsRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the TLSRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tlsRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, gatewayTLSRouteIndexFunc); err != nil { return err } @@ -377,6 +361,24 @@ func addTLSRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayTLSRouteIndexFunc(rawObj client.Object) []string { + tlsRoute := rawObj.(*gwapiv1a2.TLSRoute) + var gateways []string + for _, parent := range tlsRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the TLSRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tlsRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendTLSRouteIndexFunc(rawObj client.Object) []string { tlsroute := rawObj.(*gwapiv1a2.TLSRoute) var backendRefs []string @@ -401,23 +403,7 @@ func backendTLSRouteIndexFunc(rawObj client.Object) []string { // referenced in TCPRoute objects via `.spec.rules.backendRefs`. This helps in // querying for TCPRoutes that are affected by a particular Service CRUD. func addTCPRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, func(rawObj client.Object) []string { - tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) - var gateways []string - for _, parent := range tcpRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the TCPRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tcpRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, gatewayTCPRouteIndexFunc); err != nil { return err } @@ -427,6 +413,24 @@ func addTCPRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayTCPRouteIndexFunc(rawObj client.Object) []string { + tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) + var gateways []string + for _, parent := range tcpRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the TCPRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tcpRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendTCPRouteIndexFunc(rawObj client.Object) []string { tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) var backendRefs []string @@ -453,23 +457,7 @@ func backendTCPRouteIndexFunc(rawObj client.Object) []string { // - For Service objects that are referenced in UDPRoute objects via `.spec.rules.backendRefs`. This helps in // querying for UDPRoutes that are affected by a particular Service CRUD. func addUDPRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, func(rawObj client.Object) []string { - udpRoute := rawObj.(*gwapiv1a2.UDPRoute) - var gateways []string - for _, parent := range udpRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the UDPRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, udpRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, gatewayUDPRouteIndexFunc); err != nil { return err } @@ -479,6 +467,24 @@ func addUDPRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayUDPRouteIndexFunc(rawObj client.Object) []string { + udpRoute := rawObj.(*gwapiv1a2.UDPRoute) + var gateways []string + for _, parent := range udpRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the UDPRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, udpRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendUDPRouteIndexFunc(rawObj client.Object) []string { udproute := rawObj.(*gwapiv1a2.UDPRoute) var backendRefs []string @@ -507,10 +513,7 @@ func addGatewayIndexers(ctx context.Context, mgr manager.Manager) error { return err } - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1.Gateway{}, classGatewayIndex, func(rawObj client.Object) []string { - gateway := rawObj.(*gwapiv1.Gateway) - return []string{string(gateway.Spec.GatewayClassName)} - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1.Gateway{}, classGatewayIndex, gatewayIndexFunc); err != nil { return err } return nil @@ -539,6 +542,11 @@ func secretGatewayIndexFunc(rawObj client.Object) []string { return secretReferences } +func gatewayIndexFunc(rawObj client.Object) []string { + gateway := rawObj.(*gwapiv1.Gateway) + return []string{string(gateway.Spec.GatewayClassName)} +} + // addSecurityPolicyIndexers adds indexing on SecurityPolicy. // - For Secret objects that are referenced in SecurityPolicy objects via // `.spec.OIDC.clientSecret` and `.spec.basicAuth.users`. This helps in @@ -602,35 +610,49 @@ func secretSecurityPolicyIndexFunc(rawObj client.Object) []string { func backendSecurityPolicyIndexFunc(rawObj client.Object) []string { securityPolicy := rawObj.(*egv1a1.SecurityPolicy) - var backendRef *gwapiv1.BackendObjectReference + var ( + backendRefs []gwapiv1.BackendObjectReference + values []string + ) if securityPolicy.Spec.ExtAuth != nil { if securityPolicy.Spec.ExtAuth.HTTP != nil { http := securityPolicy.Spec.ExtAuth.HTTP - backendRef = http.BackendRef + if http.BackendRef != nil { + backendRefs = append(backendRefs, *http.BackendRef) + } if len(http.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(http.BackendRefs[0]) + backendRefs = append(backendRefs, http.BackendRefs[0].BackendObjectReference) } } else if securityPolicy.Spec.ExtAuth.GRPC != nil { grpc := securityPolicy.Spec.ExtAuth.GRPC - backendRef = grpc.BackendRef + if grpc.BackendRef != nil { + backendRefs = append(backendRefs, *grpc.BackendRef) + } if len(grpc.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(grpc.BackendRefs[0]) + backendRefs = append(backendRefs, grpc.BackendRefs[0].BackendObjectReference) + } + } + } + if securityPolicy.Spec.JWT != nil { + for _, provider := range securityPolicy.Spec.JWT.Providers { + if provider.RemoteJWKS != nil { + for _, backendRef := range provider.RemoteJWKS.BackendRefs { + backendRefs = append(backendRefs, backendRef.BackendObjectReference) + } } } } - if backendRef != nil { - return []string{ + for _, reference := range backendRefs { + values = append(values, types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(backendRef.Namespace, securityPolicy.Namespace), - Name: string(backendRef.Name), + Namespace: gatewayapi.NamespaceDerefOr(reference.Namespace, securityPolicy.Namespace), + Name: string(reference.Name), }.String(), - } + ) } - - // This should not happen because the CEL validation should catch it. - return []string{} + return values } func configMapSecurityPolicyIndexFunc(rawObj client.Object) []string { @@ -708,6 +730,55 @@ func secretCtpIndexFunc(rawObj client.Object) []string { return secretReferences } +// addBackendIndexers adds indexing on Backend, for ConfigMap or Secret objects that are +// referenced in Backend objects. +func addBackendIndexers(ctx context.Context, mgr manager.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.Backend{}, configMapBackendIndex, configMapBackendIndexFunc); err != nil { + return err + } + if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.Backend{}, secretBackendIndex, secretBackendIndexFunc); err != nil { + return err + } + + return nil +} + +func configMapBackendIndexFunc(rawObj client.Object) []string { + backend := rawObj.(*egv1a1.Backend) + var configMapReferences []string + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + if caCertRef.Kind == resource.KindConfigMap { + configMapReferences = append(configMapReferences, + types.NamespacedName{ + Namespace: backend.Namespace, + Name: string(caCertRef.Name), + }.String(), + ) + } + } + } + return configMapReferences +} + +func secretBackendIndexFunc(rawObj client.Object) []string { + backend := rawObj.(*egv1a1.Backend) + var secretReferences []string + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + if caCertRef.Kind == resource.KindSecret { + secretReferences = append(secretReferences, + types.NamespacedName{ + Namespace: backend.Namespace, + Name: string(caCertRef.Name), + }.String(), + ) + } + } + } + return secretReferences +} + // addBtpIndexers adds indexing on BackendTrafficPolicy, for ConfigMap objects that are // referenced in BackendTrafficPolicy objects. This helps in querying for BackendTrafficPolies that are // affected by a particular ConfigMap CRUD. diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index ddb0339120..20f8ad292d 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -61,34 +61,17 @@ func (r *gatewayAPIReconciler) processTLSRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tlsRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tlsRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTLSRoute, namespace: tlsRoute.Namespace, name: tlsRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTLSRoute, + tlsRoute.Namespace, + tlsRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TLSRoute", + "tlsRoute", tlsRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -143,42 +126,17 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, grpcRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != grpcRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindGRPCRoute, - namespace: grpcRoute.Namespace, - name: grpcRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindGRPCRoute, + grpcRoute.Namespace, + grpcRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for GRPCRoute", + "grpcRoute", grpcRoute, "backendRef", backendRef.BackendObjectReference) } } @@ -281,43 +239,19 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRoute", + "httpRoute", httpRoute, "backendRef", backendRef.BackendObjectReference) } + for i := range backendRef.Filters { // Some of the validation logic in processHTTPRouteFilter is not needed for backendRef filters. // However, we reuse the same function to avoid code duplication. @@ -363,7 +297,8 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( } // Load in the backendRefs from any requestMirrorFilters on the HTTPRoute - if filter.Type == gwapiv1.HTTPRouteFilterRequestMirror { + switch filter.Type { + case gwapiv1.HTTPRouteFilterRequestMirror: // Make sure the config actually exists mirrorFilter := filter.RequestMirror if mirrorFilter == nil { @@ -381,44 +316,19 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( if err := validateBackendRef(&mirrorBackendRef); err != nil { return fmt.Errorf("invalid backendRef for requestMirror filter: %w", err) } - - backendNamespace := gatewayapi.NamespaceDerefOr(mirrorBackendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: mirrorBackendRef.BackendObjectReference.Group, - Kind: mirrorBackendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: mirrorBackendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(mirrorBackendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(mirrorBackendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + mirrorBackendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRouteFilter", + "httpRoute", httpRoute, "backendRef", mirrorBackendRef.BackendObjectReference) } - } else if filter.Type == gwapiv1.HTTPRouteFilterExtensionRef { + case gwapiv1.HTTPRouteFilterExtensionRef: // NOTE: filters must be in the same namespace as the HTTPRoute // Check if it's a Kind managed by an extension and add to resourceTree key := utils.NamespacedNameWithGroupKind{ @@ -499,34 +409,17 @@ func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tcpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tcpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTCPRoute, namespace: tcpRoute.Namespace, name: tcpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTCPRoute, + tcpRoute.Namespace, + tcpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TCPRoute", + "tcpRoute", tcpRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -580,33 +473,17 @@ func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, udpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != udpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindUDPRoute, namespace: udpRoute.Namespace, name: udpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindUDPRoute, + udpRoute.Namespace, + udpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for UDPRoute", + "udpRoute", udpRoute, "backendRef", backendRef.BackendObjectReference) } } } diff --git a/internal/provider/kubernetes/status_updater.go b/internal/provider/kubernetes/status_updater.go index 1bafe23668..c8b0472953 100644 --- a/internal/provider/kubernetes/status_updater.go +++ b/internal/provider/kubernetes/status_updater.go @@ -78,7 +78,7 @@ func (u *UpdateHandler) apply(update Update) { var ( startTime = time.Now() obj = update.Resource - objKind = kindOf(obj) + objKind = KindOf(obj) ) defer func() { @@ -297,7 +297,7 @@ func isStatusEqual(objA, objB interface{}) bool { return false } -// kindOf returns the known kind string for the given Kubernetes object, +// KindOf returns the known kind string for the given Kubernetes object, // returns Unknown for the unsupported object. // // Supported objects: @@ -316,7 +316,7 @@ func isStatusEqual(objA, objB interface{}) bool { // BackendTLSPolicy // EnvoyExtensionPolicy // Unstructured (for Extension Policies) -func kindOf(obj interface{}) string { +func KindOf(obj interface{}) string { var kind string switch o := obj.(type) { case *gwapiv1.GatewayClass: diff --git a/internal/provider/kubernetes/topology_injector.go b/internal/provider/kubernetes/topology_injector.go index 1d5215909a..f507c72989 100644 --- a/internal/provider/kubernetes/topology_injector.go +++ b/internal/provider/kubernetes/topology_injector.go @@ -7,10 +7,9 @@ package kubernetes import ( "context" + "encoding/json" "fmt" - "net/http" - "github.com/go-openapi/jsonpointer" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" @@ -31,7 +30,7 @@ func (m *ProxyTopologyInjector) Handle(ctx context.Context, req admission.Reques if err := m.Decoder.Decode(req, binding); err != nil { klog.Error(err, "decoding binding failed", "request.ObjectKind", req.Object.Object.GetObjectKind()) topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() - return admission.Errored(http.StatusInternalServerError, err) + return admission.Allowed("internal error, skipped") } if binding.Target.Name == "" { @@ -48,7 +47,7 @@ func (m *ProxyTopologyInjector) Handle(ctx context.Context, req admission.Reques if err := m.Get(ctx, podName, pod); err != nil { klog.Error(err, "get pod failed", "pod", podName.String()) topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() - return admission.Errored(http.StatusInternalServerError, err) + return admission.Allowed("internal error, skipped") } // Skip non-proxy pods @@ -64,24 +63,26 @@ func (m *ProxyTopologyInjector) Handle(ctx context.Context, req admission.Reques node := &corev1.Node{} if err := m.Get(ctx, nodeName, node); err != nil { klog.Error(err, "get node failed", "node", node.Name) + topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() - return admission.Errored(http.StatusInternalServerError, err) + return admission.Allowed("internal error, skipped") } - var patch string if zone, ok := node.Labels[corev1.LabelTopologyZone]; ok { - patch = fmt.Sprintf(`[{"op":"replace", "path":"/metadata/labels/%s", "value":"%s"}]`, jsonpointer.Escape(corev1.LabelTopologyZone), zone) + if binding.Annotations == nil { + binding.Annotations = map[string]string{} + } + binding.Annotations[corev1.LabelTopologyZone] = zone + } else { + return admission.Allowed("Skipping injection due to missing topology label on node") } - rawPatch := client.RawPatch(types.JSONPatchType, []byte(patch)) - if err := m.Patch(ctx, pod, rawPatch); err != nil { - klog.Error(err, "patch pod failed", "pod", podName.String()) - topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() - return admission.Errored(http.StatusInternalServerError, err) + marshaledBinding, err := json.Marshal(binding) + if err != nil { + klog.Errorf("failed to marshal Pod Binding: %v", err) + return admission.Allowed(fmt.Sprintf("failed to marshal binding, skipped: %v", err)) } - klog.V(1).Info("patch pod succeeded", "pod", podName.String()) - topologyInjectorEventsTotal.WithSuccess().Increment() - return admission.Allowed("pod patched") + return admission.PatchResponseFromRaw(req.Object.Raw, marshaledBinding) } func hasEnvoyProxyLabels(labels map[string]string) bool { diff --git a/internal/provider/kubernetes/topology_injector_test.go b/internal/provider/kubernetes/topology_injector_test.go index 726680854c..6128214a80 100644 --- a/internal/provider/kubernetes/topology_injector_test.go +++ b/internal/provider/kubernetes/topology_injector_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -45,11 +46,11 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } cases := []struct { - caseName string - obj client.Object - node *corev1.Node - pod *corev1.Pod - wantErr bool + caseName string + obj client.Object + node *corev1.Node + pod *corev1.Pod + expectedPatchResp []jsonpatch.JsonPatchOperation }{ { caseName: "valid binding", @@ -60,9 +61,15 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { }, Target: corev1.ObjectReference{Name: defaultNode.Name}, }, - node: defaultNode, - pod: defaultPod, - wantErr: false, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: []jsonpatch.JsonPatchOperation{{ + Operation: "add", + Path: "/metadata/annotations", + Value: map[string]interface{}{ + "topology.kubernetes.io/zone": "zone1", + }, + }}, }, { caseName: "empty target", @@ -72,9 +79,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: defaultPod.Namespace, }, }, - node: defaultNode, - pod: defaultPod, - wantErr: true, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: nil, }, { caseName: "skip binding - no label", @@ -84,9 +91,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: "bar", }, }, - node: defaultNode, - pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: "baz"}}, - wantErr: true, + node: defaultNode, + pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: "baz"}}, + expectedPatchResp: nil, }, { caseName: "no matching pod", @@ -96,9 +103,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: "bar", }, }, - node: defaultNode, - pod: defaultPod, - wantErr: true, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: nil, }, } for _, tc := range cases { @@ -120,9 +127,7 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } objBytes, err := json.Marshal(tc.obj) - if err != nil { - t.Fatalf("failed to marshal object: %v", err) - } + require.NoError(t, err) req := admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -135,23 +140,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } resp := mutator.Handle(context.Background(), req) + require.True(t, resp.Allowed) - if !resp.Allowed && tc.wantErr { - t.Fatalf("expected Allowed response, got: %v", resp.Result) - } - - updatedPod := &corev1.Pod{} - if err = fakeClient.Get(context.Background(), types.NamespacedName{Name: tc.pod.Name, Namespace: tc.pod.Namespace}, updatedPod); err != nil { - t.Fatalf("get pod: %v", err) - } - - zone, ok := updatedPod.Labels[corev1.LabelTopologyZone] - if tc.wantErr { - require.False(t, ok, "pod has unexpected topology label: %v", updatedPod) - } else { - require.True(t, ok, "pod does not have expected topology label: %v", updatedPod) - require.Equal(t, zone, tc.node.Labels[corev1.LabelTopologyZone]) - } + require.Equal(t, tc.expectedPatchResp, resp.Patches) }) } } diff --git a/internal/provider/runner/runner.go b/internal/provider/runner/runner.go index 673ea867d4..128184197a 100644 --- a/internal/provider/runner/runner.go +++ b/internal/provider/runner/runner.go @@ -53,7 +53,7 @@ func (r *Runner) Start(ctx context.Context) (err error) { } case egv1a1.ProviderTypeCustom: - p, err = r.createCustomResourceProvider() + p, err = r.createCustomResourceProvider(ctx) if err != nil { return fmt.Errorf("failed to create custom provider: %w", err) } @@ -79,7 +79,7 @@ func (r *Runner) createKubernetesProvider(ctx context.Context) (*kubernetes.Prov return nil, fmt.Errorf("failed to get kubeconfig: %w", err) } - p, err := kubernetes.New(ctx, cfg, &r.Config.Server, r.ProviderResources) + p, err := kubernetes.New(ctx, cfg, &r.Server, r.ProviderResources) if err != nil { return nil, fmt.Errorf("failed to create provider %s: %w", egv1a1.ProviderTypeKubernetes, err) } @@ -87,10 +87,10 @@ func (r *Runner) createKubernetesProvider(ctx context.Context) (*kubernetes.Prov return p, err } -func (r *Runner) createCustomResourceProvider() (p provider.Provider, err error) { +func (r *Runner) createCustomResourceProvider(ctx context.Context) (p provider.Provider, err error) { switch r.EnvoyGateway.Provider.Custom.Resource.Type { case egv1a1.ResourceProviderTypeFile: - p, err = file.New(&r.Config.Server, r.ProviderResources) + p, err = file.New(ctx, &r.Server, r.ProviderResources) if err != nil { return nil, fmt.Errorf("failed to create provider %s: %w", egv1a1.ProviderTypeCustom, err) } diff --git a/internal/utils/merge.go b/internal/utils/merge.go index e3a4e1347a..337147ed01 100644 --- a/internal/utils/merge.go +++ b/internal/utils/merge.go @@ -16,11 +16,23 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) -func Merge[T client.Object](original T, patch T, mergeType egv1a1.MergeType) (T, error) { +func MergeWithPatch[T client.Object](original T, patch *egv1a1.KubernetesPatchSpec) (T, error) { + if patch == nil { + return original, nil + } + + mergeType := egv1a1.StrategicMerge + if patch.Type != nil { + mergeType = *patch.Type + } + + return mergeInternal(original, patch.Value.Raw, mergeType) +} + +func mergeInternal[T client.Object](original T, patchJSON []byte, mergeType egv1a1.MergeType) (T, error) { var ( patchedJSON []byte originalJSON []byte - patchJSON []byte err error empty T ) @@ -29,13 +41,9 @@ func Merge[T client.Object](original T, patch T, mergeType egv1a1.MergeType) (T, if err != nil { return empty, fmt.Errorf("error marshaling original service: %w", err) } - patchJSON, err = json.Marshal(patch) - if err != nil { - return empty, fmt.Errorf("error marshaling original service: %w", err) - } switch mergeType { case egv1a1.StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patchJSON, egv1a1.BackendTrafficPolicy{}) + patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patchJSON, empty) if err != nil { return empty, fmt.Errorf("error during strategic merge: %w", err) } @@ -45,7 +53,7 @@ func Merge[T client.Object](original T, patch T, mergeType egv1a1.MergeType) (T, return empty, fmt.Errorf("error during JSON merge: %w", err) } default: - return empty, fmt.Errorf("unsupported merge type: %s", mergeType) + return empty, fmt.Errorf("unsupported merge type: %v", mergeType) } res := new(T) @@ -55,3 +63,17 @@ func Merge[T client.Object](original T, patch T, mergeType egv1a1.MergeType) (T, return *res, nil } + +func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, error) { + var ( + patchJSON []byte + err error + empty T + ) + + patchJSON, err = json.Marshal(patch) + if err != nil { + return empty, fmt.Errorf("error marshaling original service: %w", err) + } + return mergeInternal(original, patchJSON, mergeType) +} diff --git a/internal/utils/merge_test.go b/internal/utils/merge_test.go index 690a331f9a..73f6a584c9 100644 --- a/internal/utils/merge_test.go +++ b/internal/utils/merge_test.go @@ -6,146 +6,63 @@ package utils import ( - "fmt" + "os" + "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestMergeBackendTrafficPolicy(t *testing.T) { - r := resource.MustParse("100m") + baseDir := "testdata" + caseFiles, err := filepath.Glob(filepath.Join(baseDir, "backendtrafficpolicy_*.in.yaml")) + require.NoError(t, err) - cases := []struct { - name string - original *egv1a1.BackendTrafficPolicy - patch *egv1a1.BackendTrafficPolicy + for _, caseFile := range caseFiles { + // get case name from path + caseName := strings.TrimPrefix(strings.TrimSuffix(caseFile, ".in.yaml"), baseDir+"/backendtrafficpolicy_") + t.Run(caseName, func(t *testing.T) { + for _, mergeType := range []egv1a1.MergeType{egv1a1.StrategicMerge, egv1a1.JSONMerge} { + patchedInput := strings.Replace(caseFile, ".in.yaml", ".patch.yaml", 1) + var output string + if mergeType == egv1a1.StrategicMerge { + output = strings.Replace(caseFile, ".in.yaml", ".strategicmerge.out.yaml", 1) + } else { + output = strings.Replace(caseFile, ".in.yaml", ".jsonmerge.out.yaml", 1) + } - expected *egv1a1.BackendTrafficPolicy - jsonMergeExpected *egv1a1.BackendTrafficPolicy - }{ - { - name: "merge", - original: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](2), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "original", - }, - }, - }, - }, - patch: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - }, - }, - }, - expected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - { - Type: "original", - }, - }, - }, - }, - jsonMergeExpected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - }, - }, - }, - }, - { - name: "override", - original: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](13), - }, - }, - }, - }, - patch: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - }, - }, - expected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - }, - }, - }, - } - for _, tc := range cases { - for _, mergeType := range []egv1a1.MergeType{egv1a1.StrategicMerge, egv1a1.JSONMerge} { - t.Run(fmt.Sprintf("%s/%s", mergeType, tc.name), func(t *testing.T) { - got, err := Merge[*egv1a1.BackendTrafficPolicy](tc.original, tc.patch, mergeType) + original := readObject[*egv1a1.BackendTrafficPolicy](t, caseFile) + patch := readObject[*egv1a1.BackendTrafficPolicy](t, patchedInput) + + got, err := Merge(original, patch, mergeType) require.NoError(t, err) - switch mergeType { - case egv1a1.StrategicMerge: - require.Equal(t, tc.expected, got) - case egv1a1.JSONMerge: - if tc.jsonMergeExpected != nil { - require.Equal(t, tc.jsonMergeExpected, got) - } else { - require.Equal(t, tc.expected, got) - } + if test.OverrideTestData() { + b, err := yaml.Marshal(got) + require.NoError(t, err) + require.NoError(t, os.WriteFile(output, b, 0o600)) + continue } - }) - } + + expected := readObject[*egv1a1.BackendTrafficPolicy](t, output) + require.Equal(t, expected, got) + } + }) } } + +func readObject[T client.Object](t *testing.T, path string) T { + t.Helper() + b, err := os.ReadFile(path) + require.NoError(t, err) + btp := new(T) + err = yaml.Unmarshal(b, btp) + require.NoError(t, err) + return *btp +} diff --git a/internal/utils/test/flags.go b/internal/utils/test/flags.go new file mode 100644 index 0000000000..95e261b253 --- /dev/null +++ b/internal/utils/test/flags.go @@ -0,0 +1,14 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package test + +import "flag" + +var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") + +func OverrideTestData() bool { + return *overrideTestData +} diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml new file mode 100644 index 0000000000..20af7411b0 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: original +spec: + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml new file mode 100644 index 0000000000..b9f7db73e2 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml @@ -0,0 +1,18 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: patched +spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s +status: + ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml new file mode 100644 index 0000000000..364d73ce0f --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml @@ -0,0 +1,12 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: patched +spec: + timeout: + tcp: + connectTimeout: 15s + connection: + bufferLimit: 100M + httpUpgrade: + - type: "websocket" diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml new file mode 100644 index 0000000000..b9f7db73e2 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml @@ -0,0 +1,18 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: patched +spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s +status: + ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml new file mode 100644 index 0000000000..3dc8eee659 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml @@ -0,0 +1,27 @@ +kind: BackendTrafficPolicy +metadata: + name: original +spec: + rateLimit: + type: Global + global: + rules: + - name: one + clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + # 21 instead of 20 to test the zero request cost. + requests: 21 + unit: Hour + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + namespace: io.envoyproxy.gateway.e2e + key: request_cost_set_by_ext_proc diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml new file mode 100644 index 0000000000..a679b04d1b --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml @@ -0,0 +1,29 @@ +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: original +spec: + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: two + type: Global +status: + ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml new file mode 100644 index 0000000000..2d13bd03b0 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml @@ -0,0 +1,27 @@ +kind: BackendTrafficPolicy +metadata: + name: original +spec: + rateLimit: + type: Global + global: + rules: + - name: two + clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + # 21 instead of 20 to test the zero request cost. + requests: 21 + unit: Hour + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + namespace: io.envoyproxy.gateway.e2e + key: request_cost_set_by_ext_proc diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml new file mode 100644 index 0000000000..1833716e3e --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml @@ -0,0 +1,47 @@ +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: original +spec: + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: two + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: one + type: Global +status: + ancestors: null diff --git a/internal/wasm/cache_test.go b/internal/wasm/cache_test.go index c23b0adc65..55238df35c 100644 --- a/internal/wasm/cache_test.go +++ b/internal/wasm/cache_test.go @@ -763,7 +763,7 @@ func TestWasmCache(t *testing.T) { cache.mux.Lock() if cacheHitKey != nil { - if entry, ok := cache.modules[*cacheHitKey]; ok && entry.last == initTime { + if entry, ok := cache.modules[*cacheHitKey]; ok && initTime.Equal(entry.last) { t.Errorf("Wasm module cache entry's last access time not updated after get operation, key: %v", *cacheHitKey) } } diff --git a/internal/wasm/httpfetcher.go b/internal/wasm/httpfetcher.go index 6850ef9974..41325addfd 100644 --- a/internal/wasm/httpfetcher.go +++ b/internal/wasm/httpfetcher.go @@ -152,9 +152,9 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string, allowInsecure bool) func retryable(code int) bool { return code >= 500 && - !(code == http.StatusNotImplemented || - code == http.StatusHTTPVersionNotSupported || - code == http.StatusNetworkAuthenticationRequired) + (code != http.StatusNotImplemented && + code != http.StatusHTTPVersionNotSupported && + code != http.StatusNetworkAuthenticationRequired) } func isPosixTar(b []byte) bool { diff --git a/internal/xds/bootstrap/bootstrap.go b/internal/xds/bootstrap/bootstrap.go index 8b3644a9d5..04d847ee76 100644 --- a/internal/xds/bootstrap/bootstrap.go +++ b/internal/xds/bootstrap/bootstrap.go @@ -301,10 +301,11 @@ func GetRenderedBootstrapConfig(opts *RenderBootstrapConfigOptions) (string, err if opts.IPFamily != nil { cfg.parameters.IPFamily = string(*opts.IPFamily) - if *opts.IPFamily == egv1a1.IPv6 { + switch *opts.IPFamily { + case egv1a1.IPv6: cfg.parameters.AdminServer.Address = EnvoyAdminAddressV6 cfg.parameters.StatsServer.Address = netutils.IPv6ListenerAddress - } else if *opts.IPFamily == egv1a1.DualStack { + case egv1a1.DualStack: cfg.parameters.StatsServer.Address = netutils.IPv6ListenerAddress } } diff --git a/internal/xds/bootstrap/bootstrap_test.go b/internal/xds/bootstrap/bootstrap_test.go index 70216bb41f..4ef244fd55 100644 --- a/internal/xds/bootstrap/bootstrap_test.go +++ b/internal/xds/bootstrap/bootstrap_test.go @@ -17,6 +17,7 @@ import ( gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestGetRenderedBootstrapConfig(t *testing.T) { @@ -187,7 +188,7 @@ func TestGetRenderedBootstrapConfig(t *testing.T) { got, err := GetRenderedBootstrapConfig(tc.opts) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { // nolint:gosec err = os.WriteFile(path.Join("testdata", "render", fmt.Sprintf("%s.yaml", tc.name)), []byte(got), 0o644) require.NoError(t, err) diff --git a/internal/xds/bootstrap/util_test.go b/internal/xds/bootstrap/util_test.go index b5cacb7afd..d105456a56 100644 --- a/internal/xds/bootstrap/util_test.go +++ b/internal/xds/bootstrap/util_test.go @@ -6,7 +6,6 @@ package bootstrap import ( - "flag" "fmt" "os" "path" @@ -17,10 +16,9 @@ import ( "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestApplyBootstrapConfig(t *testing.T) { str, _ := readTestData("enable-prometheus") cases := []struct { @@ -74,7 +72,7 @@ func TestApplyBootstrapConfig(t *testing.T) { data, err := ApplyBootstrapConfig(tc.boostrapConfig, tc.defaultBootstrap) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { // nolint:gosec err = os.WriteFile(path.Join("testdata", "merge", fmt.Sprintf("%s.out.yaml", tc.name)), []byte(data), 0o644) require.NoError(t, err) diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go index 304f22da49..e707c24dac 100644 --- a/internal/xds/translator/accesslog.go +++ b/internal/xds/translator/accesslog.go @@ -129,7 +129,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle text file access logs for _, text := range al.Text { // Filter out logs that are not Global or match the desired access log type - if !(text.LogType == nil || *text.LogType == accessLogType) { + if text.LogType != nil && *text.LogType != accessLogType { continue } @@ -182,7 +182,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle json file access logs for _, json := range al.JSON { // Filter out logs that are not Global or match the desired access log type - if !(json.LogType == nil || *json.LogType == accessLogType) { + if json.LogType != nil && *json.LogType != accessLogType { continue } @@ -242,7 +242,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle ALS access logs for _, als := range al.ALS { // Filter out logs that are not Global or match the desired access log type - if !(als.LogType == nil || *als.LogType == accessLogType) { + if als.LogType != nil && *als.LogType != accessLogType { continue } @@ -313,7 +313,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle open telemetry access logs for _, otel := range al.OpenTelemetry { // Filter out logs that are not Global or match the desired access log type - if !(otel.LogType == nil || *otel.LogType == accessLogType) { + if otel.LogType != nil && *otel.LogType != accessLogType { continue } diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 92c726e1ef..4b9e01ea51 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -193,15 +193,21 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) { socket = buildProxyProtocolSocket(args.proxyProtocol, socket) } matchName := fmt.Sprintf("%s/tls/%d", args.name, i) - cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{ - Name: matchName, - Match: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "name": structpb.NewStringValue(matchName), + + // Dynamic resolver clusters have no endpoints, so we need to set the transport socket directly. + if args.endpointType == EndpointTypeDynamicResolver { + cluster.TransportSocket = socket + } else { + cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{ + Name: matchName, + Match: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "name": structpb.NewStringValue(matchName), + }, }, - }, - TransportSocket: socket, - }) + TransportSocket: socket, + }) + } } } @@ -752,7 +758,7 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs) (map[string]*anypb requiresHTTPFilters := len(args.settings) > 0 && args.settings[0].Filters != nil && args.settings[0].Filters.CredentialInjection != nil - if !(requiresCommonHTTPOptions || requiresHTTP1Options || requiresHTTP2Options || args.useClientProtocol || requiresHTTPFilters) { + if !requiresCommonHTTPOptions && !requiresHTTP1Options && !requiresHTTP2Options && !args.useClientProtocol && !requiresHTTPFilters { return nil, nil, nil } diff --git a/internal/xds/translator/extensionserver_test.go b/internal/xds/translator/extensionserver_test.go index 89a009bfcb..0579b0ca33 100644 --- a/internal/xds/translator/extensionserver_test.go +++ b/internal/xds/translator/extensionserver_test.go @@ -95,11 +95,12 @@ func (t *testingExtensionServer) PostRouteModify(_ context.Context, req *pb.Post func (t *testingExtensionServer) PostVirtualHostModify(_ context.Context, req *pb.PostVirtualHostModifyRequest) (*pb.PostVirtualHostModifyResponse, error) { // Only make the change when the VirtualHost's name matches the expected testdata // This prevents us from having to update every single testfile.out - if req.VirtualHost.Name == "extension-post-xdsvirtualhost-hook-error/*" { + switch req.VirtualHost.Name { + case "extension-post-xdsvirtualhost-hook-error/*": return &pb.PostVirtualHostModifyResponse{ VirtualHost: req.VirtualHost, }, fmt.Errorf("extension post xds virtual host hook error") - } else if req.VirtualHost.Name == "extension-listener" { + case "extension-listener": // Setup a new VirtualHost to avoid operating directly on the passed in pointer for better test coverage that the // VirtualHost we are returning gets used properly modifiedVH := proto.Clone(req.VirtualHost).(*routeV3.VirtualHost) diff --git a/internal/xds/translator/listener_ready.go b/internal/xds/translator/listener_ready.go index 7a60b0f425..1b4ebda769 100644 --- a/internal/xds/translator/listener_ready.go +++ b/internal/xds/translator/listener_ready.go @@ -22,10 +22,8 @@ import ( ) func buildReadyListener(ready *ir.ReadyListener) (*listenerv3.Listener, error) { - ipv4Compact := false - if ready.IPFamily == egv1a1.IPv6 || ready.IPFamily == egv1a1.DualStack { - ipv4Compact = true - } + ipv4Compact := ready.IPFamily == egv1a1.IPv6 || ready.IPFamily == egv1a1.DualStack + hcmFilters := make([]*hcmv3.HttpFilter, 0, 3) healthcheckFilter, err := filters.GenerateHealthCheckFilter(bootstrap.EnvoyReadinessPath) if err != nil { diff --git a/internal/xds/translator/listener_ready_test.go b/internal/xds/translator/listener_ready_test.go index ec522ad7bb..70935476e9 100644 --- a/internal/xds/translator/listener_ready_test.go +++ b/internal/xds/translator/listener_ready_test.go @@ -18,6 +18,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/proto" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestBuildReadyListener(t *testing.T) { @@ -61,7 +62,7 @@ func TestBuildReadyListener(t *testing.T) { t.Errorf("unexpected error: %v", err) } - if *overrideTestData { + if test.OverrideTestData() { data, err := proto.ToYAML(got) require.NoError(t, err) err = os.WriteFile(path.Join("testdata", "readylistener", tc.name+".yaml"), data, 0o600) diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go index fc1814f2da..60527be071 100644 --- a/internal/xds/translator/oidc.go +++ b/internal/xds/translator/oidc.go @@ -119,13 +119,10 @@ func oauth2Config(oidc *ir.OIDC) (*oauth2v3.OAuth2, error) { } // Envoy OAuth2 filter deletes the HTTP authorization header by default, which surprises users. - preserveAuthorizationHeader := true // If the user wants to forward the oauth2 access token to the upstream service, // we should not preserve the original authorization header. - if oidc.ForwardAccessToken { - preserveAuthorizationHeader = false - } + preserveAuthorizationHeader := !oidc.ForwardAccessToken oauth2 := &oauth2v3.OAuth2{ Config: &oauth2v3.OAuth2Config{ diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml index 5d7939120e..11c3aa8fb9 100644 --- a/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml @@ -19,6 +19,11 @@ http: settings: - isDynamicResolver: true name: httproute/default/httproute-1/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: backend-1/default-ca weight: 1 hostname: gateway.envoyproxy.io isHTTP2: false diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml index 29b8be2f44..818cf07425 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml @@ -16,3 +16,15 @@ lbPolicy: CLUSTER_PROVIDED name: httproute/default/httproute-1/rule/0 perConnectionBufferLimitBytes: 32768 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: {} + validationContextSdsSecretConfig: + name: backend-1/default-ca + sdsConfig: + ads: {} + resourceApiVersion: V3 diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml new file mode 100644 index 0000000000..3a9ed37085 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml @@ -0,0 +1,4 @@ +- name: backend-1/default-ca + validationContext: + trustedCa: + inlineBytes: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index aadde3f428..5118ffd329 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -1024,31 +1024,38 @@ func buildXdsUpstreamTLSCASecret(tlsConfig *ir.TLSUpstreamConfig) *tlsv3.Secret } func buildXdsUpstreamTLSSocketWthCert(tlsConfig *ir.TLSUpstreamConfig) (*corev3.TransportSocket, error) { + validationContext := &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ + ValidationContextSdsSecretConfig: &tlsv3.SdsSecretConfig{ + Name: tlsConfig.CACertificate.Name, + SdsConfig: makeConfigSource(), + }, + DefaultValidationContext: &tlsv3.CertificateValidationContext{}, + } + + if tlsConfig.SNI != nil { + validationContext.DefaultValidationContext.MatchTypedSubjectAltNames = []*tlsv3.SubjectAltNameMatcher{ + { + SanType: tlsv3.SubjectAltNameMatcher_DNS, + Matcher: &matcherv3.StringMatcher{ + MatchPattern: &matcherv3.StringMatcher_Exact{ + Exact: *tlsConfig.SNI, + }, + }, + }, + } + } + tlsCtx := &tlsv3.UpstreamTlsContext{ CommonTlsContext: &tlsv3.CommonTlsContext{ TlsCertificateSdsSecretConfigs: nil, ValidationContextType: &tlsv3.CommonTlsContext_CombinedValidationContext{ - CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ - ValidationContextSdsSecretConfig: &tlsv3.SdsSecretConfig{ - Name: tlsConfig.CACertificate.Name, - SdsConfig: makeConfigSource(), - }, - DefaultValidationContext: &tlsv3.CertificateValidationContext{ - MatchTypedSubjectAltNames: []*tlsv3.SubjectAltNameMatcher{ - { - SanType: tlsv3.SubjectAltNameMatcher_DNS, - Matcher: &matcherv3.StringMatcher{ - MatchPattern: &matcherv3.StringMatcher_Exact{ - Exact: tlsConfig.SNI, - }, - }, - }, - }, - }, - }, + CombinedValidationContext: validationContext, }, }, - Sni: tlsConfig.SNI, + } + + if tlsConfig.SNI != nil { + tlsCtx.Sni = *tlsConfig.SNI } tlsParams := buildTLSParams(&tlsConfig.TLSConfig) diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 3dd4edae9d..5aefb9b37f 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -7,7 +7,6 @@ package translator import ( "embed" - "flag" "path/filepath" "runtime" "sort" @@ -30,6 +29,7 @@ import ( "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" xtypes "github.com/envoyproxy/gateway/internal/xds/types" "github.com/envoyproxy/gateway/internal/xds/utils" ) @@ -39,8 +39,6 @@ var ( outFiles embed.FS //go:embed testdata/in/* inFiles embed.FS - - overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") ) type testFileConfig struct { @@ -178,7 +176,7 @@ func TestTranslateXds(t *testing.T) { routes := tCtx.XdsResources[resourcev3.RouteType] clusters := tCtx.XdsResources[resourcev3.ClusterType] endpoints := tCtx.XdsResources[resourcev3.EndpointType] - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, listeners), filepath.Join("testdata", "out", "xds-ir", inputFileName+".listeners.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, routes), filepath.Join("testdata", "out", "xds-ir", inputFileName+".routes.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, clusters), filepath.Join("testdata", "out", "xds-ir", inputFileName+".clusters.yaml"))) @@ -191,7 +189,7 @@ func TestTranslateXds(t *testing.T) { secrets, ok := tCtx.XdsResources[resourcev3.SecretType] if ok && len(secrets) > 0 { - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, secrets), filepath.Join("testdata", "out", "xds-ir", inputFileName+".secrets.yaml"))) } require.Equal(t, requireTestDataOutFile(t, "xds-ir", inputFileName+".secrets.yaml"), requireResourcesToYAMLString(t, secrets)) @@ -202,7 +200,7 @@ func TestTranslateXds(t *testing.T) { for _, e := range got { require.NoError(t, field.SetValue(e, "LastTransitionTime", metav1.NewTime(time.Time{}))) } - if *overrideTestData { + if test.OverrideTestData() { out, err := yaml.Marshal(got) require.NoError(t, err) require.NoError(t, file.Write(string(out), filepath.Join("testdata", "out", "xds-ir", inputFileName+".envoypatchpolicies.yaml"))) @@ -231,7 +229,7 @@ func TestTranslateRateLimitConfig(t *testing.T) { // Call BuildRateLimitServiceConfig with the list of listeners configs := BuildRateLimitServiceConfig(listeners) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireRateLimitConfigsToYAMLString(t, configs), filepath.Join("testdata", "out", "ratelimit-config", inputFileName+".yaml"))) } require.Equal(t, requireTestDataOutFile(t, "ratelimit-config", inputFileName+".yaml"), requireRateLimitConfigsToYAMLString(t, configs)) @@ -317,7 +315,7 @@ func TestTranslateXdsWithExtensionErrorsWhenFailOpen(t *testing.T) { routes := tCtx.XdsResources[resourcev3.RouteType] clusters := tCtx.XdsResources[resourcev3.ClusterType] endpoints := tCtx.XdsResources[resourcev3.EndpointType] - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, listeners), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".listeners.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, routes), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".routes.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, clusters), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".clusters.yaml"))) @@ -330,7 +328,7 @@ func TestTranslateXdsWithExtensionErrorsWhenFailOpen(t *testing.T) { secrets, ok := tCtx.XdsResources[resourcev3.SecretType] if ok { - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, secrets), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".secrets.yaml"))) } require.Equal(t, requireTestDataOutFile(t, "extension-xds-ir", inputFileName+".secrets.yaml"), requireResourcesToYAMLString(t, secrets)) diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 3271a4f907..ea823a6d3f 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,6 +10,8 @@ security updates: | new features: | bug fixes: | + Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. + Added validation for header values. # Enhancements that improve performance. performance improvements: | diff --git a/release-notes/v1.4.0-rc.2.yaml b/release-notes/v1.4.0-rc.2.yaml new file mode 100644 index 0000000000..c0642ec8e3 --- /dev/null +++ b/release-notes/v1.4.0-rc.2.yaml @@ -0,0 +1,71 @@ +date: May 1, 2025 + +# Changes that are expected to cause an incompatibility with previous versions, such as deletions or modifications to existing APIs. +breaking changes: | + Use a dedicated listener port(19003) for envoy proxy readiness + Uses the envoy JSON formatter for the default access log instead of text formatter. + Envoy Gateway would skip xDS snapshot updates in case of errors during xDS translation. + When Extension Manager is configured to Fail Open, translation errors are logged and suppressed. + When Extension Manager is configured to not Fail Open, EG will no longer replace affected resources. Instead, xDS snapshot update would be skipped. + +# Updates addressing vulnerabilities, security flaws, or compliance requirements. +security updates: | + Fixed CVE-2025-25294 + +# New features or capabilities added in this release. +new features: | + Added support for configuring maxUnavailable in KubernetesPodDisruptionBudgetSpec + Added support for percentage-based request mirroring + Allow matchExpressions in TargetSelector + Add defaulter for gateway-api resources loading from file to be able to set default values. + Added support for defining Lua EnvoyExtensionPolicies + Added RequestID field in ClientTrafficPolicy.HeaderSettings to configure Envoy X-Request-ID behavior. + Added support for HorizontalPodAutoscaler to helm chart + Added support for distinct header and distinct source CIDR based local rate limiting + Added support for forwarding the authenticated username to the backend via a configurable header in BasicAuth + Added support for HTTP Methods and Headers based authorization in SecurityPolicy + Added support for zone aware routing + Added support for BackendTLSPolicy to target ServiceImport + Added support for kubernetes.io/h2c application protocol in ServiceImport + Added support for per-host circuit breaker thresholds + Added support for injecting a credential from a Kubernetes Secret into a request header. Credentials can be injected using either an HTTPRouteFilter or a BackendRef filter. + Added support for egctl Websocket in addation to SPDY + Added a configuration option in the Helm chart to set the TrafficDistribution field in the Envoy Gateway Service + Added support for setting the log level to trace for the Envoy Proxy + Added support for global imageRegistry and imagePullSecrets to the Helm chart + Added support for using a local JWKS in an inline string or in a ConfigMap to validate JWT tokens in SecurityPolicy + Added support for logging the status of resources in standalone mode. + Added support for per-route tracing in BackendTrafficPolicy + Added support for configuring retry settings for Extension Service hooks in EnvoyGateway config. + Added support for request buffering using the Envoy Buffer filter + Added support for merge type in BackendTrafficPolicy + Added support for `OverlappingTLSConfig` condition in Gateway status. This condition is set if there are overlapping hostnames or certificates between listeners. The ALPN protocol is set to HTTP/1.1 for the overlapping listeners to avoid HTTP/2 Connection Coalescing. + +bug fixes: | + Fix traffic splitting when filters are attached to the backendRef. + Added support for Secret and ConfigMap parsing in Standalone mode. + Bypass overload manager for stats and ready listeners + Fix translating backendSettings for extAuth + Fix an issue that stats compressor was not working. + Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode. + Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated. + Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted. + Fix allowing empty text field for opentelemetry sink when using JSON format. + Fix an issue that SamplingFraction was not working. + Fix kubernetes resources not being deleted when the customized name used. + Do not treat essential resource like namespace as the missing resource while loading from file. + Do not set retriable status codes to 503 when RetryOn is configured in BackendTrafficPolicy. + Make the Topology Injector Webhook best effort, and skip on failures. + +# Enhancements that improve performance. +performance improvements: | + Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in + a background goroutine. + +# Deprecated features or APIs. +deprecations: | + Deprecated the PreserveXRequestID field. + +# Other notable changes not covered by the above sections. +Other changes: | + Updated gateway-api to v1.3.0 diff --git a/site/archetypes/concept.md b/site/archetypes/concept.md index d90a91164d..88d0448117 100644 --- a/site/archetypes/concept.md +++ b/site/archetypes/concept.md @@ -22,9 +22,11 @@ Describe common use cases. When would someone need this concept? List scenarios where understanding this will help the user make better architectural or operational decisions. --> -## Configuration in Envoy Gateway +## {Title} in Envoy Gateway