diff --git a/.changelog/2194.txt b/.changelog/2194.txt index fb265d9739..997326218b 100644 --- a/.changelog/2194.txt +++ b/.changelog/2194.txt @@ -1,3 +1,3 @@ -```release-note:bug +```release-note: crd: fix bug on service intentions CRD causing some updates to be ignored. ``` diff --git a/.changelog/2346.txt b/.changelog/2346.txt deleted file mode 100644 index fb062ee0fb..0000000000 --- a/.changelog/2346.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Set locality on services registered with connect-inject. -``` diff --git a/.changelog/2265.txt b/.changelog/2370.txt similarity index 96% rename from .changelog/2265.txt rename to .changelog/2370.txt index 1cf6813c94..35643ce272 100644 --- a/.changelog/2265.txt +++ b/.changelog/2370.txt @@ -1,3 +1,3 @@ ```release-note:improvement (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration -``` +``` \ No newline at end of file diff --git a/.changelog/2735.txt b/.changelog/2735.txt deleted file mode 100644 index 8b74b5552d..0000000000 --- a/.changelog/2735.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: add RouteRetryFilter and RouteTimeoutFilter CRDs -``` \ No newline at end of file diff --git a/.changelog/2743.txt b/.changelog/2743.txt deleted file mode 100644 index 4e8db233b1..0000000000 --- a/.changelog/2743.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -control-plane: Changed the container ordering in connect-inject to insert consul-dataplane container first if lifecycle is enabled. Container ordering is unchanged if lifecycle is disabled. -``` diff --git a/.changelog/2748.txt b/.changelog/2748.txt deleted file mode 100644 index 2a8c922d13..0000000000 --- a/.changelog/2748.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:bug -control-plane: Set locality on sidecar proxies in addition to services when registering with connect-inject. -``` diff --git a/.changelog/2784.txt b/.changelog/2784.txt deleted file mode 100644 index 5b11ca3d43..0000000000 --- a/.changelog/2784.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -Add the `PrioritizeByLocality` field to the `ServiceResolver` and `ProxyDefaults` CRDs. -``` diff --git a/.changelog/2844.txt b/.changelog/2844.txt deleted file mode 100644 index 89ba684575..0000000000 --- a/.changelog/2844.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -helm: (Consul Enterprise) Adds rate limiting config to serviceDefaults CRD -``` diff --git a/.changelog/2904.txt b/.changelog/2904.txt deleted file mode 100644 index 69755454d9..0000000000 --- a/.changelog/2904.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:feature -api-gateway: Add support for response header modifiers in HTTPRoute filters -``` diff --git a/.changelog/2905.txt b/.changelog/2905.txt index eb1196fa0f..c40135215b 100644 --- a/.changelog/2905.txt +++ b/.changelog/2905.txt @@ -1,3 +1,3 @@ ```release-note:bug audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. -``` \ No newline at end of file +``` diff --git a/.changelog/2941.txt b/.changelog/2941.txt deleted file mode 100644 index 7b92ac64f3..0000000000 --- a/.changelog/2941.txt +++ /dev/null @@ -1,22 +0,0 @@ -```release-note:feature -:tada: This release provides the ability to preview Consul's v2 Catalog and Resource API if enabled. -The new model supports multi-port application deployments with only a single Envoy proxy. -Note that the v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview. -See the [v2 Catalog and Resource API documentation](https://developer.hashicorp.com/consul/docs/k8s/multiport) for more information. -The v2 Catalog and Resources API should be considered a feature preview within this release and should not be used in production environments. - -### Limitations -* The v1 and v2 catalog APIs cannot run concurrently. -* The Consul UI must be disable. It does not support multi-port services or the v2 catalog API in this release. -* HCP Consul does not support multi-port services or the v2 catalog API in this release. -* The v2 API only supports transparent proxy mode where services that have permissions to connect to each other can use - Kube DNS to connect. - -### Known Issues -* When using the v2 API with transparent proxy, Kubernetes pods cannot use L7 liveness, readiness, or startup probes. - -[[GH-2868]](https://github.com/hashicorp/consul-k8s/pull/2868) -[[GH-2883]](https://github.com/hashicorp/consul-k8s/pull/2883) -[[GH-2930]](https://github.com/hashicorp/consul-k8s/pull/2930) -[[GH-2967]](https://github.com/hashicorp/consul-k8s/pull/2967) -``` diff --git a/.changelog/2962.txt b/.changelog/2962.txt deleted file mode 100644 index f707e4828f..0000000000 --- a/.changelog/2962.txt +++ /dev/null @@ -1,3 +0,0 @@ -```releast-note:feature -api-gateway: (Consul Enterprise) Add JWT authentication and authorization for API Gateway and HTTPRoutes. -``` diff --git a/.changelog/3116.txt b/.changelog/3119.txt similarity index 100% rename from .changelog/3116.txt rename to .changelog/3119.txt diff --git a/.changelog/3138.txt b/.changelog/3138.txt deleted file mode 100644 index 2eefd6b616..0000000000 --- a/.changelog/3138.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -helm: Kubernetes v1.28 is now supported. Minimum tested version of Kubernetes is now v1.25. -``` diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f5d211b137..b615e69dd1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,6 +9,7 @@ How I expect reviewers to test this PR: Checklist: - [ ] Tests added -- [ ] [CHANGELOG entry added](https://github.com/hashicorp/consul-k8s/blob/main/CONTRIBUTING.md#adding-a-changelog-entry) - +- [ ] CHANGELOG entry added + > HashiCorp engineers only, community PRs should not add a changelog entry. + > Entries should use present tense (e.g. Add support for...) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 2b26c2371e..7f39b4e5a0 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -13,7 +13,7 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.3.5 + container: hashicorpdev/backport-assistant:0.3.3 steps: - name: Run Backport Assistant run: backport-assistant backport -merge-method=squash -gh-automerge diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7499ead061..e9ae8a1b49 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: outputs: go-version: ${{ steps.get-go-version.outputs.go-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go @@ -35,7 +35,7 @@ jobs: outputs: product-version: ${{ steps.get-product-version.outputs.product-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: get product version id: get-product-version run: | @@ -49,7 +49,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: "Checkout directory" - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -121,10 +121,10 @@ jobs: name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} ${{ matrix.component }} ${{ matrix.fips }} build steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 with: go-version: ${{ matrix.go }} @@ -193,7 +193,7 @@ jobs: - name: Test rpm package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: registry.access.redhat.com/ubi9/ubi:latest options: -v ${{ github.workspace }}:/work @@ -218,7 +218,7 @@ jobs: - name: Test debian package if: ${{ matrix.goos == 'linux' && matrix.component == 'cli' && matrix.goarch == 'amd64'}} - uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + uses: addnab/docker-run-action@v3 # TSCCR: no entry for repository "addnab/docker-run-action" with: image: ubuntu:latest options: -v ${{ github.workspace }}:/work @@ -258,7 +258,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_${{ matrix.goos}}_${{ matrix.goarch }}.zip @@ -341,7 +341,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip @@ -404,7 +404,7 @@ jobs: repo: ${{ github.event.repository.name }} version: ${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 with: name: consul-cni_${{ needs.get-product-version.outputs.product-version }}${{ matrix.fips }}_linux_${{ matrix.arch }}.zip diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 40c9b17c68..1eea4196e7 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index bddc69c83f..700803a45f 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -15,7 +15,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -72,14 +72,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index 54a532d940..a285c4c176 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -13,7 +13,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -39,7 +39,7 @@ jobs: id: is-team-member run: | TEAM=consul - ROLE="$(gh api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" + ROLE="$(hub api orgs/hashicorp/teams/${TEAM}/memberships/${{ github.actor }} | jq -r '.role | select(.!=null)')" if [[ -n ${ROLE} ]]; then echo "Actor ${{ github.actor }} is a ${TEAM} team member" echo "MESSAGE=true" >> $GITHUB_OUTPUT @@ -86,14 +86,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/nightly-acceptance-1-3-0.yml b/.github/workflows/nightly-acceptance-1-3-0.yml deleted file mode 100644 index e7681a6835..0000000000 --- a/.github/workflows/nightly-acceptance-1-3-0.yml +++ /dev/null @@ -1,26 +0,0 @@ -# Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-acceptance-1-3-0 -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run nightly at 12AM UTC/8PM EST/5PM PST - - cron: '0 0 * * *' - -# these should be the only settings that you will ever need to change -env: - BRANCH: "release/1.3.0" - CONTEXT: "nightly" - -jobs: - cloud: - name: cloud - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - name: cloud - with: - workflow: cloud.yml - repo: hashicorp/consul-k8s-workflows - ref: main - token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/nightly-api-gateway-conformance.yml b/.github/workflows/nightly-api-gateway-conformance.yml deleted file mode 100644 index abeec34659..0000000000 --- a/.github/workflows/nightly-api-gateway-conformance.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Dispatch to the consul-k8s-workflows with a nightly cron -name: nightly-api-gateway-conformance -on: - schedule: - # * is a special character in YAML so you have to quote this string - # Run nightly at 12AM UTC/8PM EST/5PM PST. - - cron: '0 0 * * *' - - -# these should be the only settings that you will ever need to change -env: - BRANCH: ${{ github.ref_name }} - CONTEXT: "nightly" - -jobs: - api-gateway-conformance: - name: api-gateway-conformance - runs-on: ubuntu-latest - steps: - - uses: benc-uk/workflow-dispatch@798e70c97009500150087d30d9f11c5444830385 # v1.2.2 - name: conformance - with: - workflow: api-gateway-conformance.yml - repo: hashicorp/consul-k8s-workflows - ref: main - token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - inputs: '{ "context":"${{ env.CONTEXT }}", "repository":"${{ github.repository }}", "branch":"${{ env.BRANCH }}", "sha":"${{ github.sha }}", "token":"${{ secrets.ELEVATED_GITHUB_TOKEN }}" }' diff --git a/.github/workflows/weekly-acceptance-1-3-x.yml b/.github/workflows/weekly-acceptance-0-49-x.yml similarity index 81% rename from .github/workflows/weekly-acceptance-1-3-x.yml rename to .github/workflows/weekly-acceptance-0-49-x.yml index e0cd935204..5e1c17f3c7 100644 --- a/.github/workflows/weekly-acceptance-1-3-x.yml +++ b/.github/workflows/weekly-acceptance-0-49-x.yml @@ -1,18 +1,16 @@ # Dispatch to the consul-k8s-workflows with a weekly cron # # A separate file is needed for each release because the cron schedules are different for each release. -name: weekly-acceptance-1-3-x +name: weekly-acceptance-0-49-x on: schedule: # * is a special character in YAML so you have to quote this string - # Run weekly on Wednesday at 3AM UTC/11PM EST/8PM PST - # - cron: '0 3 * * 3' - - cron: '0 0 * * *' # Temporarily nightly until 1.2.0 GA - + # Run weekly on Monday at 3AM UTC/11PM EST/8PM PST + - cron: '0 3 * * 1' # these should be the only settings that you will ever need to change env: - BRANCH: "release/1.3.x" + BRANCH: "release/0.49.x" CONTEXT: "weekly" jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index 06934dd5cf..c47fea8db4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,30 @@ +## 1.2.3 (November 2, 2023) + +SECURITY: + +* Update Envoy version to 1.25.11 to address [CVE-2023-44487](https://github.com/envoyproxy/envoy/security/advisories/GHSA-jhv4-f7mr-xx76) [[GH-3119](https://github.com/hashicorp/consul-k8s/issues/3119)] +* Upgrade `google.golang.org/grpc` to 1.56.3. +This resolves vulnerability [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3139](https://github.com/hashicorp/consul-k8s/issues/3139)] +* Upgrade to use Go 1.20.10 and `x/net` 0.17.0. +This resolves [CVE-2023-39325](https://nvd.nist.gov/vuln/detail/CVE-2023-39325) +/ [CVE-2023-44487](https://nvd.nist.gov/vuln/detail/CVE-2023-44487). [[GH-3085](https://github.com/hashicorp/consul-k8s/issues/3085)] + +BUG FIXES: + +* api-gateway: fix issue where missing `NET_BIND_SERVICE` capability prevented api-gateway `Pod` from starting up when deployed to OpenShift [[GH-3070](https://github.com/hashicorp/consul-k8s/issues/3070)] +* control-plane: only alert on valid errors, not timeouts in gateway [[GH-3128](https://github.com/hashicorp/consul-k8s/issues/3128)] +* crd: fix misspelling of preparedQuery field in ControlPlaneRequestLimit CRD [[GH-3001](https://github.com/hashicorp/consul-k8s/issues/3001)] + ## 1.2.2 (September 21, 2023) SECURITY: * Upgrade to use Go 1.20.8. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] +[CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), +[CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), +[CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), +[CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and +[CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] FEATURES: @@ -24,8 +41,8 @@ IMPROVEMENTS: * control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] * helm: Add `JWKSCluster` field to `JWTProvider` CRD. [[GH-2881](https://github.com/hashicorp/consul-k8s/issues/2881)] * vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] +secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. +This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] BUG FIXES: @@ -36,56 +53,6 @@ BUG FIXES: * helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] * ingress-gateway: Adds missing PassiveHealthCheck to IngressGateways CRD and updates missing fields on ServiceDefaults CRD [[GH-2796](https://github.com/hashicorp/consul-k8s/issues/2796)] -## 1.1.6 (September 21, 2023) - -SECURITY: - -* Upgrade to use Go 1.20.8. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2936](https://github.com/hashicorp/consul-k8s/issues/2936)] - -IMPROVEMENTS: - -* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] -* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] - -BUG FIXES: - -* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] - -## 1.0.10 (September 21, 2023) - -SECURITY: - -* Upgrade to use Go 1.19.13. This resolves CVEs - [CVE-2023-39320](https://github.com/advisories/GHSA-rxv8-v965-v333) (`cmd/go`), - [CVE-2023-39318](https://github.com/advisories/GHSA-vq7j-gx56-rxjh) (`html/template`), - [CVE-2023-39319](https://github.com/advisories/GHSA-vv9m-32rr-3g55) (`html/template`), - [CVE-2023-39321](https://github.com/advisories/GHSA-9v7r-x7cv-v437) (`crypto/tls`), and - [CVE-2023-39322](https://github.com/advisories/GHSA-892h-r6cr-53g4) (`crypto/tls`) [[GH-2938](https://github.com/hashicorp/consul-k8s/issues/2938)] - -IMPROVEMENTS: - -* Add NET_BIND_SERVICE capability to restricted security context used for consul-dataplane [[GH-2787](https://github.com/hashicorp/consul-k8s/issues/2787)] -* Add new value `global.argocd.enabled`. Set this to `true` when using ArgoCD to deploy this chart. [[GH-2785](https://github.com/hashicorp/consul-k8s/issues/2785)] -* control-plane: Improve performance for pod deletions by reducing the number of fetched tokens. [[GH-2910](https://github.com/hashicorp/consul-k8s/issues/2910)] -* control-plane: prevent updation of anonymous-token-policy and anonymous-token if anonymous-token-policy is already attached to the anonymous-token [[GH-2790](https://github.com/hashicorp/consul-k8s/issues/2790)] -* vault: Adds `namespace` to `secretsBackend.vault.connectCA` in Helm chart and annotation: "vault.hashicorp.com/namespace: namespace" to - secretsBackend.vault.agentAnnotations, if "vault.hashicorp.com/namespace" annotation is not present. - This provides a more convenient way to specify the Vault namespace than nested JSON in `connectCA.additionalConfig`. [[GH-2841](https://github.com/hashicorp/consul-k8s/issues/2841)] - -BUG FIXES: - -* audit-log: fix parsing error for some audit log configuration fields fail with uncovertible string to integer errors. [[GH-2905](https://github.com/hashicorp/consul-k8s/issues/2905)] -* control-plane: Fix issue where ACL tokens would have an empty pod name that prevented proper token cleanup. [[GH-2808](https://github.com/hashicorp/consul-k8s/issues/2808)] -* control-plane: When using transparent proxy or CNI, reduced required permissions by setting privileged to false. Privileged must be true when using OpenShift without CNI. [[GH-2755](https://github.com/hashicorp/consul-k8s/issues/2755)] -* helm: Update prometheus port and scheme annotations if tls is enabled [[GH-2782](https://github.com/hashicorp/consul-k8s/issues/2782)] - ## 1.2.1 (Aug 10, 2023) BREAKING CHANGES: @@ -94,10 +61,10 @@ BREAKING CHANGES: SECURITY: * Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] +This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] * Upgrade to use Go 1.20.7 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] +This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) +and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] FEATURES: @@ -133,88 +100,15 @@ BUG FIXES: * api-gateway: Fix creation of invalid Kubernetes Service when multiple Gateway listeners have the same port. [[GH-2413](https://github.com/hashicorp/consul-k8s/issues/2413)] * api-gateway: fix helm install when setting copyAnnotations or nodeSelector [[GH-2597](https://github.com/hashicorp/consul-k8s/issues/2597)] * api-gateway: fixes bug where envoy will silently reject RSA keys less than 2048 bits in length when not in FIPS mode, and - will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate - and reject invalid certs earlier. [[GH-2478](https://github.com/hashicorp/consul-k8s/issues/2478)] +will reject keys that are not 2048, 3072, or 4096 bits in length in FIPS mode. We now validate +and reject invalid certs earlier. [[GH-2478](https://github.com/hashicorp/consul-k8s/issues/2478)] * api-gateway: set route condition appropriately when parent ref includes non-existent section name [[GH-2420](https://github.com/hashicorp/consul-k8s/issues/2420)] * control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] * control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] * helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] * helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] * transparent-proxy: Fix issue where connect-inject lacked sufficient `mesh:write` privileges in some deployments, - which prevented virtual IPs from persisting properly. [[GH-2520](https://github.com/hashicorp/consul-k8s/issues/2520)] - -## 1.1.4 (Aug 10, 2023) - -SECURITY: - -* Upgrade to use Go 1.20.6 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2642](https://github.com/hashicorp/consul-k8s/issues/2642)] -* Upgrade to use Go 1.20.7 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2710](https://github.com/hashicorp/consul-k8s/issues/2710)] - -IMPROVEMENTS: - -* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields -1. `global.acls.logLevel` -2. `global.tls.logLevel` -3. `global.federation.logLevel` -4. `global.gossipEncryption.logLevel` -5. `server.logLevel` -6. `client.logLevel` -7. `meshGateway.logLevel` -8. `ingressGateways.logLevel` -9. `terminatingGateways.logLevel` -10. `telemetryCollector.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] -* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] -* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] -* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] -* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] - -BUG FIXES: - -* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] -* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] -* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] - -## 1.0.9 (Aug 10, 2023) - -SECURITY: - -* Upgrade to use Go 1.19.11 and `x/net/http` 0.12.0. - This resolves [CVE-2023-29406](https://github.com/advisories/GHSA-f8f7-69v5-w4vx)(`net/http`). [[GH-2650](https://github.com/hashicorp/consul-k8s/issues/2650)] -* Upgrade to use Go 1.19.12 and `x/net` 0.13.0. - This resolves [CVE-2023-29409](https://nvd.nist.gov/vuln/detail/CVE-2023-29409)(`crypto/tls`) - and [CVE-2023-3978](https://nvd.nist.gov/vuln/detail/CVE-2023-3978)(`net/html`). [[GH-2717](https://github.com/hashicorp/consul-k8s/issues/2717)] - -IMPROVEMENTS: - -* Add support to provide the logLevel flag via helm for multiple low level components. Introduces the following fields -1. `global.acls.logLevel` -2. `global.tls.logLevel` -3. `global.federation.logLevel` -4. `global.gossipEncryption.logLevel` -5. `server.logLevel` -6. `client.logLevel` -7. `meshGateway.logLevel` -8. `ingressGateways.logLevel` -9. `terminatingGateways.logLevel` [[GH-2302](https://github.com/hashicorp/consul-k8s/issues/2302)] -* control-plane: increase timeout after login for ACL replication to 60 seconds [[GH-2656](https://github.com/hashicorp/consul-k8s/issues/2656)] -* helm: adds values for `securityContext` and `annotations` on TLS and ACL init/cleanup jobs. [[GH-2525](https://github.com/hashicorp/consul-k8s/issues/2525)] -* helm: do not set container securityContexts by default on OpenShift < 4.11 [[GH-2678](https://github.com/hashicorp/consul-k8s/issues/2678)] -* helm: set container securityContexts to match the `restricted` Pod Security Standards policy to support running Consul in a namespace with restricted PSA enforcement enabled [[GH-2572](https://github.com/hashicorp/consul-k8s/issues/2572)] - -BUG FIXES: - -* control-plane: fix bug in endpoints controller when deregistering services from consul when a node is deleted. [[GH-2571](https://github.com/hashicorp/consul-k8s/issues/2571)] -* helm: fix CONSUL_LOGIN_DATACENTER for consul client-daemonset. [[GH-2652](https://github.com/hashicorp/consul-k8s/issues/2652)] -* helm: fix ui ingress manifest formatting, and exclude `ingressClass` when not defined. [[GH-2687](https://github.com/hashicorp/consul-k8s/issues/2687)] - -## 0.49.8 (July 12, 2023) - -IMPROVEMENTS: - -* helm: Add `connectInject.prepareDataplanesUpgrade` setting for help upgrading to dataplanes. This setting is required if upgrading from non-dataplanes to dataplanes when ACLs are enabled. See https://developer.hashicorp.com/consul/docs/k8s/upgrade#upgrading-to-consul-dataplane for more information. [[GH-2514](https://github.com/hashicorp/consul-k8s/issues/2514)] +which prevented virtual IPs from persisting properly. [[GH-2520](https://github.com/hashicorp/consul-k8s/issues/2520)] ## 1.2.0 (June 28, 2023) @@ -264,89 +158,6 @@ BUG FIXES: * control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] * control-plane: fix issue where consul-connect-injector acl token was unintentionally being deleted and not recreated when a container was restarted due to a livenessProbe failure. [[GH-1914](https://github.com/hashicorp/consul-k8s/issues/1914)] -## 1.1.3 (June 28, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] -* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] - -FEATURES: - -* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] -* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2369](https://github.com/hashicorp/consul-k8s/issues/2369)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] - -BUG FIXES: - -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] - -## 1.0.8 (June 28, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] -* Bump `controller-runtime` to address CVEs in dependencies. [[GH-2225](https://github.com/hashicorp/consul-k8s/issues/2225)] -* Update [Go-Discover](https://github.com/hashicorp/go-discover) in the container has been updated to address [CVE-2020-14040](https://github.com/advisories/GHSA-5rcv-m4m3-hfh7) [[GH-2390](https://github.com/hashicorp/consul-k8s/issues/2390)] - -FEATURES: - -* Add support for configuring graceful shutdown proxy lifecycle management settings. [[GH-2233](https://github.com/hashicorp/consul-k8s/issues/2233)] -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] -* sync-catalog: add ability to support weighted loadbalancing by service annotation `consul.hashicorp.com/service-weight: ` [[GH-2293](https://github.com/hashicorp/consul-k8s/issues/2293)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] - -BUG FIXES: - -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* control-plane: Fix casing of the Enforce Consecutive 5xx field on Service Defaults and acceptance test fixtures. [[GH-2266](https://github.com/hashicorp/consul-k8s/issues/2266)] -* control-plane: add support for idleTimeout in the Service Router config [[GH-2156](https://github.com/hashicorp/consul-k8s/issues/2156)] -* control-plane: fix issue with json tags of service defaults fields EnforcingConsecutive5xx, MaxEjectionPercent and BaseEjectionTime. [[GH-2159](https://github.com/hashicorp/consul-k8s/issues/2159)] -* control-plane: fix issue with multiport pods crashlooping due to dataplane port conflicts by ensuring dns redirection is disabled for non-tproxy pods [[GH-2176](https://github.com/hashicorp/consul-k8s/issues/2176)] -* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] - - -## 0.49.7 (June 28, 2023) -BREAKING CHANGES: - -* control-plane: All policies managed by consul-k8s will now be updated on upgrade. If you previously edited the policies after install, your changes will be overwritten. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] - -SECURITY: - -* Bump Dockerfile base image for RedHat UBI `consul-k8s-control-plane` image to `ubi-minimal:9.2`. [[GH-2204](https://github.com/hashicorp/consul-k8s/issues/2204)] -* Bump Dockerfile base image to `alpine:3.18`. Resolves [CVE-2023-2650](https://github.com/advisories/GHSA-gqxg-9vfr-p9cg) vulnerability in openssl@3.0.8-r4 [[GH-2284](https://github.com/hashicorp/consul-k8s/issues/2284)] - -FEATURES: - -* helm: Adds `acls.resources` field which can be configured to override the `resource` settings for the `server-acl-init` and `server-acl-init-cleanup` Jobs. [[GH-2416](https://github.com/hashicorp/consul-k8s/issues/2416)] - -IMPROVEMENTS: - -* (Consul Enterprise) Add support to provide inputs via helm for audit log related configuration [[GH-2265](https://github.com/hashicorp/consul-k8s/issues/2265)] -* helm: Update the default amount of memory used by the connect-inject controller so that its less likely to get OOM killed. [[GH-2249](https://github.com/hashicorp/consul-k8s/issues/2249)] - -BUG FIXES: - -* control-plane: Always update ACL policies upon upgrade. [[GH-2392](https://github.com/hashicorp/consul-k8s/issues/2392)] -* crd: fix bug on service intentions CRD causing some updates to be ignored. [[GH-2194](https://github.com/hashicorp/consul-k8s/issues/2194)] - ## 1.1.2 (June 5, 2023) SECURITY: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b06c27d8a..c1e3446e8d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ 1. [Running linters locally](#running-linters-locally) 1. [Rebasing contributions against main](#rebasing-contributions-against-main) 1. [Creating a new CRD](#creating-a-new-crd) - 1. [The Structs](#the-structs) + 1. [The Structs](#the-structs) 1. [Spec Methods](#spec-methods) 1. [Spec Tests](#spec-tests) 1. [Controller](#controller) @@ -31,13 +31,13 @@ ### Building and running `consul-k8s-control-plane` -To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. +To build and install the control plane binary `consul-k8s-control-plane` locally, Go version 1.17.0+ is required. You will also need to install the Docker engine: - [Docker for Mac](https://docs.docker.com/engine/installation/mac/) - [Docker for Windows](https://docs.docker.com/engine/installation/windows/) - [Docker for Linux](https://docs.docker.com/engine/installation/linux/ubuntulinux/) - + Install [gox](https://github.com/mitchellh/gox) (v1.14+). For Mac and Linux: ```bash brew install gox @@ -102,7 +102,7 @@ controller: enabled: true ``` -Run a `helm install` from the project root directory to target your dev version of the Helm chart. +Run a `helm install` from the project root directory to target your dev version of the Helm chart. ```shell helm install consul --create-namespace -n consul -f ./values.dev.yaml ./charts/consul @@ -125,7 +125,7 @@ consul-k8s version ### Making changes to consul-k8s -The first step to making changes is to fork Consul K8s. Afterwards, the easiest way +The first step to making changes is to fork Consul K8s. Afterwards, the easiest way to work on the fork is to set it as a remote of the Consul K8s project: 1. Rename the existing remote's name: `git remote rename origin upstream`. @@ -164,7 +164,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ## Creating a new CRD ### The Structs -1. Run the generate command from the `control-plane` directory: (installation instructions for `operator-sdk` found [here](https://sdk.operatorframework.io/docs/installation/): +1. Run the generate command: ```bash operator-sdk create api --group consul --version v1alpha1 --kind IngressGateway --controller --namespaced=true --make=false --resource=true ``` @@ -173,37 +173,37 @@ rebase the branch on main, fixing any conflicts along the way before the code ca func init() { SchemeBuilder.Register(&IngressGateway{}, &IngressGatewayList{}) } - + // +kubebuilder:object:root=true // +kubebuilder:subresource:status - + // IngressGateway is the Schema for the ingressgateways API type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` Status IngressGatewayStatus `json:"status,omitempty"` } - + // +kubebuilder:object:root=true - + // IngressGatewayList contains a list of IngressGateway type IngressGatewayList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []IngressGateway `json:"items"` } - + // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - + // Foo is an example field of IngressGateway. Edit IngressGateway_types.go to remove/update Foo string `json:"foo,omitempty"` } - + // IngressGatewayStatus defines the observed state of IngressGateway type IngressGatewayStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -225,7 +225,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca type IngressGateway struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - + Spec IngressGatewaySpec `json:"spec,omitempty"` - Status IngressGatewayStatus `json:"status,omitempty"` + Status `json:"status,omitempty"` @@ -235,7 +235,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. Copy the top-level fields over into the `Spec` struct except for `Kind`, `Name`, `Namespace`, `Partition`, `Meta`, `CreateIndex` and `ModifyIndex`. In this example, the top-level fields remaining are `TLS` and `Listeners`: - + ```go // IngressGatewaySpec defines the desired state of IngressGateway type IngressGatewaySpec struct { @@ -261,8 +261,8 @@ rebase the branch on main, fixing any conflicts along the way before the code ca automatically stub out all the methods by using Code -> Generate -> IngressGateway -> ConfigEntryResource. 1. Use existing implementations of other types to implement the methods. We have to copy their code because we can't use a common struct that implements the methods - because that messes up the CRD code generation. - + because that messes up the CRD code generation. + You should be able to follow the other "normal" types. The non-normal types are `ServiceIntention` and `ProxyDefault` because they have special behaviour around being global or their spec not matching up with Consul's directly. @@ -273,7 +273,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. For `Validate`, we again follow the pattern of implementing the method on each sub-struct. You'll need to read the Consul documentation to understand what validation needs to be done. - + Things to keep in mind: 1. Re-use the `sliceContains` and `notInSliceMessage` helper methods where applicable. 1. If the invalid field is an entire struct, encode as json (look for `asJSON` for an example). @@ -333,7 +333,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca 1. `TestConfigEntryControllers_doesNotCreateUnownedConfigEntry` 1. `TestConfigEntryControllers_doesNotDeleteUnownedConfig` 1. Note: we don't add tests to `configentry_controller_ent_test.go` because we decided - it's too much duplication and the controllers are already properly exercised in the oss tests. + it's too much duplication and the controllers are already properly exercised in the oss tests. ### Webhook 1. Copy an existing webhook to `control-plane/api/v1alpha/ingressgateway_webhook.go` @@ -376,7 +376,7 @@ rebase the branch on main, fixing any conflicts along the way before the code ca ### Generating YAML 1. Run `make ctrl-manifests` to generate the CRD and webhook YAML. -1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patches:` +1. Uncomment your CRD in `control-plane/config/crd/kustomization` under `patchesStrategicMerge:` 1. Update the sample, e.g. `control-plane/config/samples/consul_v1alpha1_ingressgateway.yaml` to a valid resource that can be used for testing: ```yaml @@ -507,7 +507,7 @@ a token named `foo`. ``` * Add `if` statement in `Run` to create your token (follow placement of other tokens). You'll need to decide if you need a local token (use `createLocalACL()`) or a global token (use `createGlobalACL()`). - + ```go if c.flagCreateFooToken { err := c.createLocalACL("foo", fooRules, consulDC, isPrimary, consulClient) @@ -588,7 +588,7 @@ The acceptance tests require a Kubernetes cluster with a configured `kubectl`. ```bash brew install python-yq ``` -* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) +* [Helm 3](https://helm.sh) (Currently, must use v3.8.0+.) ```bash brew install kubernetes-helm ``` @@ -617,7 +617,7 @@ To run a specific test by name use the `--filter` flag: bats ./charts/consul/test/unit/.bats --filter "my test name" #### Acceptance Tests -##### Pre-requisites +##### Pre-requisites * [gox](https://github.com/mitchellh/gox) (v1.14+) ```bash brew install gox @@ -629,7 +629,7 @@ To run the acceptance tests: cd acceptance/tests go test ./... -p 1 - + The above command will run all tests that can run against a single Kubernetes cluster, using the current context set in your kubeconfig locally. @@ -689,7 +689,7 @@ Changes to the Helm chart should be accompanied by appropriate unit tests. #### Formatting -- Put tests in the test file in the same order as the variables appear in the `values.yaml`. +- Put tests in the test file in the same order as the variables appear in the `values.yaml`. - Start tests for a chart value with a header that says what is being tested, like this: ``` #-------------------------------------------------------------------- @@ -710,8 +710,8 @@ In all of the tests in this repo, the base command being run is [helm template]( In this way, we're able to test that the various conditionals in the templates render as we would expect. Each test defines the files that should be rendered using the `-x` flag, then it might adjust chart values by adding `--set` flags as well. -The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). -`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). +The output from this `helm template` command is then piped to [yq](https://pypi.org/project/yq/). +`yq` allows us to pull out just the information we're interested in, either by referencing its position in the yaml file directly or giving information about it (like its length). The `-r` flag can be used with `yq` to return a raw string instead of a quoted one which is especially useful when looking for an exact match. The test passes or fails based on the conditional at the end that is in square brackets, which is a comparison of our expected value and the output of `helm template` piped to `yq`. @@ -786,11 +786,11 @@ Here are some examples of common test patterns: cd `chart_dir` assert_empty helm template \ -s templates/sync-catalog-deployment.yaml \ - . + . } ``` Here we are using the `assert_empty` helper command. - + ### Writing Acceptance Tests If you are adding a feature that fits thematically with one of the existing test suites, @@ -831,9 +831,9 @@ you need to handle that in the `TestMain` function. ```go func TestMain(m *testing.M) { - // First, create a new suite so that all flags are parsed. + // First, create a new suite so that all flags are parsed. suite = framework.NewSuite(m) - + // Run the suite only if our example feature test flag is set. if suite.Config().EnableExampleFeature { os.Exit(suite.Run()) @@ -866,16 +866,16 @@ func TestExample(t *testing.T) { helmValues := map[string]string{ "exampleFeature.enabled": "true", } - - // Generate a random name for this test. + + // Generate a random name for this test. releaseName := helpers.RandomName() // Create a new Consul cluster object. consulCluster := framework.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - + // Create the Consul cluster with Helm. consulCluster.Create(t) - + // Make test assertions. } ``` @@ -981,7 +981,7 @@ Any given test can be run either through GoLand or another IDE, or via command l To run all of the connect tests from command line: ```shell $ cd acceptance/tests -$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls +$ go test ./connect/... -v -p 1 -timeout 2h -failfast -use-kind -no-cleanup-on-failure -kubecontext=kind-dc1 -secondary-kubecontext=kind-dc2 -enable-enterprise -enable-multi-cluster -debug-directory=/tmp/debug -consul-k8s-image=kyleschochenmaier/consul-k8s-acls ``` When running from command line a few things are important: @@ -1129,17 +1129,17 @@ Certificate: X509v3 Subject Alternative Name: DNS:pri-1dchdli.vault.ca.34a76791.consul, URI:spiffe://34a76791-b9b2-b93e-b0e4-1989ed11a28e.consul -``` +``` --- ## Helm Reference Docs - + The Helm reference docs (https://www.consul.io/docs/k8s/helm) are automatically generated from our `values.yaml` file. ### Generating Helm Reference Docs - + To generate the docs and update the `helm.mdx` file: 1. Fork `hashicorp/consul` (https://github.com/hashicorp/consul) on GitHub. @@ -1147,7 +1147,7 @@ To generate the docs and update the `helm.mdx` file: ```shell-session git clone https://github.com//consul.git ``` -1. Change directory into your `consul-k8s` repo: +1. Change directory into your `consul-k8s` repo: ```shell-session cd /path/to/consul-k8s ``` @@ -1216,11 +1216,11 @@ manage. One such example is the Gateway API CRDs which we use to configure API G Networking. To pull external CRDs into our Helm chart and make sure they get installed, we generate their configuration using -[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these +[Kustomize](https://kustomize.io/) which can pull in Kubernetes config from external sources. We split these generated CRDs into individual files and store them in the `charts/consul/templates` directory. -If you need to update the external CRDs we depend on, or add to them, you can do this by editing the -[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. +If you need to update the external CRDs we depend on, or add to them, you can do this by editing the +[control-plane/config/crd/external/kustomization.yaml](/control-plane/config/crd/external/kustomization.yaml) file. Once modified, running ```bash @@ -1261,7 +1261,7 @@ Some common values are: - `control-plane`: related to control-plane functionality - `helm`: related to the charts module and any files, yaml, go, etc. therein -There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these +There may be cases where a `code area` doesn't make sense (i.e. addressing a Go CVE). In these cases it is okay not to provide a `code area`. For more examples, look in the [`.changelog/`](../.changelog) folder for existing changelog entries. diff --git a/Makefile b/Makefile index 5626bbe99d..8facd3dbc8 100644 --- a/Makefile +++ b/Makefile @@ -14,9 +14,6 @@ gen-helm-docs: ## Generate Helm reference docs from values.yaml and update Consu copy-crds-to-chart: ## Copy generated CRD YAML into charts/consul. Usage: make copy-crds-to-chart @cd hack/copy-crds-to-chart; go run ./... -camel-crds: ## Convert snake_case keys in yaml to camelCase. Usage: make camel-crds - @cd hack/camel-crds; go run ./... - generate-external-crds: ## Generate CRDs for externally defined CRDs and copy them to charts/consul. Usage: make generate-external-crds @cd ./control-plane/config/crd/external; \ kustomize build | yq --split-exp '.metadata.name + ".yaml"' --no-doc @@ -88,7 +85,6 @@ cni-plugin-lint: cd control-plane/cni; golangci-lint run -c ../../.golangci.yml ctrl-generate: get-controller-gen ## Run CRD code generation. - make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) object paths="./..." # Perform a terraform fmt check but don't change anything @@ -175,9 +171,7 @@ lint: cni-plugin-lint ## Run linter in the control-plane, cli, and acceptance di for p in control-plane cli acceptance; do cd $$p; golangci-lint run --path-prefix $$p -c ../.golangci.yml; cd ..; done ctrl-manifests: get-controller-gen ## Generate CRD manifests. - make ensure-controller-gen-version cd control-plane; $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - make camel-crds make copy-crds-to-chart make generate-external-crds make add-copyright-header @@ -189,7 +183,7 @@ ifeq (, $(shell which controller-gen)) CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ cd $$CONTROLLER_GEN_TMP_DIR ;\ go mod init tmp ;\ - go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.1 ;\ + go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.12.0 ;\ rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ } CONTROLLER_GEN=$(shell go env GOPATH)/bin/controller-gen @@ -197,15 +191,6 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -ensure-controller-gen-version: ## Ensure controller-gen version is v0.12.1. -ifeq (, $(shell $(CONTROLLER_GEN) --version | grep v0.12.1)) - @echo "controller-gen version is not v0.12.1, uninstall the binary and install the correct version with 'make get-controller-gen'." - @echo "Found version: $(shell $(CONTROLLER_GEN) --version)" - @exit 1 -else - @echo "Found correct version: $(shell $(CONTROLLER_GEN) --version)" -endif - add-copyright-header: ## Add copyright header to all files in the project ifeq (, $(shell which copywrite)) @echo "Installing copywrite" @@ -272,24 +257,7 @@ endif prepare-release: prepare-release-script check-preview-containers -prepare-rc-script: ## Sets the versions, updates changelog to prepare this repository to release -ifndef CONSUL_K8S_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_RELEASE_DATE - $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) -endif -ifndef CONSUL_K8S_LAST_RELEASE_GIT_TAG - $(error CONSUL_K8S_LAST_RELEASE_GIT_TAG is required) -endif -ifndef CONSUL_K8S_CONSUL_VERSION - $(error CONSUL_K8S_CONSUL_VERSION is required) -endif - @source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_rc_branch $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" $(CONSUL_K8S_LAST_RELEASE_GIT_TAG) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) $(CONSUL_K8S_PRERELEASE_VERSION); \ - -prepare-rc-branch: prepare-rc-script - -prepare-main-dev: +prepare-dev: ifndef CONSUL_K8S_RELEASE_VERSION $(error CONSUL_K8S_RELEASE_VERSION is required) endif @@ -307,24 +275,6 @@ ifndef CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION endif source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_NEXT_CONSUL_VERSION) $(CONSUL_K8S_NEXT_CONSUL_DATAPLANE_VERSION) -prepare-release-dev: -ifndef CONSUL_K8S_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_RELEASE_DATE - $(error CONSUL_K8S_RELEASE_DATE is required, use format , (ex. October 4, 2022)) -endif -ifndef CONSUL_K8S_NEXT_RELEASE_VERSION - $(error CONSUL_K8S_RELEASE_VERSION is required) -endif -ifndef CONSUL_K8S_CONSUL_VERSION - $(error CONSUL_K8S_CONSUL_VERSION is required) -endif -ifndef CONSUL_K8S_CONSUL_DATAPLANE_VERSION - $(error CONSUL_K8S_CONSUL_DATAPLANE_VERSION is required) -endif - source $(CURDIR)/control-plane/build-support/scripts/functions.sh; prepare_dev $(CURDIR) $(CONSUL_K8S_RELEASE_VERSION) "$(CONSUL_K8S_RELEASE_DATE)" "" $(CONSUL_K8S_NEXT_RELEASE_VERSION) $(CONSUL_K8S_CONSUL_VERSION) $(CONSUL_K8S_CONSUL_DATAPLANE_VERSION) - # ===========> Makefile config .DEFAULT_GOAL := help .PHONY: gen-helm-docs copy-crds-to-chart generate-external-crds bats-tests help ci.aws-acceptance-test-cleanup version cli-dev prepare-dev prepare-release @@ -336,4 +286,4 @@ DOCKER_HUB_USER=$(shell cat $(HOME)/.dockerhub) GIT_COMMIT?=$(shell git rev-parse --short HEAD) GIT_DIRTY?=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) GIT_DESCRIBE?=$(shell git describe --tags --always) -CRD_OPTIONS ?= "crd:ignoreUnexportedFields=true,allowDangerousTypes=true" +CRD_OPTIONS ?= "crd:allowDangerousTypes=true" diff --git a/README.md b/README.md index 52e909741b..d43a12b455 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,11 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). ## Features - * [**Consul Service Mesh**](https://developer.hashicorp.com/consul/docs/connect): + * [**Consul Service Mesh**](https://www.consul.io/docs/k8s/connect): Run Consul Service Mesh on Kubernetes. This feature injects Envoy sidecars and registers your Pods with Consul. - - * [**Consul API Gateway**](https://developer.hashicorp.com/consul/docs/api-gateway): - Run Consul API Gateway on Kubernetes to allow north/south traffic into Consul Service Mesh. - * [**Catalog Sync**](https://developer.hashicorp.com/consul/docs/k8s/service-sync): + * [**Catalog Sync**](https://www.consul.io/docs/k8s/service-sync): Sync Consul services into first-class Kubernetes services and vice versa. This enables Kubernetes to easily access external services and for non-Kubernetes nodes to easily discover and access Kubernetes services. @@ -50,13 +47,13 @@ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). * A [Docker image `hashicorp/consul-k8s-control-plane`](https://hub.docker.com/r/hashicorp/consul-k8s-control-plane) is available. This can be used to manually run `consul-k8s-control-plane` within a scheduled environment. - * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://developer.hashicorp.com/consul/docs/k8s/k8s-cli) for more details on usage. + * Consul K8s CLI, distributed as `consul-k8s`, can be used to install and uninstall Consul Kubernetes. See the [Consul K8s CLI Reference](https://www.consul.io/docs/k8s/k8s-cli) for more details on usage. ### Prerequisites The following pre-requisites must be met before installing Consul on Kubernetes. - * **Kubernetes 1.25.x - 1.28.x** - This represents the earliest versions of Kubernetes tested. + * **Kubernetes 1.24.x - 1.27.x** - This represents the earliest versions of Kubernetes tested. It is possible that this chart works with earlier versions, but it is untested. * Helm install @@ -92,7 +89,7 @@ for each subcommand. ### Helm -The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://developer.hashicorp.com/consul/docs/k8s/installation/install). +The Helm chart is ideal for those who prefer to use Helm for automation for either the installation or upgrade of Consul on Kubernetes. The chart supports multiple use cases of Consul on Kubernetes, depending on the values provided. Detailed installation instructions for Consul on Kubernetes are found [here](https://www.consul.io/docs/k8s/installation/overview). 1. Add the HashiCorp Helm repository: @@ -115,7 +112,7 @@ The Helm chart is ideal for those who prefer to use Helm for automation for eith Please see the many options supported in the `values.yaml` file. These are also fully documented directly on the -[Consul website](https://developer.hashicorp.com/consul/docs/k8s/helm). +[Consul website](https://www.consul.io/docs/platform/k8s/helm.html). ## Tutorials diff --git a/acceptance/ci-inputs/kind-inputs.yaml b/acceptance/ci-inputs/kind-inputs.yaml index 9b047bdac6..ba21d2cdaf 100644 --- a/acceptance/ci-inputs/kind-inputs.yaml +++ b/acceptance/ci-inputs/kind-inputs.yaml @@ -2,5 +2,5 @@ # SPDX-License-Identifier: MPL-2.0 kindVersion: v0.19.0 -kindNodeImage: kindest/node:v1.28.0@sha256:dad5a6238c5e41d7cac405fae3b5eda2ad1de6f1190fa8bfc64ff5bb86173213 +kindNodeImage: kindest/node:v1.27.1 kubectlVersion: v1.27.1 diff --git a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml index eaf1dae73d..e0e126bbda 100644 --- a/acceptance/ci-inputs/kind_acceptance_test_packages.yaml +++ b/acceptance/ci-inputs/kind_acceptance_test_packages.yaml @@ -8,4 +8,3 @@ - {runner: 4, test-packages: "cli vault metrics"} - {runner: 5, test-packages: "api-gateway ingress-gateway sync example consul-dns"} - {runner: 6, test-packages: "config-entries terminating-gateway basic"} -- {runner: 7, test-packages: "mesh_v2"} diff --git a/acceptance/framework/connhelper/connect_helper.go b/acceptance/framework/connhelper/connect_helper.go index 3f978e1d30..f5ee134b04 100644 --- a/acceptance/framework/connhelper/connect_helper.go +++ b/acceptance/framework/connhelper/connect_helper.go @@ -11,25 +11,22 @@ import ( "time" terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( StaticClientName = "static-client" StaticServerName = "static-server" JobName = "job-client" - - retryTimeout = 120 * time.Second ) // ConnectHelper configures a Consul cluster for connect injection tests. @@ -52,7 +49,6 @@ type ConnectHelper struct { // Ctx is used to deploy Consul Ctx environment.TestContext - // UseAppNamespace is used top optionally deploy applications into a separate namespace. // If unset, the namespace associated with Ctx is used. UseAppNamespace bool @@ -66,13 +62,6 @@ type ConnectHelper struct { ConsulClient *api.Client } -// ConnHelperOpts allows for configuring optional parameters to be passed into the -// conn helper methods. This provides added flexibility, although not every value will be used -// by every method. See documentation for more details. -type ConnHelperOpts struct { - ClientType string -} - // Setup creates a new cluster using the New*Cluster function and assigns it // to the consulCluster field. func (c *ConnectHelper) Setup(t *testing.T) { @@ -101,8 +90,6 @@ func (c *ConnectHelper) Upgrade(t *testing.T) { c.consulCluster.Upgrade(t, c.helmValues()) } -// KubectlOptsForApp returns options using the -apps appended namespace if -// UseAppNamespace is enabled. Otherwise, it returns the ctx options. func (c *ConnectHelper) KubectlOptsForApp(t *testing.T) *terratestK8s.KubectlOptions { opts := c.Ctx.KubectlOptions(t) if !c.UseAppNamespace { @@ -123,7 +110,7 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { // deployments because golang will execute them in reverse order // (i.e. the last registered cleanup function will be executed first). t.Cleanup(func() { - retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} + retrier := &retry.Timer{Timeout: 60 * time.Second, Wait: 100 * time.Millisecond} retry.RunWith(retrier, t, func(r *retry.R) { tokens, _, err := c.ConsulClient.ACL().TokenList(nil) require.NoError(r, err) @@ -158,17 +145,17 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-openshift-inject") } } else { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-server-inject") if c.Cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-tproxy") } else { - k8s.DeployKustomize(t, opts, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") + k8s.DeployKustomize(t, c.Ctx.KubectlOptions(t), c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, c.Cfg.DebugDirectory, "../fixtures/cases/static-client-inject") } } // Check that both static-server and static-client have been injected and // now have 2 containers. retry.RunWith( - &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond}, t, + &retry.Timer{Timeout: 30 * time.Second, Wait: 100 * time.Millisecond}, t, func(r *retry.R) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { podList, err := c.Ctx.KubernetesClient(t).CoreV1(). @@ -184,18 +171,6 @@ func (c *ConnectHelper) DeployClientAndServer(t *testing.T) { }) } -func (c *ConnectHelper) CreateNamespace(t *testing.T, namespace string) { - opts := c.Ctx.KubectlOptions(t) - _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", namespace) - if err != nil && strings.Contains(err.Error(), "AlreadyExists") { - return - } - require.NoError(t, err) - helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { - k8s.RunKubectl(t, opts, "delete", "ns", namespace) - }) -} - // DeployJob deploys a job pod to the Kubernetes // cluster which will be used to test service mesh connectivity. If the Secure // flag is true, a pre-check is done to ensure that the ACL tokens for the test @@ -282,7 +257,14 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { opts := c.KubectlOptsForApp(t) // If we are deploying apps in another namespace, create the namespace. - c.CreateNamespace(t, opts.Namespace) + _, err := k8s.RunKubectlAndGetOutputE(t, opts, "create", "ns", opts.Namespace) + if err != nil && strings.Contains(err.Error(), "AlreadyExists") { + return + } + require.NoError(t, err) + helpers.Cleanup(t, c.Cfg.NoCleanupOnFailure, c.Cfg.NoCleanup, func() { + k8s.RunKubectl(t, opts, "delete", "ns", opts.Namespace) + }) if c.Cfg.EnableRestrictedPSAEnforcement { // Allow anything to run in the app namespace. @@ -291,6 +273,7 @@ func (c *ConnectHelper) SetupAppNamespace(t *testing.T) { "pod-security.kubernetes.io/enforce-version=v1.24", ) } + } // CreateResolverRedirect creates a resolver that redirects to a static-server, a corresponding k8s service, @@ -308,17 +291,15 @@ func (c *ConnectHelper) CreateResolverRedirect(t *testing.T) { } // TestConnectionFailureWithoutIntention ensures the connection to the static -// server fails when no intentions are configured. When provided with a ClientType option -// the client is overridden, otherwise a default will be used. -func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, connHelperOpts ConnHelperOpts) { +// server fails when no intentions are configured. +func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, clientType ...string) { logger.Log(t, "checking that the connection is not successful because there's no intention") opts := c.KubectlOptsForApp(t) //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if connHelperOpts.ClientType != "" { - client = connHelperOpts.ClientType + if len(clientType) > 0 { + client = clientType[0] } - if c.Cfg.EnableTransparentProxy { k8s.CheckStaticServerConnectionFailing(t, opts, client, "http://static-server") } else { @@ -326,63 +307,38 @@ func (c *ConnectHelper) TestConnectionFailureWithoutIntention(t *testing.T, conn } } -type IntentionOpts struct { - ConnHelperOpts - SourceNamespace string - DestinationNamespace string -} - // CreateIntention creates an intention for the static-server pod to connect to -// the static-client pod. opts parameter allows for overriding of some fields. If opts is empty -// then all namespaces and clients use defaults. -func (c *ConnectHelper) CreateIntention(t *testing.T, opts IntentionOpts) { +// the static-client pod. +func (c *ConnectHelper) CreateIntention(t *testing.T, clientType ...string) { logger.Log(t, "creating intention") //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if opts.ClientType != "" { - client = opts.ClientType - } - - sourceNamespace := c.KubectlOptsForApp(t).Namespace - if opts.SourceNamespace != "" { - sourceNamespace = opts.SourceNamespace - } - - destinationNamespace := c.KubectlOptsForApp(t).Namespace - if opts.DestinationNamespace != "" { - destinationNamespace = opts.DestinationNamespace + if len(clientType) > 0 { + client = clientType[0] } - - retrier := &retry.Timer{Timeout: retryTimeout, Wait: 100 * time.Millisecond} - retry.RunWith(retrier, t, func(r *retry.R) { - _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: StaticServerName, - Namespace: destinationNamespace, - Sources: []*api.SourceIntention{ - { - Namespace: sourceNamespace, - Name: client, - Action: api.IntentionActionAllow, - }, + _, _, err := c.ConsulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ + Kind: api.ServiceIntentions, + Name: StaticServerName, + Sources: []*api.SourceIntention{ + { + Name: client, + Action: api.IntentionActionAllow, }, - }, nil) - require.NoError(r, err) - }) + }, + }, nil) + require.NoError(t, err) } // TestConnectionSuccess ensures the static-server pod can connect to the -// static-client pod once the intention is set. When provided with a ClientType option -// the client is overridden, otherwise a default will be used. -func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, connHelperOpts ConnHelperOpts) { +// static-client pod once the intention is set. +func (c *ConnectHelper) TestConnectionSuccess(t *testing.T, clientType ...string) { logger.Log(t, "checking that connection is successful") opts := c.KubectlOptsForApp(t) //Default to deploying static-client. If a client type is passed in (ex. job-client), use that instead. client := StaticClientName - if connHelperOpts.ClientType != "" { - client = connHelperOpts.ClientType + if len(clientType) > 0 { + client = clientType[0] } - if c.Cfg.EnableTransparentProxy { // todo: add an assertion that the traffic is going through the proxy k8s.CheckStaticServerConnectionSuccessful(t, opts, client, "http://static-server") @@ -401,7 +357,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { opts := c.KubectlOptsForApp(t) logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, // CheckStaticServerConnection will retry until Consul marks the service @@ -426,7 +382,7 @@ func (c *ConnectHelper) TestConnectionFailureWhenUnhealthy(t *testing.T) { } // Return the static-server to a "healthy state". - k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "-c", "static-server", "--", "rm", "/tmp/unhealthy") + k8s.RunKubectl(t, opts, "exec", "deploy/"+StaticServerName, "--", "rm", "/tmp/unhealthy") } // helmValues uses the Secure and AutoEncrypt fields to set values for the Helm diff --git a/acceptance/framework/consul/helm_cluster.go b/acceptance/framework/consul/helm_cluster.go index 492c7ec513..9e12df91e1 100644 --- a/acceptance/framework/consul/helm_cluster.go +++ b/acceptance/framework/consul/helm_cluster.go @@ -46,10 +46,6 @@ type HelmCluster struct { // if there are any previous installations of this Helm chart in the cluster. SkipCheckForPreviousInstallations bool - // ChartPath is an option field that allows consumers to change the default - // chart path if so desired - ChartPath string - ctx environment.TestContext helmOptions *helm.Options releaseName string @@ -68,10 +64,6 @@ func NewHelmCluster( cfg *config.TestConfig, releaseName string, ) *HelmCluster { - if cfg.EnableRestrictedPSAEnforcement { - configureNamespace(t, ctx.KubernetesClient(t), cfg, ctx.KubectlOptions(t).Namespace) - } - if cfg.EnablePodSecurityPolicies { configurePodSecurityPolicies(t, ctx.KubernetesClient(t), cfg, ctx.KubectlOptions(t).Namespace) } @@ -147,9 +139,7 @@ func (h *HelmCluster) Create(t *testing.T) { logger.Logf(t, "Unable to update helm repository, proceeding anyway: %s.", err) } } - if h.ChartPath != "" { - chartName = h.ChartPath - } + helm.Install(t, h.helmOptions, chartName, h.releaseName) k8s.WaitForAllPodsToBeReady(t, h.kubernetesClient, h.helmOptions.KubectlOptions.Namespace, fmt.Sprintf("release=%s", h.releaseName)) @@ -532,35 +522,6 @@ func createOrUpdateLicenseSecret(t *testing.T, client kubernetes.Interface, cfg CreateK8sSecret(t, client, cfg, namespace, config.LicenseSecretName, config.LicenseSecretKey, cfg.EnterpriseLicense) } -func configureNamespace(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) { - ctx := context.Background() - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - Labels: map[string]string{}, - }, - } - if cfg.EnableRestrictedPSAEnforcement { - ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce"] = "restricted" - ns.ObjectMeta.Labels["pod-security.kubernetes.io/enforce-version"] = "latest" - } - - _, createErr := client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if createErr == nil { - logger.Logf(t, "Created namespace %s", namespace) - return - } - - _, updateErr := client.CoreV1().Namespaces().Update(ctx, ns, metav1.UpdateOptions{}) - if updateErr == nil { - logger.Logf(t, "Updated namespace %s", namespace) - return - } - - require.Failf(t, "Failed to create or update namespace", "Namespace=%s, CreateError=%s, UpdateError=%s", namespace, createErr, updateErr) -} - // configureSCCs creates RoleBindings that bind the default service account to cluster roles // allowing access to the anyuid and privileged Security Context Constraints on OpenShift. func configureSCCs(t *testing.T, client kubernetes.Interface, cfg *config.TestConfig, namespace string) { diff --git a/acceptance/framework/flags/flags.go b/acceptance/framework/flags/flags.go index c68983fe8c..d16de7b5b1 100644 --- a/acceptance/framework/flags/flags.go +++ b/acceptance/framework/flags/flags.go @@ -35,13 +35,13 @@ type TestFlags struct { flagHelmChartVersion string flagConsulImage string flagConsulK8sImage string - flagConsulDataplaneImage string flagConsulVersion string - flagConsulDataplaneVersion string flagEnvoyImage string flagConsulCollectorImage string flagVaultHelmChartVersion string flagVaultServerVersion string + flagConsulDataplaneImage string + flagConsulDataplaneVersion string flagHCPResourceID string @@ -118,13 +118,13 @@ func (t *TestFlags) init() { flag.BoolVar(&t.flagEnableCNI, "enable-cni", false, "If true, the test suite will run tests with consul-cni plugin enabled. "+ "In general, this will only run against tests that are mesh related (connect, mesh-gateway, peering, etc") - flag.BoolVar(&t.flagEnableRestrictedPSAEnforcement, "enable-restricted-psa-enforcement", false, - "If true, deploy Consul into a namespace with restricted PSA enforcement enabled. "+ - "The Consul namespaces (-kube-namespaces) will be configured with restricted PSA enforcement. "+ - "The CNI and test applications are deployed in different namespaces because they need more privilege than is allowed in a restricted namespace. "+ - "The CNI will be deployed into the kube-system namespace, which is a privileged namespace that should always exist. "+ - "Test applications are deployed, by default, into a namespace named '-apps' instead of the Consul namespace.") + "If true, this indicates that Consul is being run in a namespace with restricted PSA enforcement enabled. "+ + "The tests do not configure Consul's namespace with PSA enforcement enabled. This must configured before tests are run. "+ + "The CNI and test applications need more privilege than is allowed in a restricted namespace. "+ + "When set, the CNI will be deployed into the kube-system namespace, and in supported test cases, applications "+ + "are deployed, by default, into a namespace named '-apps' instead of being deployed into the "+ + "Consul namespace.") flag.BoolVar(&t.flagEnableTransparentProxy, "enable-transparent-proxy", false, "If true, the test suite will run tests with transparent proxy enabled. "+ @@ -202,7 +202,6 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { kubeEnvs := config.NewKubeTestConfigList(t.flagKubeconfigs, t.flagKubecontexts, t.flagKubeNamespaces) c := &config.TestConfig{ - EnableEnterprise: t.flagEnableEnterprise, EnterpriseLicense: t.flagEnterpriseLicense, @@ -223,15 +222,14 @@ func (t *TestFlags) TestConfigFromFlags() *config.TestConfig { HelmChartVersion: t.flagHelmChartVersion, ConsulImage: t.flagConsulImage, ConsulK8SImage: t.flagConsulK8sImage, - ConsulDataplaneImage: t.flagConsulDataplaneImage, ConsulVersion: consulVersion, - ConsulDataplaneVersion: consulDataplaneVersion, EnvoyImage: t.flagEnvoyImage, ConsulCollectorImage: t.flagConsulCollectorImage, VaultHelmChartVersion: t.flagVaultHelmChartVersion, VaultServerVersion: t.flagVaultServerVersion, - - HCPResourceID: t.flagHCPResourceID, + ConsulDataplaneImage: t.flagConsulDataplaneImage, + ConsulDataplaneVersion: consulDataplaneVersion, + HCPResourceID: t.flagHCPResourceID, NoCleanupOnFailure: t.flagNoCleanupOnFailure, NoCleanup: t.flagNoCleanup, diff --git a/acceptance/framework/helpers/helpers.go b/acceptance/framework/helpers/helpers.go index f8b1d2f15d..6d2641ecd3 100644 --- a/acceptance/framework/helpers/helpers.go +++ b/acceptance/framework/helpers/helpers.go @@ -150,3 +150,38 @@ func MergeMaps(a, b map[string]string) { a[k] = v } } + +// RegisterExternalService registers an external service to a virtual node in Consul for testing purposes. +// This function takes a testing.T object, a Consul client, service namespace, service name, address, and port as +// parameters. It registers the service with Consul, and if a namespace is provided, it also creates the namespace +// in Consul. It uses the provided testing.T object to log registration details and verify the registration process. +// If the registration fails, the test calling the function will fail. +func RegisterExternalService(t *testing.T, consulClient *api.Client, namespace, name, address string, port int) { + t.Helper() + + service := &api.AgentService{ + ID: name, + Service: name, + Port: port, + } + + if namespace != "" { + address = fmt.Sprintf("%s.%s", name, namespace) + service.Namespace = namespace + + logger.Logf(t, "creating the %s namespace in Consul", namespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: namespace, + }, nil) + require.NoError(t, err) + } + + logger.Log(t, "registering the external service %s", name) + _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ + Node: "external", + Address: address, + NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, + Service: service, + }, nil) + require.NoError(t, err) +} diff --git a/acceptance/framework/k8s/kubectl.go b/acceptance/framework/k8s/kubectl.go index 2e37b9bd0a..acfedbdb3d 100644 --- a/acceptance/framework/k8s/kubectl.go +++ b/acceptance/framework/k8s/kubectl.go @@ -122,14 +122,6 @@ func KubectlScale(t *testing.T, options *k8s.KubectlOptions, deployment string, require.NoError(t, err) } -// KubectlLabel takes an object and applies the given label to it. -// Example: `KubectlLabel(t, options, "node", nodeId, corev1.LabelTopologyRegion, "us-east-1")`. -func KubectlLabel(t *testing.T, options *k8s.KubectlOptions, objectType string, objectId string, key string, value string) { - // `kubectl label` doesn't support timeouts - _, err := RunKubectlAndGetOutputE(t, options, "label", objectType, objectId, "--overwrite", fmt.Sprintf("%s=%s", key, value)) - require.NoError(t, err) -} - // RunKubectl runs an arbitrary kubectl command provided via args and ignores the output. // If there's an error running the command, fail the test. func RunKubectl(t *testing.T, options *k8s.KubectlOptions, args ...string) { diff --git a/acceptance/framework/vault/vault_cluster.go b/acceptance/framework/vault/vault_cluster.go index 8b82e41841..68e41eb238 100644 --- a/acceptance/framework/vault/vault_cluster.go +++ b/acceptance/framework/vault/vault_cluster.go @@ -6,8 +6,6 @@ package vault import ( "context" "fmt" - "os" - "strings" "testing" "time" @@ -15,7 +13,6 @@ import ( terratestk8s "github.com/gruntwork-io/terratest/modules/k8s" terratestLogger "github.com/gruntwork-io/terratest/modules/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/config" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" @@ -58,32 +55,12 @@ func NewVaultCluster(t *testing.T, ctx environment.TestContext, cfg *config.Test logger := terratestLogger.New(logger.TestLogger{}) kopts := ctx.KubectlOptions(t) - ns := ctx.KubectlOptions(t).Namespace - - entstr := "-ent" values := defaultHelmValues(releaseName) if cfg.EnablePodSecurityPolicies { values["global.psp.enable"] = "true" } - vaultReleaseName := helpers.RandomName() - k8sClient := environment.KubernetesClientFromOptions(t, ctx.KubectlOptions(t)) - vaultLicenseSecretName := fmt.Sprintf("%s-enterprise-license", vaultReleaseName) - vaultLicenseSecretKey := "license" - - vaultEnterpriseLicense := os.Getenv("VAULT_LICENSE") - if cfg.VaultServerVersion != "" { - - if strings.Contains(cfg.VaultServerVersion, entstr) { - - logger.Logf(t, "Creating secret for Vault license") - consul.CreateK8sSecret(t, k8sClient, cfg, ns, vaultLicenseSecretName, vaultLicenseSecretKey, vaultEnterpriseLicense) - - values["server.image.repository"] = "docker.mirror.hashicorp.services/hashicorp/vault-enterprise" - values["server.enterpriseLicense.secretName"] = vaultLicenseSecretName - values["server.enterpriseLicense.secretKey"] = vaultLicenseSecretKey - } values["server.image.tag"] = cfg.VaultServerVersion } vaultHelmChartVersion := defaultVaultHelmChartVersion diff --git a/acceptance/go.mod b/acceptance/go.mod index b34fcf246c..a243dd747b 100644 --- a/acceptance/go.mod +++ b/acceptance/go.mod @@ -4,17 +4,14 @@ go 1.20 require ( github.com/gruntwork-io/terratest v0.31.2 - github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 - github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 + github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230724205934-5b57e6340dff + github.com/hashicorp/consul/api v1.25.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/hcp-sdk-go v0.50.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 github.com/stretchr/testify v1.8.3 - go.opentelemetry.io/proto/otlp v1.0.0 - google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -27,7 +24,6 @@ require ( require ( github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-radix v1.0.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/aws/aws-sdk-go v1.44.262 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect @@ -43,18 +39,9 @@ require ( github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.21.4 // indirect - github.com/go-openapi/errors v0.20.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/loads v0.21.2 // indirect - github.com/go-openapi/runtime v0.25.0 // indirect - github.com/go-openapi/spec v0.20.8 // indirect - github.com/go-openapi/strfmt v0.21.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-openapi/validate v0.22.1 // indirect - github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -63,7 +50,6 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/gruntwork-io/gruntwork-cli v0.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.11 // indirect @@ -91,7 +77,6 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/miekg/dns v1.1.50 // indirect - github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect @@ -103,8 +88,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.0.0 // indirect - github.com/oklog/ulid v1.3.1 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pierrec/lz4 v2.5.2+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -115,18 +98,14 @@ require ( github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/urfave/cli v1.22.2 // indirect - go.mongodb.org/mongo-driver v1.11.0 // indirect - go.opentelemetry.io/otel v1.11.1 // indirect - go.opentelemetry.io/otel/trace v1.11.1 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect @@ -134,9 +113,9 @@ require ( golang.org/x/tools v0.7.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/acceptance/go.sum b/acceptance/go.sum index 219f7a08ec..f19c42177b 100644 --- a/acceptance/go.sum +++ b/acceptance/go.sum @@ -90,9 +90,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -210,88 +207,31 @@ github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= -github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc= -github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo= -github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc= -github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= -github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro= -github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw= -github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc= -github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= -github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o= -github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU= -github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= -github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -299,7 +239,6 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -331,7 +270,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -346,7 +284,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -384,16 +321,14 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/gruntwork-io/gruntwork-cli v0.7.0 h1:YgSAmfCj9c61H+zuvHwKfYUwlMhu5arnQQLM4RH+CYs= github.com/gruntwork-io/gruntwork-cli v0.7.0/go.mod h1:jp6Z7NcLF2avpY8v71fBx6hds9eOFPELSuD/VPv7w00= github.com/gruntwork-io/terratest v0.31.2 h1:xvYHA80MUq5kx670dM18HInewOrrQrAN+XbVVtytUHg= github.com/gruntwork-io/terratest v0.31.2/go.mod h1:EEgJie28gX/4AD71IFqgMj6e99KP5mi81hEtzmDjxTo= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892 h1:4iI0ztWbVPTSDax+m1/XDs4jIRorxY4kSMyuM0fX+Dc= -github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230609143603-198c4433d892/go.mod h1:iZ8BJGSnY52wnxJTo2VIfGX63CPjqiNzbuqdOtJCKnI= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968 h1:lQ7QmlL0N4/ftLBex8n73Raji29o7EVssqCoeeczKac= -github.com/hashicorp/consul/api v1.10.1-0.20230906155245-56917eb4c968/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230724205934-5b57e6340dff h1:E5o8N01LGheJfgXAbFgjXd37DgnT7MmfeUnmXFMgSuc= +github.com/hashicorp/consul-k8s/control-plane v0.0.0-20230724205934-5b57e6340dff/go.mod h1:stDdIOMKKlo8hZMViCPPNiLCNuYea2eQofHzOPZUz/o= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -448,8 +383,6 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcp-sdk-go v0.50.0 h1:vOUpVf4MQF/gtoBukuoYKs/i6KinTSpP5jhKCvsZ2bc= -github.com/hashicorp/hcp-sdk-go v0.50.0/go.mod h1:hZqky4HEzsKwvLOt4QJlZUrjeQmb4UCZUhDP2HyQFfc= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= @@ -478,7 +411,6 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -496,13 +428,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -510,7 +439,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -521,11 +450,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -556,8 +482,6 @@ github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -567,7 +491,6 @@ github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -585,7 +508,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -593,11 +515,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -612,14 +531,11 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/oracle/oci-go-sdk v7.1.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -668,8 +584,6 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= @@ -687,12 +601,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -718,7 +629,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -726,8 +636,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -737,14 +645,8 @@ github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -753,23 +655,11 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= -go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE= -go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= -go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= -go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= -go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= -go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -781,7 +671,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -789,10 +678,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -872,10 +759,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -889,13 +774,12 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -917,13 +801,10 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -961,7 +842,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -989,7 +869,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -1011,13 +890,9 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1126,11 +1001,8 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e h1:Ao9GzfUMPH3zjVfzXG5rlWlk+Q8MXWKwWpwVQE1MXfw= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= -google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e h1:NumxXLPfHSndr3wBBdeKiVHjGVFzi9RX2HwwQke94iY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1159,13 +1031,12 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -1191,9 +1062,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= @@ -1221,7 +1090,7 @@ k8s.io/client-go v0.26.3/go.mod h1:ZPNu9lm8/dbRIPAgteN30RSXea6vrCpFvq+MateTUuQ= k8s.io/cloud-provider v0.17.0/go.mod h1:Ze4c3w2C0bRsjkBUoHpFi+qWe3ob1wI2/7cUn+YQIDE= k8s.io/code-generator v0.0.0-20191121015212-c4c8f8345c7e/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= +k8s.io/component-base v0.26.3 h1:oC0WMK/ggcbGDTkdcqefI4wIZRYdK3JySx9/HADpV0g= k8s.io/csi-translation-lib v0.17.0/go.mod h1:HEF7MEz7pOLJCnxabi45IPkhSsE/KmxPQksuCrHKWls= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go index 89ba07a1e7..22faf55529 100644 --- a/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go +++ b/acceptance/tests/api-gateway/api_gateway_gatewayclassconfig_test.go @@ -63,7 +63,7 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { k8sClient := ctx.ControllerRuntimeClient(t) - // create a GatewayClassConfig with configuration set + // Create a GatewayClassConfig. gatewayClassConfigName := "gateway-class-config" gatewayClassConfig := &v1alpha1.GatewayClassConfig{ ObjectMeta: metav1.ObjectMeta{ @@ -127,7 +127,6 @@ func TestAPIGateway_GatewayClassConfig(t *testing.T) { gatewayName := "gcctestgateway" + namespace logger.Log(t, "creating controlled gateway") gateway := createGateway(t, k8sClient, gatewayName, namespace, gatewayClassName, certificateName) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { logger.Log(t, "deleting all gateways") k8sClient.DeleteAllOf(context.Background(), &gwv1beta1.Gateway{}, client.InNamespace(namespace)) diff --git a/acceptance/tests/api-gateway/api_gateway_test.go b/acceptance/tests/api-gateway/api_gateway_test.go index bbbaa569d4..721bbf2527 100644 --- a/acceptance/tests/api-gateway/api_gateway_test.go +++ b/acceptance/tests/api-gateway/api_gateway_test.go @@ -13,13 +13,12 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -127,7 +126,7 @@ func TestAPIGateway_Basic(t *testing.T) { // On startup, the controller can take upwards of 1m to perform // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the timeout here). + // the reconcile loop to run (hence the 1m timeout here). var gatewayAddress string counter := &retry.Counter{Count: 120, Wait: 2 * time.Second} retry.RunWith(counter, t, func(r *retry.R) { @@ -289,383 +288,6 @@ func TestAPIGateway_Basic(t *testing.T) { } } -const ( - // valid JWT token with role of "doctor". - doctorToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJkb2N0b3IifQ.FfgpzjMf8Evh6K-fJ1cLXklfIXOm-vojVbWlPPbGVFtzxZ9hxMxoyAY_G8i36SfGrpUlp-RJ6ohMvprMrEgyRgbenu7u5kkm5iGHW-zpMus4izXRxPELBcpWOGF105HIssT2NYRstXieNR8EVzvGfLdvR0GW8ttEERgseqGvuAfdb4-aNYsysGwUUHbsZjazA6H1rZmWqHdCLOJ2ZwFsIdckO9CadnkyTILpcPUmLYyUVJdtlLGOySb0GG8c_dPML_IR5jSXCSUZt6S2JBNBNBdqukrlqpA-fIaaWft0dbWVMhv8DqPC8znult8dKvLZ1qXeU0itsqqJUyE16ihJjw" - // valid JWT token with role of "pet". - petToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJQUzI1NiIsImtpZCI6IkMtRTFuQ2p3Z0JDLVB1R00yTzQ2N0ZSRGhLeDhBa1ZjdElTQWJvM3JpZXcifQ.eyJpc3MiOiJsb2NhbCIsInJvbGUiOiJwZXQifQ.l94rJayGGTMB426HwEw5ipSjaIHjm-UWDHiBAlB_Slmi814AxAfl_0AdRwSz67UDnkoygKbvPpR5xUB03JCXNshLZuKLegWsBeQg_OJYvZGmFagl5NglBFvH7Jbta4e1eQoAxZI6Xyy1jHbu7jFBjQPVnK8EaRvWoW8Pe8a8rp_5xhub0pomhvRF6Pm5kAS4cMnxvqpVc5Oo5nO7ws_SmoNnbt2Ok14k23Zx5E2EWmGStOfbgFsdbhVbepB2DMzqv1j8jvBbwa_OxCwc_7pEOthOOxRV6L3ZjgbRSB4GumlXAOCBYXD1cRLgrMSrWB1GkefAKu8PV0Ho1px6sI9Evg" -) - -func TestAPIGateway_JWTAuth_Basic(t *testing.T) { - ctx := suite.Environment().DefaultContext(t) - cfg := suite.Config() - - if !cfg.EnableEnterprise { - t.Skipf("skipping this test because -enable-enterprise is not set") - } - - helmValues := map[string]string{ - "connectInject.enabled": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - "global.acls.manageSystemACLs": "true", - "global.tls.enabled": "true", - "global.logLevel": "trace", - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - // this is necesary when running tests with ACLs enabled - runTestsAsSecure := true - // Override the default proxy config settings for this test - consulClient, _ := consulCluster.SetupConsulClient(t, runTestsAsSecure) - _, _, err := consulClient.ConfigEntries().Set(&api.ProxyConfigEntry{ - Kind: api.ProxyDefaults, - Name: api.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, nil) - require.NoError(t, err) - - logger.Log(t, "creating other namespace") - out, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "create", "namespace", "other") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "namespace", "other") - }) - - logger.Log(t, "creating api-gateway resources") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth") - }) - - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-n", "other", "-f", "../fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml") - }) - - logger.Log(t, "try (and fail) to add a second gateway policy to the gateway") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") - require.Error(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-k", "../fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy") - }) - - // Create certificate secret, we do this separately since - // applying the secret will make an invalid certificate that breaks other tests - logger.Log(t, "creating certificate secret") - out, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "apply", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - require.NoError(t, err, out) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - // Ignore errors here because if the test ran as expected - // the custom resources will have been deleted. - k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "delete", "-f", "../fixtures/bases/api-gateway/certificate.yaml") - }) - - // patch certificate with data - logger.Log(t, "patching certificate secret with generated data") - certificate := generateCertificate(t, nil, "gateway.test.local") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "patch", "secret", "certificate", "-p", fmt.Sprintf(`{"data":{"tls.crt":"%s","tls.key":"%s"}}`, base64.StdEncoding.EncodeToString(certificate.CertPEM), base64.StdEncoding.EncodeToString(certificate.PrivateKeyPEM)), "--type=merge") - - // We use the static-client pod so that we can make calls to the api gateway - // via kubectl exec without needing a route into the cluster from the test machine. - logger.Log(t, "creating static-client pod") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") - - k8s.RunKubectl(t, ctx.KubectlOptions(t), "wait", "--for=condition=available", "--timeout=5m", fmt.Sprintf("deploy/%s", "static-server")) - // Grab a kubernetes client so that we can verify binding - // behavior prior to issuing requests through the gateway. - k8sClient := ctx.ControllerRuntimeClient(t) - - // On startup, the controller can take upwards of 1m to perform - // leader election so we may need to wait a long time for - // the reconcile loop to run (hence the 2m timeout here). - var ( - gatewayAddress string - gatewayClass gwv1beta1.GatewayClass - httpRoute gwv1beta1.HTTPRoute - httpRouteAuth gwv1beta1.HTTPRoute - httpRouteAuth2 gwv1beta1.HTTPRoute - httpRouteNoAuthOnAuthListener gwv1beta1.HTTPRoute - httpRouteInvalid gwv1beta1.HTTPRoute - ) - - counter := &retry.Counter{Count: 60, Wait: 2 * time.Second} - retry.RunWith(counter, t, func(r *retry.R) { - var gateway gwv1beta1.Gateway - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway", Namespace: "default"}, &gateway) - require.NoError(r, err) - - // check our finalizers - require.Len(r, gateway.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, gateway.Finalizers[0]) - - // check our statuses - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Conditions, trueCondition("ConsulAccepted", "Accepted")) - require.Len(r, gateway.Status.Listeners, 5) - - require.EqualValues(r, int32(3), gateway.Status.Listeners[0].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - require.EqualValues(r, int32(1), gateway.Status.Listeners[1].AttachedRoutes) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, falseCondition("Conflicted", "NoConflicts")) - checkStatusCondition(r, gateway.Status.Listeners[1].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - - // check that we have an address to use - require.Len(r, gateway.Status.Addresses, 1) - // now we know we have an address, set it so we can use it - gatewayAddress = gateway.Status.Addresses[0].Value - - // gateway class checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "gateway-class"}, &gatewayClass) - require.NoError(r, err) - - // check our finalizers - require.Len(r, gatewayClass.Finalizers, 1) - require.EqualValues(r, gatewayClassFinalizer, gatewayClass.Finalizers[0]) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route", Namespace: "default"}, &httpRoute) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth", Namespace: "default"}, &httpRouteAuth) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-no-auth-on-auth-listener", Namespace: "default"}, &httpRouteNoAuthOnAuthListener) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route2-auth", Namespace: "default"}, &httpRouteAuth2) - require.NoError(r, err) - - // http route checks - err = k8sClient.Get(context.Background(), types.NamespacedName{Name: "http-route-auth-invalid", Namespace: "default"}, &httpRouteInvalid) - require.NoError(r, err) - - // check our finalizers - require.Len(r, httpRoute.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRoute.Finalizers[0]) - - // check parent status - require.Len(r, httpRoute.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRoute.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRoute.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRoute.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteAuth.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteAuth.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteAuth.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteAuth.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteAuth.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteAuth.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteNoAuthOnAuthListener.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteNoAuthOnAuthListener.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteNoAuthOnAuthListener.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteNoAuthOnAuthListener.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteNoAuthOnAuthListener.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteNoAuthOnAuthListener.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check our finalizers - require.Len(r, httpRouteAuth2.Finalizers, 1) - require.EqualValues(r, gatewayFinalizer, httpRouteAuth2.Finalizers[0]) - - // check parent status - require.Len(r, httpRouteAuth2.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteAuth2.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteAuth2.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("Accepted", "Accepted")) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteAuth2.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - - // check parent status - require.Len(r, httpRouteInvalid.Status.Parents, 1) - require.EqualValues(r, gatewayClassControllerName, httpRouteInvalid.Status.Parents[0].ControllerName) - require.EqualValues(r, "gateway", httpRouteInvalid.Status.Parents[0].ParentRef.Name) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, falseCondition("Accepted", "FilterNotFound")) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ResolvedRefs", "ResolvedRefs")) - checkStatusCondition(r, httpRouteInvalid.Status.Parents[0].Conditions, trueCondition("ConsulAccepted", "Accepted")) - }) - - // check that the Consul entries were created - entry, _, err := consulClient.ConfigEntries().Get(api.APIGateway, "gateway", nil) - require.NoError(t, err) - gateway := entry.(*api.APIGatewayConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route", nil) - require.NoError(t, err) - consulHTTPRoute := entry.(*api.HTTPRouteConfigEntry) - - entry, _, err = consulClient.ConfigEntries().Get(api.HTTPRoute, "http-route-auth", nil) - require.NoError(t, err) - consulHTTPRouteAuth := entry.(*api.HTTPRouteConfigEntry) - - // now check the gateway status conditions - checkConsulStatusCondition(t, gateway.Status.Conditions, trueConsulCondition("Accepted", "Accepted")) - - // and the route status conditions - checkConsulStatusCondition(t, consulHTTPRoute.Status.Conditions, trueConsulCondition("Bound", "Bound")) - checkConsulStatusCondition(t, consulHTTPRouteAuth.Status.Conditions, trueConsulCondition("Bound", "Bound")) - - // finally we check that we can actually route to the service(s) via the gateway - k8sOptions := ctx.KubectlOptions(t) - targetHTTPAddress := fmt.Sprintf("http://%s/v1", gatewayAddress) - targetHTTPAddressAdmin := fmt.Sprintf("http://%s:8081/admin", gatewayAddress) - targetHTTPAddressPet := fmt.Sprintf("http://%s:8081/pet", gatewayAddress) - targetHTTPAddressAdmin2 := fmt.Sprintf("http://%s:8081/admin-2", gatewayAddress) - targetHTTPAddressPet2 := fmt.Sprintf("http://%s:8081/pet-2", gatewayAddress) - targetHTTPAddressAdminNoAuthOnRoute := fmt.Sprintf("http://%s:8081/admin-no-auth", gatewayAddress) - targetHTTPAddressPetNotAuthOnRoute := fmt.Sprintf("http://%s:8081/pet-no-auth", gatewayAddress) - - // Now we create the allow intention. - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - _, _, err = consulClient.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ - Kind: api.ServiceIntentions, - Name: "static-server-protected", - Sources: []*api.SourceIntention{ - { - Name: "gateway", - Action: api.IntentionActionAllow, - }, - }, - }, nil) - require.NoError(t, err) - - // Test that we can make a call to the api gateway - logger.Log(t, "trying calls to api gateway http") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, targetHTTPAddress) - - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that a "role" of "doctor" - // we can see that: - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin) - - // ensure that overrides -> route extension -> default by making a request to the admin route with a JWT that has a "role" of "pet" - // the route does not define - // we can see that: - // * the "role" verification in the gateway default is used - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet) - - // ensure that routes attached to the same gateway don't cause changes in another route - // * the verification on the gateway is the only one used as this route does not define any JWT configuration - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet-no-auth should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPetNotAuthOnRoute) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet-no-auth should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPetNotAuthOnRoute) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet-no-auth should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPetNotAuthOnRoute) - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin-no-auth should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdminNoAuthOnRoute) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin-no-auth should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdminNoAuthOnRoute) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin-no-auth should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdminNoAuthOnRoute) - - // multiple routes can use the same external ref - // we can see that: - // * the "role" verification in the route extension takes precedence over the "role" verification in the gateway default - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /admin-2 should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressAdmin2) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /admin-2 should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressAdmin2) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /admin-2 should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressAdmin2) - - // should fail because we're missing JWT - logger.Log(t, "trying calls to api gateway /pet-2 should fail without JWT token") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, targetHTTPAddressPet2) - - // should fail because we use the token with the wrong role and correct issuer - logger.Log(t, "trying calls to api gateway /pet-2 should fail with wrong role") - k8s.CheckStaticServerHTTPConnectionFailing(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", doctorToken), targetHTTPAddressPet2) - - // will succeed because we use the token with the correct role and the correct issuer - logger.Log(t, "trying calls to api gateway /pet-2 should succeed with JWT token with correct role") - k8s.CheckStaticServerConnectionSuccessful(t, k8sOptions, StaticClientName, "-H", fmt.Sprintf("Authorization: Bearer %s", petToken), targetHTTPAddressPet2) -} - func checkStatusCondition(t require.TestingT, conditions []metav1.Condition, toCheck metav1.Condition) { for _, c := range conditions { if c.Type == toCheck.Type { diff --git a/acceptance/tests/cli/cli_install_test.go b/acceptance/tests/cli/cli_install_test.go index c8b66ff451..eacccb1fdc 100644 --- a/acceptance/tests/cli/cli_install_test.go +++ b/acceptance/tests/cli/cli_install_test.go @@ -52,8 +52,8 @@ func TestInstall(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } // Run proxy list and get the two results. @@ -121,7 +121,7 @@ func TestInstall(t *testing.T) { logger.Log(t, string(proxyOut)) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } diff --git a/acceptance/tests/cloud/observability_test.go b/acceptance/tests/cloud/basic_test.go similarity index 63% rename from acceptance/tests/cloud/observability_test.go rename to acceptance/tests/cloud/basic_test.go index 5a2bf7b365..7858450904 100644 --- a/acceptance/tests/cloud/observability_test.go +++ b/acceptance/tests/cloud/basic_test.go @@ -4,6 +4,12 @@ package cloud import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" "strings" "testing" "time" @@ -18,6 +24,10 @@ import ( "github.com/stretchr/testify/require" ) +type TokenResponse struct { + Token string `json:"token"` +} + var ( resourceSecretName = "resource-sec-name" resourceSecretKey = "resource-sec-key" @@ -44,7 +54,47 @@ var ( scadaAddressSecretKeyValue = "fake-server:443" ) -func TestObservabilityCloud(t *testing.T) { +// The fake-server has a requestToken endpoint to retrieve the token. +func requestToken(endpoint string) (string, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{Transport: tr} + url := fmt.Sprintf("https://%s/token", endpoint) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + fmt.Println("Error creating request:", err) + return "", errors.New("error creating request") + } + + // Perform the request + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return "", errors.New("error making request") + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response:", err) + return "", errors.New("error reading body") + } + + var tokenResponse TokenResponse + err = json.Unmarshal(body, &tokenResponse) + if err != nil { + fmt.Println("Error parsing response:", err) + return "", errors.New("error parsing body") + } + + return tokenResponse.Token, nil + +} + +func TestBasicCloud(t *testing.T) { ctx := suite.Environment().DefaultContext(t) kubectlOptions := ctx.KubectlOptions(t) @@ -79,7 +129,7 @@ func TestObservabilityCloud(t *testing.T) { localPort, 443, logger.TestLogger{}) - defer tunnel.Close() + // Retry creating the port forward since it can fail occasionally. retry.RunWith(&retry.Counter{Wait: 5 * time.Second, Count: 60}, t, func(r *retry.R) { // NOTE: It's okay to pass in `t` to ForwardPortE despite being in a retry @@ -88,20 +138,18 @@ func TestObservabilityCloud(t *testing.T) { require.NoError(r, tunnel.ForwardPortE(t)) }) - fsClient := newfakeServerClient(tunnel.Endpoint()) logger.Log(t, "fake-server addr:"+tunnel.Endpoint()) - consulToken, err := fsClient.requestToken() + consulToken, err := requestToken(tunnel.Endpoint()) if err != nil { logger.Log(t, "error finding consul token") return } - + tunnel.Close() logger.Log(t, "consul test token :"+consulToken) releaseName := helpers.RandomName() helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", "global.cloud.enabled": "true", "global.cloud.resourceId.secretName": resourceSecretName, "global.cloud.resourceId.secretKey": resourceSecretKey, @@ -122,6 +170,13 @@ func TestObservabilityCloud(t *testing.T) { "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, "connectInject.default": "true", + // TODO: Follow up with this bug + "global.acls.manageSystemACLs": "false", + "global.gossipEncryption.autoGenerate": "false", + "global.tls.enabled": "true", + "global.tls.enableAutoEncrypt": "true", + // TODO: Take this out + "telemetryCollector.enabled": "true", "telemetryCollector.image": cfg.ConsulCollectorImage, "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, @@ -129,6 +184,8 @@ func TestObservabilityCloud(t *testing.T) { "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, + // Either we set the global.trustedCAs (make sure it's idented exactly) or we + // set TLS to insecure "telemetryCollector.extraEnvironmentVars.HCP_API_TLS": "insecure", "telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", @@ -138,85 +195,39 @@ func TestObservabilityCloud(t *testing.T) { "server.extraEnvironmentVars.HCP_API_TLS": "insecure", "server.extraEnvironmentVars.HCP_AUTH_TLS": "insecure", "server.extraEnvironmentVars.HCP_SCADA_TLS": "insecure", + + // This is pregenerated CA used for testing. It can be replaced at any time and isn't + // meant for anything other than testing + // "global.trustedCAs[0]": `-----BEGIN CERTIFICATE----- + // MIICrjCCAZYCCQD5LxMcnMY8rDANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5m + // YWtlLXNlcnZlci1jYTAeFw0yMzA1MTkxMjIwMzhaFw0zMzA1MTYxMjIwMzhaMBkx + // FzAVBgNVBAMMDmZha2Utc2VydmVyLWNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + // MIIBCgKCAQEAwhbiII7sMultedFzQVhVZz5Ti+9lWrpZb8y0ZR6NaNvoxDPX151t + // Adh5NegSeH/+351iDBGZHhmKECtBuk8FJgk88O7y8A7Yg+/lyeZd0SJTEeiYUe7d + // sSaBTYSmixyn6s15Y5MVp9gM7t2YXrocRkFxDtdhLMWf0zwzJEwDouFMMiFZw5II + // yDbI6UfwKyB8C8ln10+TcczbheaOMQ1jGn35YWAG/LEdutU6DO2Y/GZYQ41nyLF1 + // klqh34USQPVQSQW7R7GiDxyhh1fGaDF6RAzH4RerzQSNvvTHmBXIGurB/Hnu1n3p + // CwWeatWMU5POy1es73S/EPM0NpWD5RabSwIDAQABMA0GCSqGSIb3DQEBCwUAA4IB + // AQBayoTltSW55PvKVp9cmqGOBMlkIMKPd6Ny4bCb/3UF+3bzQmIblh3O3kEt7WoY + // fA9vp+6cSRGVqgBfR2bi40RrerLNA79yywIZjfBMteNuRoul5VeD+mLyFCo4197r + // Atl2TEx2kl2V8rjCsEBcTqKqetVOMLYEZ2tbCeUt1A/K7OzaJfHgelEYcsVt68Q9 + // /BLoo2UXfOpRrcsx7u7s5HPVbG3bx+1MvGJZ2C3i0B6agnkGDzEpoM4KZGxEefB9 + // DOHIJfie9d9BQD52nZh3SGHz0b3vfJ430XrQmaNZ26fuIEyIYrpvyAhBXckj2iTD + // 1TXpqr/1D7EUbddktyhXTK9e + // -----END CERTIFICATE-----`, } if cfg.ConsulImage != "" { helmValues["global.image"] = cfg.ConsulImage } + if cfg.ConsulCollectorImage != "" { + helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage + } consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), suite.Config(), releaseName) consulCluster.Create(t) logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - t.Log("Finished deployment. Validating expected conditions now") - - for name, tc := range map[string]struct { - refresh *modifyTelemetryConfigBody - refreshTime int64 - recordsPath string - timeout time.Duration - wait time.Duration - validations *metricValidations - }{ - "collectorExportsMetrics": { - recordsPath: recordsPathCollector, - // High timeout as Collector metrics scraped every 1 minute (https://github.com/hashicorp/consul-telemetry-collector/blob/dfdbf51b91d502a18f3b143a94ab4d50cdff10b8/internal/otel/config/helpers/receivers/prometheus_receiver.go#L54) - timeout: 5 * time.Minute, - wait: 1 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"service_name", "service_instance_id"}, - expectedMetricName: "otelcol_receiver_accepted_metric_points", - disallowedMetricName: "server.memory_heap_size", - }, - }, - "consulPeriodicRefreshUpdateConfig": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 30 * time.Second, - validations: &metricValidations{ - expectedLabelKeys: []string{"node_id", "node_name", "new_label"}, - expectedMetricName: "consul.state.services", - disallowedMetricName: "consul.fsm", - }, - }, - "consulPeriodicRefreshDisabled": { - refresh: &modifyTelemetryConfigBody{ - Filters: []string{"consul.state"}, - Labels: map[string]string{"new_label": "testLabel"}, - Disabled: true, - }, - recordsPath: recordsPathConsul, - // High timeout as Consul server metrics exported every 1 minute (https://github.com/hashicorp/consul/blob/9776c10efb4472f196b47f88bc0db58b1bfa12ef/agent/hcp/telemetry/otel_sink.go#L27) - timeout: 3 * time.Minute, - wait: 30 * time.Second, - validations: &metricValidations{ - disabled: true, - }, - }, - } { - t.Run(name, func(t *testing.T) { - // For a refresh test, we force a telemetry config update before validating metrics using fakeserver's /telemetry_config_modify endpoint. - if tc.refresh != nil { - refreshTime := time.Now() - err := fsClient.modifyTelemetryConfig(tc.refresh) - require.NoError(t, err) - // Add 10 seconds (2 * periodic refresh interval in fakeserver) to allow a periodic refresh from Consul side to take place. - tc.refreshTime = refreshTime.Add(10 * time.Second).UnixNano() - } - - // Validate metrics are correct using fakeserver's /records endpoint to retrieve metric exports that occured from Consul/Collector to fakeserver. - // We use retry as we wait for Consul or the Collector to export metrics. This is the best we can do to avoid flakiness. - retry.RunWith(&retry.Timer{Timeout: tc.timeout, Wait: tc.wait}, t, func(r *retry.R) { - records, err := fsClient.getRecordsForPath(tc.recordsPath, tc.refreshTime) - require.NoError(r, err) - validateMetrics(r, records, tc.validations, tc.refreshTime) - }) - }) - } + // time.Sleep(1 * time.Hour) + // TODO: add in test assertions here } diff --git a/acceptance/tests/cloud/fakeserver_client.go b/acceptance/tests/cloud/fakeserver_client.go deleted file mode 100644 index ec668d16e5..0000000000 --- a/acceptance/tests/cloud/fakeserver_client.go +++ /dev/null @@ -1,158 +0,0 @@ -package cloud - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strconv" -) - -const ( - // recordsPathConsul and recordsPathCollector distinguish metrics for consul vs. collector when fetching records. - recordsPathConsul = "v1/metrics/consul" - recordsPathCollector = "v1/metrics/collector" -) - -var ( - errEncodingPayload = errors.New("failed to encode payload") - errCreatingRequest = errors.New("failed to create HTTP request") - errMakingRequest = errors.New("failed to make request") - errReadingBody = errors.New("failed to read body") - errClosingBody = errors.New("failed to close body") - errParsingBody = errors.New("failed to parse body") -) - -// fakeServerClient provides an interface to communicate with the fakesever (a fake HCP Telemetry Gateway) via HTTP. -type fakeServerClient struct { - client *http.Client - tunnel string -} - -// modifyTelemetryConfigBody is a POST body that provides telemetry config changes to the fakeserver. -type modifyTelemetryConfigBody struct { - Filters []string `json:"filters"` - Labels map[string]string `json:"labels"` - Disabled bool `json:"disabled"` -} - -// TokenResponse is used to read a token response from the fakeserver. -type TokenResponse struct { - Token string `json:"token"` -} - -// RecordsResponse is used to read a /records response from the fakeserver. -type RecordsResponse struct { - Records []*RequestRecord `json:"records"` -} - -// RequestRecord holds info about a single request. -type RequestRecord struct { - Method string `json:"method"` - Path string `json:"path"` - Body []byte `json:"body"` - ValidRequest bool `json:"validRequest"` - Timestamp int64 `json:"timestamp"` -} - -// newfakeServerClient returns a fakeServerClient to be used in tests to communicate with the fake Telemetry Gateway. -func newfakeServerClient(tunnel string) *fakeServerClient { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - return &fakeServerClient{ - client: &http.Client{Transport: tr}, - tunnel: tunnel, - } -} - -// requestToken retrieves a token from the fakeserver's token endpoint. -func (f *fakeServerClient) requestToken() (string, error) { - url := fmt.Sprintf("https://%s/token", f.tunnel) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return "", fmt.Errorf("%w: %w", errCreatingRequest, err) - } - - resp, err := f.handleRequest(req) - if err != nil { - return "", err - } - - tokenResponse := &TokenResponse{} - err = json.Unmarshal(resp, tokenResponse) - if err != nil { - return "", fmt.Errorf("%w : %w", errParsingBody, err) - } - - return tokenResponse.Token, nil -} - -// modifyTelemetryConfig can update the telemetry config returned by the fakeserver. -// via the fakeserver's modify_telemetry_config endpoint. -func (f *fakeServerClient) modifyTelemetryConfig(payload *modifyTelemetryConfigBody) error { - url := fmt.Sprintf("https://%s/modify_telemetry_config", f.tunnel) - payloadBuf := new(bytes.Buffer) - - err := json.NewEncoder(payloadBuf).Encode(payload) - if err != nil { - return fmt.Errorf("%w:%w", errEncodingPayload, err) - } - - req, err := http.NewRequest("POST", url, payloadBuf) - if err != nil { - return fmt.Errorf("%w: %w", errCreatingRequest, err) - } - - _, err = f.handleRequest(req) - - return err -} - -func (f *fakeServerClient) getRecordsForPath(path string, refreshTime int64) ([]*RequestRecord, error) { - url := fmt.Sprintf("https://%s/records/%s", f.tunnel, path) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, fmt.Errorf("%w: %w", errCreatingRequest, err) - } - if refreshTime > 0 { - q := req.URL.Query() - q.Add("since", strconv.FormatInt(refreshTime, 10)) - req.URL.RawQuery = q.Encode() - } - - resp, err := f.handleRequest(req) - if err != nil { - return nil, err - } - - recordsResponse := &RecordsResponse{} - err = json.Unmarshal(resp, recordsResponse) - if err != nil { - return nil, fmt.Errorf("%w : %w", errParsingBody, err) - } - - return recordsResponse.Records, nil -} - -// handleRequest returns the response body if the request is succesful. -func (f *fakeServerClient) handleRequest(req *http.Request) ([]byte, error) { - resp, err := f.client.Do(req) - if err != nil { - return nil, fmt.Errorf("%w : %w", errMakingRequest, err) - } - body, err := io.ReadAll(resp.Body) - cErr := resp.Body.Close() - if cErr != nil { - return nil, fmt.Errorf("%w : %w", errClosingBody, err) - } - if err != nil { - return nil, fmt.Errorf("%w : %w", errReadingBody, err) - } - - return body, nil -} diff --git a/acceptance/tests/cloud/load/main_test.go b/acceptance/tests/cloud/load/main_test.go deleted file mode 100644 index 4629e0d4d0..0000000000 --- a/acceptance/tests/cloud/load/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/cloud/load/remote.go b/acceptance/tests/cloud/load/remote.go deleted file mode 100644 index 5ce2ffa286..0000000000 --- a/acceptance/tests/cloud/load/remote.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "fmt" - "strings" -) - -// GetDomainSuffix gets the suffix. -func GetDomainSuffix(env string) (string, error) { - suffix, ok := map[string]string{ - "dev-remote": "hcp.dev", - "dev": "hcp.dev", - "int": "hcp.to", - "prod": "hashicorp.cloud", - }[strings.ToLower(env)] - - if !ok { - return "", fmt.Errorf("unrecognized env: %s", env) - } - return suffix, nil -} - -// GetAPIAddr returns the address of HCP given the passed environment. -func GetAPIAddr(env string) (string, error) { - suffix, ok := map[string]string{ - "local": "http://127.0.0.1:28081", - "dev-remote": "https://api.hcp.dev", - "dev": "https://api.hcp.dev", - "int": "https://api.hcp.to", - "prod": "https://api.hashicorp.cloud", - }[strings.ToLower(env)] - - if !ok { - return "", fmt.Errorf("unrecognized env: %s", env) - } - return suffix, nil -} - -// GetAuthIDP returns the authidp for the env. -func GetAuthIDP(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("https://auth.idp.%s", suffix), nil -} - -// GetScadaAddr returns the scadara for the env. -func GetScadaAddr(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("https://scada.internal.%s:7224", suffix), nil -} - -// GetScadaAddr returns the scadara for the env. -func GetScadaAddrWithoutProtocol(env string) (string, error) { - suffix, err := GetDomainSuffix(env) - if err != nil { - return "", fmt.Errorf("unrecognized env: %w", err) - } - - return fmt.Sprintf("scada.internal.%s:7224", suffix), nil -} diff --git a/acceptance/tests/cloud/load/remote_load_test.go b/acceptance/tests/cloud/load/remote_load_test.go deleted file mode 100644 index 2ca8e1b814..0000000000 --- a/acceptance/tests/cloud/load/remote_load_test.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package load - -import ( - "crypto/tls" - "encoding/json" - "os" - "strconv" - "testing" - "text/template" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/httpclient" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -type DevTokenResponse struct { - Token string `json:"token"` -} - -type hcp struct { - ResourceID string - ClientID string - ClientSecret string - AuthURL string - APIHostname string - ScadaAddress string -} - -func TestLoadTestCTGW(t *testing.T) { - _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") - _, cIDok := os.LookupEnv("HCP_CLIENT_ID") - _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") - - if !rIDok || !cIDok || !cSECok { - t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") - t.FailNow() - } - - apiHost := os.Getenv("HCP_AUTH_URL") - if apiHost == "" { - apiHost = "https://api.hcp.dev" - } - authURL := os.Getenv("HCP_API_HOST") - if authURL == "" { - authURL = "https://auth.idp.hcp.dev" - } - scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") - if scadaAddr == "" { - scadaAddr = "scada.internal.hcp.dev:7224" - } - - env := os.Getenv("HCP_ENV") - var err error - if env != "" { - apiHost, err = GetAPIAddr(env) - require.NoError(t, err) - authURL, err = GetAuthIDP(env) - require.NoError(t, err) - scadaAddr, err = GetScadaAddrWithoutProtocol(env) - require.NoError(t, err) - } - - ctx := suite.Environment().DefaultContext(t) - - kubectlOptions := ctx.KubectlOptions(t) - ns := kubectlOptions.Namespace - k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) - - var ( - resourceSecretName = "resource-sec-name" - resourceSecretKey = "resource-sec-key" - resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") - - clientIDSecretName = "clientid-sec-name" - clientIDSecretKey = "clientid-sec-key" - clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") - - clientSecretName = "client-sec-name" - clientSecretKey = "client-sec-key" - clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") - - apiHostSecretName = "apihost-sec-name" - apiHostSecretKey = "apihost-sec-key" - apiHostSecretKeyValue = apiHost - - authUrlSecretName = "authurl-sec-name" - authUrlSecretKey = "authurl-sec-key" - authUrlSecretKeyValue = authURL - - scadaAddressSecretName = "scadaaddress-sec-name" - scadaAddressSecretKey = "scadaaddress-sec-key" - scadaAddressSecretKeyValue = scadaAddr - - bootstrapTokenSecretName = "bootstrap-token" - bootstrapTokenSecretKey = "token" - ) - - aclToken := os.Getenv("HCP_CONSUL_TOKEN") - - // This should never happen during the load test since we should have already controlled for it. - if aclToken == "" { - hcpCfg := hcp{ - ResourceID: resourceSecretKeyValue, - ClientID: clientIDSecretKeyValue, - ClientSecret: clientSecretKeyValue, - AuthURL: authUrlSecretKeyValue, - APIHostname: apiHostSecretKeyValue, - ScadaAddress: scadaAddressSecretKeyValue, - } - - aclToken = hcpCfg.fetchAgentBootstrapConfig(t) - } - - cfg := suite.Config() - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, - - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, - - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, - - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, - - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, - - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", - - "global.acls.manageSystemACLs": "true", - "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, - "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, - - "global.gossipEncryption.autoGenerate": "false", - "global.tls.enabled": "true", - "global.tls.enableAutoEncrypt": "true", - - "global.metrics.enableTelemetryCollector": "true", - "server.replicas": "3", - "server.affinity": "null", - - "global.acls.resources.requests.memory": "15Mi", - "global.acls.resources.requests.cpu": "5m", - "global.acls.resources.limits.memory": "15Mi", - "global.acls.resources.limits.cpu": "5m", - - "connectInject.initContainer.resources.requests.memory": "15Mi", - "connectInject.initContainer.resources.requests.cpu": "5m", - - "connectInject.initContainer.resources.limits.memory": "15Mi", - "connectInject.initContainer.resources.limits.cpu": "5m", - - "telemetryCollector.enabled": "true", - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, - - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - "telemetryCollector.resources.requests.cpu": "100m", - "telemetryCollector.resources.limits.cpu": "100m", - - // Either we set the global.trustedCAs (make sure it's idented exactly) or we - // set TLS to insecure - - "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, - } - - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } - if cfg.ConsulCollectorImage != "" { - helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage - } - - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) - consulCluster.ChartPath = "../../../../charts/consul" - consulCluster.Create(t) - - logger.Log(t, "setting acl permissions for collector and services") - aclDir := "../../fixtures/bases/cloud/service-intentions" - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) - }) - - logger.Log(t, "creating static-server deployment") - nServices := 2 - nServicesStr := os.Getenv("LOAD_TEST_RUNS") - if nServicesStr != "" { - i, err := strconv.Atoi(nServicesStr) - require.NoError(t, err) - nServices = i - } - - tmpl, err := template.ParseFiles("../../fixtures/bases/pingpong/template.tmpl") - if err != nil { - require.NoError(t, err) - } - - for i := 0; i < nServices; i++ { - data := &tmplData{Iteration: i, Replicas: 1} - tmplIt, err := tmpl.Clone() - require.NoError(t, err) - - // Create a temporary file - tempFile, err := os.CreateTemp("", "temp*.yaml") - if err != nil { - require.NoError(t, err) - } - - err = tmplIt.Execute(tempFile, data) - require.NoError(t, err) - // Close the temporary file to ensure that changes are saved - err = tempFile.Close() - require.NoError(t, err) - - k8s.KubectlApply(t, ctx.KubectlOptions(t), tempFile.Name()) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDelete(t, ctx.KubectlOptions(t), tempFile.Name()) - os.Remove(tempFile.Name()) - }) - } -} - -type tmplData struct { - Iteration int - Replicas int -} - -// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret -// to call to the agent bootstrap config endpoint and parse the response into a -// CloudBootstrapConfig struct. -func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { - cfg, err := c.HCPConfig() - require.NoError(t, err) - logger.Log(t, "Fetching Consul cluster configuration from HCP") - httpClientCfg := httpclient.Config{ - HCPConfig: cfg, - } - clientRuntime, err := httpclient.New(httpClientCfg) - require.NoError(t, err) - - hcpgnmClient := hcpgnm.New(clientRuntime, nil) - clusterResource, err := resource.FromString(c.ResourceID) - require.NoError(t, err) - - params := hcpgnm.NewAgentBootstrapConfigParams(). - WithID(clusterResource.ID). - WithLocationOrganizationID(clusterResource.Organization). - WithLocationProjectID(clusterResource.Project) - - resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) - require.NoError(t, err) - - bootstrapConfig := resp.GetPayload() - logger.Log(t, "HCP configuration successfully fetched.") - - return c.parseBootstrapConfigResponse(t, bootstrapConfig) -} - -// ConsulConfig represents 'cluster.consul_config' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ConsulConfig struct { - ACL ACL `json:"acl"` -} - -// ACL represents 'cluster.consul_config.acl' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ACL struct { - Tokens Tokens `json:"tokens"` -} - -// Tokens represents 'cluster.consul_config.acl.tokens' in the -// response fetched from the agent bootstrap config endpoint in HCP. -type Tokens struct { - Agent string `json:"agent"` - InitialManagement string `json:"initial_management"` -} - -// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse -// and also sets the HCPConfig values to return CloudBootstrapConfig struct. -func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { - - var consulConfig ConsulConfig - err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) - require.NoError(t, err) - - return consulConfig.ACL.Tokens.InitialManagement -} - -func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - if c.ClientID != "" && c.ClientSecret != "" { - opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) - } - if c.AuthURL != "" { - opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) - } - if c.APIHostname != "" { - opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) - } - if c.ScadaAddress != "" { - opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) - } - opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) - return hcpcfg.NewHCPConfig(opts...) -} diff --git a/acceptance/tests/cloud/metrics_validation.go b/acceptance/tests/cloud/metrics_validation.go deleted file mode 100644 index 558ae54509..0000000000 --- a/acceptance/tests/cloud/metrics_validation.go +++ /dev/null @@ -1,114 +0,0 @@ -package cloud - -import ( - "strings" - - "github.com/hashicorp/serf/testutil/retry" - "github.com/stretchr/testify/require" - otlpcolmetrics "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - otlpcommon "go.opentelemetry.io/proto/otlp/common/v1" - otlpmetrics "go.opentelemetry.io/proto/otlp/metrics/v1" - "google.golang.org/protobuf/proto" -) - -type metricValidations struct { - disabled bool - expectedMetricName string - disallowedMetricName string - expectedLabelKeys []string -} - -// validateMetrics ensure OTLP metrics as recorded by the Collector or Consul as expected. -func validateMetrics(r *retry.R, records []*RequestRecord, validations *metricValidations, since int64) { - // If metrics are disabled, no metrics records should exist, and return early. - if validations.disabled { - require.Empty(r, records) - return - } - - // If metrics are not disabled, records should not be empty. - require.NotEmpty(r, records) - - for _, record := range records { - require.True(r, record.ValidRequest, "expected request to be valid") - - req := &otlpcolmetrics.ExportMetricsServiceRequest{} - err := proto.Unmarshal(record.Body, req) - require.NoError(r, err, "failed to extract metrics from body") - - // Basic validation that metrics are not empty. - require.NotEmpty(r, req.GetResourceMetrics()) - require.NotEmpty(r, req.ResourceMetrics[0].GetScopeMetrics()) - require.NotEmpty(r, req.ResourceMetrics[0].ScopeMetrics[0].GetMetrics()) - - // Verify expected key labels and metric names. - labels := externalLabels(req, since) - for _, key := range validations.expectedLabelKeys { - require.Contains(r, labels, key) - } - validateMetricName(r, req, validations) - } -} - -// validateMetricName ensures an expected metric name has been recorded based on filters and disallowed metrics are not present. -func validateMetricName(t *retry.R, request *otlpcolmetrics.ExportMetricsServiceRequest, validations *metricValidations) { - exists := false - for _, metric := range request.ResourceMetrics[0].ScopeMetrics[0].GetMetrics() { - require.NotContains(t, metric.Name, validations.disallowedMetricName) - - if strings.Contains(metric.Name, validations.expectedMetricName) { - exists = true - } - } - - require.True(t, exists) -} - -// externalLabels converts OTLP labels to a map[string]string format. -func externalLabels(request *otlpcolmetrics.ExportMetricsServiceRequest, since int64) map[string]string { - // For the Consul Telemetry Collector, labels are contained at the higher level scope. - attrs := request.ResourceMetrics[0].GetResource().GetAttributes() - - // For Consul server metrics, labels are contained with individual metrics, and must be extracted. - if len(attrs) < 1 { - attrs = getMetricLabel(request.ResourceMetrics[0].GetScopeMetrics(), since) - } - - labels := make(map[string]string, len(attrs)) - for _, kv := range attrs { - k := strings.ReplaceAll(kv.GetKey(), ".", "_") - labels[k] = kv.GetValue().GetStringValue() - } - - return labels -} - -// getMetricLabel returns labels at each datapoint within a metric. -func getMetricLabel(scopeMetrics []*otlpmetrics.ScopeMetrics, since int64) []*otlpcommon.KeyValue { - // The attributes field can only be accessed on the specific implementation (gauge, sum or hist). - for _, metric := range scopeMetrics[0].Metrics { - switch v := metric.Data.(type) { - case *otlpmetrics.Metric_Gauge: - for _, dp := range v.Gauge.GetDataPoints() { - // When a refresh has occured, filter time since last refresh as older data points may not have latest labels. - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - case *otlpmetrics.Metric_Histogram: - for _, dp := range v.Histogram.GetDataPoints() { - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - case *otlpmetrics.Metric_Sum: - for _, dp := range v.Sum.GetDataPoints() { - if dp.StartTimeUnixNano > uint64(since) { - return dp.Attributes - } - } - } - } - - return []*otlpcommon.KeyValue{} -} diff --git a/acceptance/tests/cloud/remote_dev_test.go b/acceptance/tests/cloud/remote_dev_test.go deleted file mode 100644 index 457dc4f269..0000000000 --- a/acceptance/tests/cloud/remote_dev_test.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloud - -import ( - "crypto/tls" - "encoding/json" - "os" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - - hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" - "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" - hcpcfg "github.com/hashicorp/hcp-sdk-go/config" - "github.com/hashicorp/hcp-sdk-go/httpclient" - "github.com/hashicorp/hcp-sdk-go/resource" -) - -type DevTokenResponse struct { - Token string `json:"token"` -} - -type hcp struct { - ResourceID string - ClientID string - ClientSecret string - AuthURL string - APIHostname string - ScadaAddress string -} - -func TestRemoteDevCloud(t *testing.T) { - _, rIDok := os.LookupEnv("HCP_RESOURCE_ID") - _, cIDok := os.LookupEnv("HCP_CLIENT_ID") - _, cSECok := os.LookupEnv("HCP_CLIENT_SECRET") - - if !rIDok || !cIDok || !cSECok { - t.Log("Must set HCP_RESOURCE_ID, HCP_CLIENT_ID and HCP_CLIENT_SECRET") - t.FailNow() - } - - apiHost := os.Getenv("HCP_AUTH_URL") - if apiHost == "" { - apiHost = "https://api.hcp.dev" - } - authURL := os.Getenv("HCP_API_HOST") - if authURL == "" { - authURL = "https://auth.idp.hcp.dev" - } - scadaAddr := os.Getenv("HCP_SCADA_ADDRESS") - if scadaAddr == "" { - scadaAddr = "scada.internal.hcp.dev:7224" - } - - ctx := suite.Environment().DefaultContext(t) - - kubectlOptions := ctx.KubectlOptions(t) - ns := kubectlOptions.Namespace - k8sClient := environment.KubernetesClientFromOptions(t, kubectlOptions) - - var ( - resourceSecretName = "resource-sec-name" - resourceSecretKey = "resource-sec-key" - resourceSecretKeyValue = os.Getenv("HCP_RESOURCE_ID") - - clientIDSecretName = "clientid-sec-name" - clientIDSecretKey = "clientid-sec-key" - clientIDSecretKeyValue = os.Getenv("HCP_CLIENT_ID") - - clientSecretName = "client-sec-name" - clientSecretKey = "client-sec-key" - clientSecretKeyValue = os.Getenv("HCP_CLIENT_SECRET") - - apiHostSecretName = "apihost-sec-name" - apiHostSecretKey = "apihost-sec-key" - apiHostSecretKeyValue = apiHost - - authUrlSecretName = "authurl-sec-name" - authUrlSecretKey = "authurl-sec-key" - authUrlSecretKeyValue = authURL - - scadaAddressSecretName = "scadaaddress-sec-name" - scadaAddressSecretKey = "scadaaddress-sec-key" - scadaAddressSecretKeyValue = scadaAddr - - bootstrapTokenSecretName = "bootstrap-token" - bootstrapTokenSecretKey = "token" - ) - - hcpCfg := hcp{ - ResourceID: resourceSecretKeyValue, - ClientID: clientIDSecretKeyValue, - ClientSecret: clientSecretKeyValue, - AuthURL: authUrlSecretKeyValue, - APIHostname: apiHostSecretKeyValue, - ScadaAddress: scadaAddressSecretKeyValue, - } - - aclToken := hcpCfg.fetchAgentBootstrapConfig(t) - - cfg := suite.Config() - consul.CreateK8sSecret(t, k8sClient, cfg, ns, resourceSecretName, resourceSecretKey, resourceSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientIDSecretName, clientIDSecretKey, clientIDSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, clientSecretName, clientSecretKey, clientSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, apiHostSecretName, apiHostSecretKey, apiHostSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, authUrlSecretName, authUrlSecretKey, authUrlSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, scadaAddressSecretName, scadaAddressSecretKey, scadaAddressSecretKeyValue) - consul.CreateK8sSecret(t, k8sClient, cfg, ns, bootstrapTokenSecretName, bootstrapTokenSecretKey, aclToken) - - releaseName := helpers.RandomName() - - helmValues := map[string]string{ - "global.imagePullPolicy": "IfNotPresent", - "global.cloud.enabled": "true", - "global.cloud.resourceId.secretName": resourceSecretName, - "global.cloud.resourceId.secretKey": resourceSecretKey, - - "global.cloud.clientId.secretName": clientIDSecretName, - "global.cloud.clientId.secretKey": clientIDSecretKey, - - "global.cloud.clientSecret.secretName": clientSecretName, - "global.cloud.clientSecret.secretKey": clientSecretKey, - - "global.cloud.apiHost.secretName": apiHostSecretName, - "global.cloud.apiHost.secretKey": apiHostSecretKey, - - "global.cloud.authUrl.secretName": authUrlSecretName, - "global.cloud.authUrl.secretKey": authUrlSecretKey, - - "global.cloud.scadaAddress.secretName": scadaAddressSecretName, - "global.cloud.scadaAddress.secretKey": scadaAddressSecretKey, - "connectInject.default": "true", - - "global.acls.manageSystemACLs": "true", - "global.acls.bootstrapToken.secretName": bootstrapTokenSecretName, - "global.acls.bootstrapToken.secretKey": bootstrapTokenSecretKey, - - "global.gossipEncryption.autoGenerate": "false", - "global.tls.enabled": "true", - "global.tls.enableAutoEncrypt": "true", - - "telemetryCollector.enabled": "true", - "telemetryCollector.cloud.clientId.secretName": clientIDSecretName, - "telemetryCollector.cloud.clientId.secretKey": clientIDSecretKey, - - "telemetryCollector.cloud.clientSecret.secretName": clientSecretName, - "telemetryCollector.cloud.clientSecret.secretKey": clientSecretKey, - // Either we set the global.trustedCAs (make sure it's idented exactly) or we - // set TLS to insecure - - "telemetryCollector.extraEnvironmentVars.HCP_API_ADDRESS": apiHostSecretKeyValue, - } - - if cfg.ConsulImage != "" { - helmValues["global.image"] = cfg.ConsulImage - } - if cfg.ConsulCollectorImage != "" { - helmValues["telemetryCollector.image"] = cfg.ConsulCollectorImage - } - - consulCluster := consul.NewHelmCluster(t, helmValues, suite.Environment().DefaultContext(t), cfg, releaseName) - consulCluster.Create(t) - - logger.Log(t, "setting acl permissions for collector and services") - aclDir := "../fixtures/bases/cloud/service-intentions" - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), aclDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), aclDir) - }) - - logger.Log(t, "creating static-server deployment") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") - time.Sleep(1 * time.Hour) - - // TODO: add in test assertions here - -} - -// fetchAgentBootstrapConfig use the resource-id, client-id, and client-secret -// to call to the agent bootstrap config endpoint and parse the response into a -// CloudBootstrapConfig struct. -func (c *hcp) fetchAgentBootstrapConfig(t *testing.T) string { - cfg, err := c.HCPConfig() - require.NoError(t, err) - logger.Log(t, "Fetching Consul cluster configuration from HCP") - httpClientCfg := httpclient.Config{ - HCPConfig: cfg, - } - clientRuntime, err := httpclient.New(httpClientCfg) - require.NoError(t, err) - - hcpgnmClient := hcpgnm.New(clientRuntime, nil) - clusterResource, err := resource.FromString(c.ResourceID) - require.NoError(t, err) - - params := hcpgnm.NewAgentBootstrapConfigParams(). - WithID(clusterResource.ID). - WithLocationOrganizationID(clusterResource.Organization). - WithLocationProjectID(clusterResource.Project) - - resp, err := hcpgnmClient.AgentBootstrapConfig(params, nil) - require.NoError(t, err) - - bootstrapConfig := resp.GetPayload() - logger.Log(t, "HCP configuration successfully fetched.") - - return c.parseBootstrapConfigResponse(t, bootstrapConfig) -} - -// ConsulConfig represents 'cluster.consul_config' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ConsulConfig struct { - ACL ACL `json:"acl"` -} - -// ACL represents 'cluster.consul_config.acl' in the response -// fetched from the agent bootstrap config endpoint in HCP. -type ACL struct { - Tokens Tokens `json:"tokens"` -} - -// Tokens represents 'cluster.consul_config.acl.tokens' in the -// response fetched from the agent bootstrap config endpoint in HCP. -type Tokens struct { - Agent string `json:"agent"` - InitialManagement string `json:"initial_management"` -} - -// parseBootstrapConfigResponse unmarshals the boostrap parseBootstrapConfigResponse -// and also sets the HCPConfig values to return CloudBootstrapConfig struct. -func (c *hcp) parseBootstrapConfigResponse(t *testing.T, bootstrapRepsonse *models.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) string { - - var consulConfig ConsulConfig - err := json.Unmarshal([]byte(bootstrapRepsonse.Bootstrap.ConsulConfig), &consulConfig) - require.NoError(t, err) - - return consulConfig.ACL.Tokens.InitialManagement -} - -func (c *hcp) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { - if c.ClientID != "" && c.ClientSecret != "" { - opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) - } - if c.AuthURL != "" { - opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) - } - if c.APIHostname != "" { - opts = append(opts, hcpcfg.WithAPI(c.APIHostname, &tls.Config{})) - } - if c.ScadaAddress != "" { - opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, &tls.Config{})) - } - opts = append(opts, hcpcfg.FromEnv(), hcpcfg.WithoutBrowserLogin()) - return hcpcfg.NewHCPConfig(opts...) -} diff --git a/acceptance/tests/config-entries/config_entries_test.go b/acceptance/tests/config-entries/config_entries_test.go index 81b0a75ff4..a36e9baaf5 100644 --- a/acceptance/tests/config-entries/config_entries_test.go +++ b/acceptance/tests/config-entries/config_entries_test.go @@ -9,11 +9,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" - "github.com/hashicorp/consul-k8s/acceptance/framework/config" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" @@ -21,6 +16,10 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul-k8s/acceptance/framework/vault" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" + "github.com/hashicorp/go-uuid" + "github.com/stretchr/testify/require" ) const ( @@ -105,7 +104,6 @@ func TestController(t *testing.T) { svcDefaultEntry, ok := entry.(*api.ServiceConfigEntry) require.True(r, ok, "could not cast to ServiceConfigEntry") require.Equal(r, "http", svcDefaultEntry.Protocol) - require.Equal(r, 1234, svcDefaultEntry.RateLimits.InstanceLevel.RequestsPerSecond) // service-resolver entry, _, err = consulClient.ConfigEntries().Get(api.ServiceResolver, "resolver", nil) @@ -234,6 +232,8 @@ func TestController(t *testing.T) { require.Equal(r, 100.0, rateLimitIPConfigEntry.KV.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.ReadRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Tenancy.WriteRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.ReadRate) + //require.Equal(r, 100.0, rateLimitIPConfigEntry.PreparedQuery.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.ReadRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Session.WriteRate) require.Equal(r, 100.0, rateLimitIPConfigEntry.Txn.ReadRate) diff --git a/acceptance/tests/connect/connect_external_servers_test.go b/acceptance/tests/connect/connect_external_servers_test.go index c0a61f160f..46f9e14573 100644 --- a/acceptance/tests/connect/connect_external_servers_test.go +++ b/acceptance/tests/connect/connect_external_servers_test.go @@ -128,7 +128,7 @@ func TestConnectInject_ExternalServers(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_inject_namespaces_test.go b/acceptance/tests/connect/connect_inject_namespaces_test.go index 03200cc75b..7b7785a44f 100644 --- a/acceptance/tests/connect/connect_inject_namespaces_test.go +++ b/acceptance/tests/connect/connect_inject_namespaces_test.go @@ -221,7 +221,7 @@ func TestConnectInjectNamespaces(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, staticServerOpts, "exec", "deploy/"+connhelper.StaticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_inject_test.go b/acceptance/tests/connect/connect_inject_test.go index 0badf69cc0..39235db36e 100644 --- a/acceptance/tests/connect/connect_inject_test.go +++ b/acceptance/tests/connect/connect_inject_test.go @@ -51,11 +51,11 @@ func TestConnectInject(t *testing.T) { connHelper.Install(t) connHelper.DeployClientAndServer(t) if c.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) connHelper.TestConnectionFailureWhenUnhealthy(t) }) } @@ -328,9 +328,9 @@ func TestConnectInject_MultiportServices(t *testing.T) { // and check inbound connections to the multi port pods' services. // Create the files so that the readiness probes of the multi port pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport") logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") + k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "--", "touch", "/tmp/unhealthy-multiport-admin") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/connect/connect_proxy_lifecycle_test.go b/acceptance/tests/connect/connect_proxy_lifecycle_test.go index 53c48281fd..4a5febfa23 100644 --- a/acceptance/tests/connect/connect_proxy_lifecycle_test.go +++ b/acceptance/tests/connect/connect_proxy_lifecycle_test.go @@ -11,10 +11,10 @@ import ( "testing" "time" + "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/require" @@ -36,8 +36,6 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { cfg := suite.Config() cfg.SkipWhenOpenshiftAndCNI(t) - t.Skipf("TODO(flaky-1.17): NET-XXXX") - for _, testCfg := range []LifecycleShutdownConfig{ {secure: false, helmValues: map[string]string{ helmDrainListenersKey: "true", @@ -122,11 +120,11 @@ func TestConnectInject_ProxyLifecycleShutdown(t *testing.T) { }) if testCfg.secure { - connHelper.TestConnectionFailureWithoutIntention(t, connhelper.ConnHelperOpts{}) - connHelper.CreateIntention(t, connhelper.IntentionOpts{}) + connHelper.TestConnectionFailureWithoutIntention(t) + connHelper.CreateIntention(t) } - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) + connHelper.TestConnectionSuccess(t) // Get static-client pod name ns := ctx.KubectlOptions(t).Namespace @@ -280,7 +278,7 @@ func TestConnectInject_ProxyLifecycleShutdownJob(t *testing.T) { } }) - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{ClientType: connhelper.JobName}) + connHelper.TestConnectionSuccess(t, connhelper.JobName) // Get job-client pod name ns := ctx.KubectlOptions(t).Namespace diff --git a/acceptance/tests/connect/local_rate_limit_test.go b/acceptance/tests/connect/local_rate_limit_test.go deleted file mode 100644 index a9b0df50f0..0000000000 --- a/acceptance/tests/connect/local_rate_limit_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connect - -import ( - "testing" - "time" - - terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" -) - -// TestConnectInject_LocalRateLimiting tests that local rate limiting works as expected between services. -func TestConnectInject_LocalRateLimiting(t *testing.T) { - cfg := suite.Config() - ctx := suite.Environment().DefaultContext(t) - - releaseName := helpers.RandomName() - connHelper := connhelper.ConnectHelper{ - ClusterKind: consul.Helm, - Secure: false, - ReleaseName: releaseName, - Ctx: ctx, - UseAppNamespace: cfg.EnableRestrictedPSAEnforcement, - Cfg: cfg, - } - - connHelper.Setup(t) - connHelper.Install(t) - connHelper.DeployClientAndServer(t) - connHelper.TestConnectionSuccess(t, connhelper.ConnHelperOpts{}) - - // By default, target the static-server on localhost:1234 - staticServer := "localhost:1234" - if cfg.EnableTransparentProxy { - // When TProxy is enabled, use the service name. - staticServer = connhelper.StaticServerName - } - - // Map the static-server URL and path to the rate limits defined in the service defaults at: - // ../fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml - rateLimitMap := map[string]int{ - "http://" + staticServer: 2, - "http://" + staticServer + "/exact": 3, - "http://" + staticServer + "/prefix-test": 4, - "http://" + staticServer + "/regex": 5, - } - - opts := newRateLimitOptions(t, ctx) - - // Ensure that all requests from static-client to static-server succeed (no rate limiting set). - for addr, rps := range rateLimitMap { - opts.rps = rps - assertRateLimits(t, opts, addr) - } - - // Apply local rate limiting to the static-server - writeCrd(t, connHelper, "../fixtures/cases/local-rate-limiting") - - // Ensure that going over the limit causes the static-server to apply rate limiting and - // reply with 429 Too Many Requests - opts.enforced = true - for addr, reqPerSec := range rateLimitMap { - opts.rps = reqPerSec - assertRateLimits(t, opts, addr) - } -} - -func assertRateLimits(t *testing.T, opts *assertRateLimitOptions, curlArgs ...string) { - t.Helper() - - args := []string{"exec", opts.resourceType + opts.sourceApp, "-c", opts.sourceApp, "--", "curl", opts.curlOpts} - args = append(args, curlArgs...) - - // This check is time sensitive due to the nature of rate limiting. - // Run the entire assertion in a retry block and on each pass: - // 1. Send the exact number of requests that are allowed per the rate limiting configuration - // and check that all the requests succeed. - // 2. Send an extra request that should exceed the configured rate limit and check that this request fails. - // 3. Make sure that all requests happened within the rate limit enforcement window of one second. - retry.RunWith(opts.retryTimer, t, func(r *retry.R) { - // Make up to the allowed numbers of calls in a second - t0 := time.Now() - for i := 0; i < opts.rps; i++ { - output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) - require.NoError(r, err) - require.Contains(r, output, opts.successOutput) - } - - // Exceed the configured rate limit. - output, err := k8s.RunKubectlAndGetOutputE(t, opts.k8sOpts, args...) - if opts.enforced { - require.Error(r, err) - require.Contains(r, output, opts.rateLimitOutput, "request was not rate limited") - } else { - require.NoError(r, err) - require.Contains(r, output, opts.successOutput, "request was not successful") - } - require.True(r, time.Since(t0) < time.Second, "failed to make all requests within one second window") - }) -} - -type assertRateLimitOptions struct { - resourceType string - successOutput string - rateLimitOutput string - k8sOpts *terratestK8s.KubectlOptions - sourceApp string - rps int - enforced bool - retryTimer *retry.Timer - curlOpts string -} - -func newRateLimitOptions(t *testing.T, ctx environment.TestContext) *assertRateLimitOptions { - return &assertRateLimitOptions{ - resourceType: "deploy/", - successOutput: "hello world", - rateLimitOutput: "curl: (22) The requested URL returned error: 429", - k8sOpts: ctx.KubectlOptions(t), - sourceApp: connhelper.StaticClientName, - retryTimer: &retry.Timer{Timeout: 120 * time.Second, Wait: 2 * time.Second}, - curlOpts: "-vvvsSf", - } -} diff --git a/acceptance/tests/connect/permissive_mtls_test.go b/acceptance/tests/connect/permissive_mtls_test.go index 929d56acfd..52b5ba513e 100644 --- a/acceptance/tests/connect/permissive_mtls_test.go +++ b/acceptance/tests/connect/permissive_mtls_test.go @@ -67,7 +67,7 @@ func deployStaticServer(t *testing.T, cfg *config.TestConfig, ch connhelper.Conn t.Helper() logger.Log(t, "Creating static-server deployment with connect-inject=true") - k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") + k8s.DeployKustomize(t, ch.Ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, ch.Cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") requirePodContainers(t, ch, "app=static-server", 2) } diff --git a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml index 78547d5118..7278557cdb 100644 --- a/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml +++ b/acceptance/tests/fixtures/bases/cloud/hcp-mock/deployment.yaml @@ -19,7 +19,7 @@ spec: containers: - name: fake-server # TODO: move this to a hashicorp mirror - image: docker.io/achooo/fakeserver:latest + image: docker.io/chaapppie/fakeserver:latest ports: - containerPort: 443 name: https diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml deleted file mode 100644 index fb3f77f496..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/service-intentions/acl.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: consul-telemetry-collector -spec: - destination: - name: 'consul-telemetry-collector' - sources: - - name: '*' - action: allow - - - \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml b/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml deleted file mode 100644 index 9c19bf4ca3..0000000000 --- a/acceptance/tests/fixtures/bases/cloud/service-intentions/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - acl.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml index d0d1fe73bb..cd9c35fa39 100644 --- a/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml +++ b/acceptance/tests/fixtures/bases/crds-oss/servicedefaults.yaml @@ -31,18 +31,6 @@ spec: interval: 10s maxFailures: 2 balanceInboundConnections: "exact_balance" - rateLimits: - instanceLevel: - requestsPerSecond: 1234 - requestsMaxBurst: 2345 - routes: - - pathExact: "/exact" - requestsPerSecond: 222 - requestsMaxBurst: 333 - - pathPrefix: "/prefix" - requestsPerSecond: 444 - - pathRegex: "/regex" - requestsPerSecond: 555 envoyExtensions: - name: builtin/aws/lambda required: false diff --git a/acceptance/tests/fixtures/bases/pingpong/template.tmpl b/acceptance/tests/fixtures/bases/pingpong/template.tmpl deleted file mode 100644 index a8b893025b..0000000000 --- a/acceptance/tests/fixtures/bases/pingpong/template.tmpl +++ /dev/null @@ -1,123 +0,0 @@ -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: pingpong-client-{{.Iteration}} -spec: - protocol: 'http' ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: pingpong-client-{{.Iteration}} ---- -apiVersion: v1 -kind: Service -metadata: - name: pingpong-client-{{.Iteration}} -spec: - selector: - app: pingpong-client-{{.Iteration}} - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: pingpong-client-{{.Iteration}} - name: pingpong-client-{{.Iteration}} -spec: - replicas: {{.Replicas}} - selector: - matchLabels: - app: pingpong-client-{{.Iteration}} - template: - metadata: - annotations: - consul.hashicorp.com/connect-inject: 'true' - labels: - app: pingpong-client-{{.Iteration}} - spec: - serviceAccountName: pingpong-client-{{.Iteration}} - containers: - - name: pingpong-client-{{.Iteration}} - image: rancher/curlimages-curl:7.73.0 - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 1; curl -s --output /dev/null http://pingpong-server-{{.Iteration}} ; done;'] - resources: - requests: - memory: "10Mi" - cpu: "5m" - limits: - memory: "10Mi" - cpu: "5m" ---- -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: pingpong-server-{{.Iteration}} -spec: - protocol: 'http' ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: pingpong-server-{{.Iteration}} ---- -apiVersion: v1 -kind: Service -metadata: - name: pingpong-server-{{.Iteration}} -spec: - selector: - app: pingpong-server-{{.Iteration}} - ports: - - port: 80 - targetPort: 8080 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: pingpong-server-{{.Iteration}} - name: pingpong-server-{{.Iteration}} -spec: - replicas: {{.Replicas}} - selector: - matchLabels: - app: pingpong-server-{{.Iteration}} - template: - metadata: - annotations: - consul.hashicorp.com/connect-inject: 'true' - labels: - app: pingpong-server-{{.Iteration}} - spec: - serviceAccountName: pingpong-server-{{.Iteration}} - containers: - - name: pingpong-server-{{.Iteration}} - image: hashicorp/http-echo:latest - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - resources: - requests: - memory: "10Mi" - cpu: "5m" - limits: - memory: "10Mi" - cpu: "5m" ---- - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceIntentions -metadata: - name: client-to-server-{{.Iteration}} -spec: - destination: - name: pingpong-server-{{.Iteration}} - sources: - - name: pingpong-client-{{.Iteration}} - action: allow diff --git a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml index 87f6a71f32..f88d143728 100644 --- a/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml +++ b/acceptance/tests/fixtures/bases/sameness/override-ns/service-defaults.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceDefaults metadata: diff --git a/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml b/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml index 2fb6a04bb6..de84382d3e 100644 --- a/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml +++ b/acceptance/tests/fixtures/bases/sameness/peering/mesh/mesh.yaml @@ -1,6 +1,3 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - apiVersion: consul.hashicorp.com/v1alpha1 kind: Mesh metadata: diff --git a/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml deleted file mode 100644 index 8e36fe276e..0000000000 --- a/acceptance/tests/fixtures/bases/service-resolver/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - service-resolver.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml b/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml deleted file mode 100644 index 2e0459e381..0000000000 --- a/acceptance/tests/fixtures/bases/service-resolver/service-resolver.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml deleted file mode 100644 index 249cb948bb..0000000000 --- a/acceptance/tests/fixtures/bases/trafficpermissions/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - trafficpermissions.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml b/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml deleted file mode 100644 index ed5c0436ed..0000000000 --- a/acceptance/tests/fixtures/bases/trafficpermissions/trafficpermissions.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: client-to-server -spec: - destination: - identityName: multiport - action: ACTION_ALLOW - permissions: - - sources: - - identityName: static-client diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml deleted file mode 100644 index 5c2e0dcfa2..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/anyuid-scc-rolebinding.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-openshift-anyuid -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:anyuid -subjects: - - kind: ServiceAccount - name: multiport ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-admin-openshift-anyuid -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:anyuid -subjects: - - kind: ServiceAccount - name: multiport-admin diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml deleted file mode 100644 index 0fecd3b590..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/deployment.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: multiport -spec: - replicas: 1 - selector: - matchLabels: - app: multiport - template: - metadata: - name: multiport - labels: - app: multiport - annotations: - "consul.hashicorp.com/mesh-inject": "true" - # TODO: remove this when we add tproxy patch support for this fixture - 'consul.hashicorp.com/transparent-proxy': 'true' - 'consul.hashicorp.com/enable-metrics': 'false' - 'consul.hashicorp.com/enable-metrics-merging': 'false' - spec: - containers: - - name: multiport - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - name: web - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - - name: multiport-admin - image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine - args: - - -text="hello world from 9090 admin" - - -listen=:9090 - ports: - - containerPort: 9090 - # This name is meant to be used alongside the _numeric_ K8s service target port - # to verify that we can still route traffic to the named port when there's a mismatch. - name: admin - livenessProbe: - httpGet: - port: 9090 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 9090 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy-multiport-admin'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: multiport - terminationGracePeriodSeconds: 0 # so deletion is quick diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml deleted file mode 100644 index fb792d63a7..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: - - deployment.yaml - - service.yaml - - secret.yaml - - serviceaccount.yaml - - psp-rolebinding.yaml - - anyuid-scc-rolebinding.yaml - - privileged-scc-rolebinding.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml deleted file mode 100644 index f4f734813e..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/privileged-scc-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport-openshift-privileged -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: system:openshift:scc:privileged -subjects: - - kind: ServiceAccount - name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml deleted file mode 100644 index 623a388d20..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/psp-rolebinding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: multiport -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: test-psp -subjects: - - kind: ServiceAccount - name: multiport diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml deleted file mode 100644 index a412cac6c5..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Secret -metadata: - name: multiport - annotations: - kubernetes.io/service-account.name: multiport -type: kubernetes.io/service-account-token diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml deleted file mode 100644 index fe47663c3d..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: Service -metadata: - name: multiport -spec: - selector: - app: multiport - ports: - - name: web - port: 8080 - targetPort: web - - name: admin - port: 9090 - # Test with a mix of named and numeric target ports. - targetPort: 9090 diff --git a/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml b/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml deleted file mode 100644 index 8af955e059..0000000000 --- a/acceptance/tests/fixtures/bases/v2-multiport-app/serviceaccount.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: multiport diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml deleted file mode 100644 index 64e3d3c8d5..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/api-gateway.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: Gateway -metadata: - name: gateway -spec: - gatewayClassName: gateway-class - listeners: - - protocol: HTTP - port: 8081 - name: http-auth - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTP - port: 8082 - name: http-invalid-attach - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTP - port: 80 - name: http - allowedRoutes: - namespaces: - from: "All" - - protocol: TCP - port: 81 - name: tcp - allowedRoutes: - namespaces: - from: "All" - - protocol: HTTPS - port: 443 - name: https - tls: - certificateRefs: - - name: "certificate" - allowedRoutes: - namespaces: - from: "All" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml deleted file mode 100644 index 19b0669c1a..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/external-ref-other-ns.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter-other - namespace: other -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml deleted file mode 100644 index 03960f67be..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/extra-gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -# This is used to show that a gateway cannot have more than one gateway policy attached to it -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: bad-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml deleted file mode 100644 index 0886ca4bed..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/extraGatewayPolicy/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- extra-gateway-policy.yaml diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml deleted file mode 100644 index 5552d7e085..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/gateway-policy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: GatewayPolicy -metadata: - name: my-policy -spec: - targetRef: - name: gateway - sectionName: http-auth - group: gateway.networking.k8s.io/v1beta1 - kind: Gateway - override: - jwt: - providers: - - name: "local" - default: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: pet diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml deleted file mode 100644 index 93fee5f24a..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-auth.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-auth -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter - - matches: - - path: - type: PathPrefix - value: "/pet" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml deleted file mode 100644 index 55753c29aa..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-invalid-external-ref.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-auth-invalid -spec: - parentRefs: - - name: gateway - sectionName: http-invalid-attach - rules: - - matches: - - path: - type: PathPrefix - value: "/admin" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter-other - - matches: - - path: - type: PathPrefix - value: "/pet" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml deleted file mode 100644 index e4dc1b5a8b..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute-no-auth-on-auth-listener.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route-no-auth-on-auth-listener -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin-no-auth" - backendRefs: - - name: static-server - port: 8080 - - matches: - - path: - type: PathPrefix - value: "/pet-no-auth" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml deleted file mode 100644 index b505d36cb1..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route -spec: - parentRefs: - - name: gateway - sectionName: http - rules: - - matches: - - path: - type: PathPrefix - value: "/v1" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml deleted file mode 100644 index 3894e654ff..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/httproute2-auth.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: gateway.networking.k8s.io/v1beta1 -kind: HTTPRoute -metadata: - name: http-route2-auth -spec: - parentRefs: - - name: gateway - sectionName: http-auth - rules: - - matches: - - path: - type: PathPrefix - value: "/admin-2" - backendRefs: - - name: static-server - port: 8080 - filters: - - type: ExtensionRef - extensionRef: - group: consul.hashicorp.com - kind: RouteAuthFilter - name: route-jwt-auth-filter - - matches: - - path: - type: PathPrefix - value: "/pet-2" - backendRefs: - - name: static-server - port: 8080 diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml deleted file mode 100644 index 1e5cbf35d6..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-provider.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: JWTProvider -metadata: - name: local -spec: - issuer: local - jsonWebKeySet: - local: - jwks: "ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAicCI6ICI5TTlWSVhJR0hpR3FlTnhseEJ2V0xFV09oUFh3dXhXZUpod01uM3dGdG9STEtfZmF6VWxjWEc1cUViLTdpMXo3VmlPUWVZRnh6WUZYTS1pbVU3OVFRa1dTVUVSazR2dHZuc2R5UnpUSnVPc3A0ZUhuWFVMSHJPOU51NkJ5bC1VeVprMzFvSnFGeGllM0pHQXlRLUM2OVF2NVFkVjFZV0hfVDkyTzk4d1hYZGMiLAogICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICJxIjogInFIVnZBb3h0ckgxUTVza25veXNMMkhvbC1ubnU3ZlM3Mjg4clRGdE9jeG9Jb29nWXBKVTljemxwcjctSlo2bjc0TUViVHBBMHRkSUR5TEtQQ0xIN3JKTFRrZzBDZVZNQWpmY01zdkRUcWdFOHNBWE42bzd2ZjYya2hwcExYOHVCU3JxSHkyV1JhZXJsbDROU09hcmRGSkQ2MWhHSVF2cEpXRk4xazFTV3pWcyIsCiAgICAgICAgICAgICJkIjogIlp3elJsVklRZkg5ekZ6d1hOZ2hEMHhkZVctalBCbmRkWnJNZ0wwQ2JjeXZZYlg2X1c0ajlhM1dmYWpobmI2bTFILW9CWjRMczVmNXNRVTB2ZFJ2ZG1laFItUG43aWNRcUdURFNKUTYtdWVtNm15UVRWaEo2UmZiM0lINVJ2VDJTOXUzcVFDZWFadWN3aXFoZ1RCbFhnOWFfV0pwVHJYNFhPQ3JCR1ZsTng3Z2JETVJOamNEN0FnRkZ3S2p2TEZVdDRLTkZmdEJqaFF0TDFLQ2VwblNmamtvRm1RUTVlX3RSS2ozX2U1V3pNSkJkekpQejNkR2YxZEk3OF9wYmJFbmFMcWhqNWg0WUx2UU5JUUhVcURYSGx4ZDc1Qlh3aFJReE1nUDRfd1EwTFk2cVRKNGFDa2Q0RDJBTUtqMzJqeVFiVTRKTE9jQjFNMnZBRWFyc2NTU3l0USIsCiAgICAgICAgICAgICJlIjogIkFRQUIiLAogICAgICAgICAgICAidXNlIjogInNpZyIsCiAgICAgICAgICAgICJraWQiOiAiQy1FMW5DandnQkMtUHVHTTJPNDY3RlJEaEt4OEFrVmN0SVNBYm8zcmlldyIsCiAgICAgICAgICAgICJxaSI6ICJ0N2VOQjhQV21xVHdKREZLQlZKZExrZnJJT2drMFJ4MnREODBGNHB5cjhmNzRuNGlVWXFmWG1haVZtbGx2c2FlT3JlNHlIczQ4UE45NVZsZlVvS3Z6ZEJFaDNZTDFINGZTOGlYYXNzNGJiVnVuWHR4U0hMZFFPYUNZYUplSmhBbGMyUWQ4elR0NFFQWk9yRWVWLVJTYU0tN095ekkwUWtSSF9tcmk1YmRrOXMiLAogICAgICAgICAgICAiZHAiOiAiYnBLckQtVXhrRENDal81MFZLU0NFeE1Ec1Zob2VBZm1tNjMxb1o5aDhUTkZ4TUU1YVptbUJ2VzBJUG9wMm1PUF9qTW9FVWxfUG1RYUlBOEgtVEdqTFp2QTMxSlZBeFN3TU5aQzdwaVFPRjYzVnhneTZUTzlmb1hENVdndC1oLUNxU1N6T2V3eFdmUWNTMmpMcTA3NUFxOTYwTnA2SHhjbE8weUdRN1JDSlpjIiwKICAgICAgICAgICAgImFsZyI6ICJQUzI1NiIsCiAgICAgICAgICAgICJkcSI6ICJpdVZveGwwckFKSEM1c2JzbTZpZWQ3c2ZIVXIwS2Rja0hiVFBLb0lPU1BFcU5YaXBlT3BrWkdEdU55NWlDTXNyRnNHaDFrRW9kTkhZdE40ay1USm5KSDliV296SGdXbGloNnN2R1V0Zi1raFMxWC16ckxaMTJudzlyNDRBbjllWG54bjFaVXMxZm5OakltM3dtZ083algyTWxIeVlNVUZVd0RMd09xNEFPUWsiLAogICAgICAgICAgICAibiI6ICJvUmhjeUREdmp3NFZ4SHRRNTZhRDlNSmRTaWhWSk1nTHd1b2FCQVhhc0RjVDNEWVZjcENlVGxDMVBPdzdPNW1Ec2ZSWVFtcGpoendyRDVZWU8yeDE4REl4czdyNTNJdFMxRy1ybnQxQ1diVE9fUzFJT01DR2xxYzh5VWJnLUhSUkRETXQyb2V3TjJoRGtxYlBKVFJNbXpjRkpNMHRpTm1RZVVMcWViZEVYaWVUblJMT1BkMWg2ZmJycVNLS01mSXlIbGZ1WXFQc1VWSEdkMVBESGljZ3NMazFtZDhtYTNIS1hWM0hJdzZrdUV6R0hQb1gxNHo4YWF6RFFZWndUR3ZxVGlPLUdRUlVDZUJueVo4bVhyWnRmSjNqVk83UUhXcEx3MlM1VDVwVTRwcE0xQXppWTFxUDVfY3ZpOTNZT2Zrb09PalRTX3V3RENZWGFxWjB5bTJHYlEiCiAgICAgICAgfQogICAgXQp9Cg==" diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml deleted file mode 100644 index 9ea3ee2acd..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/jwt-route-filter.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: RouteAuthFilter -metadata: - name: route-jwt-auth-filter -spec: - jwt: - providers: - - name: "local" - verifyClaims: - - path: - - role - value: doctor diff --git a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml b/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml deleted file mode 100644 index 648c936746..0000000000 --- a/acceptance/tests/fixtures/cases/api-gateways/jwt-auth/kustomization.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: -- ../../../bases/api-gateway -- ../../static-server-inject -- httproute-auth.yaml -- httproute-invalid-external-ref.yaml -- httproute2-auth.yaml -- httproute-no-auth-on-auth-listener.yaml -- jwt-provider.yaml -- jwt-route-filter.yaml -- gateway-policy.yaml - - -patches: -- path: httproute.yaml -- path: api-gateway.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml index f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-default/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml index f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/default-partition-ns1/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml index 77c6bd3fae..bb16f51e64 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-default/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-secondary -patches: -- path: patch.yaml + - ../../../bases/exportedservices-secondary + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml index 77c6bd3fae..bb16f51e64 100644 --- a/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-partitions/secondary-partition-ns1/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-secondary -patches: -- path: patch.yaml + - ../../../bases/exportedservices-secondary + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml index f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default-namespace/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml index f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/default/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml similarity index 80% rename from acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml rename to acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml index 4d00c57dfd..f3d0bca3ce 100644 --- a/acceptance/tests/fixtures/cases/trafficpermissions-deny/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/external/kustomization.yaml @@ -4,6 +4,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: -- ../../bases/trafficpermissions +- ../../../bases/exportedservices-default patches: - path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml b/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml new file mode 100644 index 0000000000..4512603bcd --- /dev/null +++ b/acceptance/tests/fixtures/cases/crd-peers/external/patch.yaml @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ExportedServices +metadata: + name: default +spec: + services: + - name: static-server + consumers: + - peer: client + - name: static-server-hostname + consumers: + - peer: client diff --git a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml index f3d0bca3ce..a175d8ece0 100644 --- a/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crd-peers/non-default-namespace/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml index 3fd5556ebd..69d972b417 100644 --- a/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/crds-ent/kustomization.yaml @@ -1,9 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: - ../../bases/crds-oss -patches: -- path: exportedservices.yaml +patchesStrategicMerge: +- exportedservices.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml index 4a3d4f8f3a..63bc1d1900 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-0s/kustomization.yaml @@ -1,10 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 + resources: -- ../../../bases/job-client + - ../../../bases/job-client -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml index 4a3d4f8f3a..63bc1d1900 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject-grace-period-10s/kustomization.yaml @@ -1,10 +1,9 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 + resources: -- ../../../bases/job-client + - ../../../bases/job-client -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml index 4a3d4f8f3a..0aee0558b7 100644 --- a/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/jobs/job-client-inject/kustomization.yaml @@ -2,9 +2,7 @@ # SPDX-License-Identifier: MPL-2.0 resources: -- ../../../bases/job-client + - ../../../bases/job-client -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml b/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml deleted file mode 100644 index b38b25696d..0000000000 --- a/acceptance/tests/fixtures/cases/local-rate-limiting/service-defaults-static-server.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceDefaults -metadata: - name: static-server - namespace: default -spec: - protocol: http - rateLimits: - instanceLevel: - requestsPerSecond: 2 - requestsMaxBurst: 2 - routes: - - pathExact: "/exact" - requestsPerSecond: 3 - requestsMaxBurst: 3 - - pathPrefix: "/prefix" - requestsPerSecond: 4 - - pathRegex: "/regex" - requestsPerSecond: 5 - diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml index 08c7c9b818..30ddacd76c 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-a-acceptor/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml index 08c7c9b818..30ddacd76c 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-01-b-acceptor/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml index 08c7c9b818..30ddacd76c 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-02-a-acceptor/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml index 08c7c9b818..6a54cd6eab 100644 --- a/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/cluster-03-a-acceptor/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/sameness/peering/acceptor -patches: -- path: patch.yaml + - ../../../bases/sameness/peering/acceptor + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml index d25bed6eee..2a2f47a332 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/ap1-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/sameness/exportedservices-ap1 -patches: -- path: patch.yaml + - ../../../../bases/sameness/exportedservices-ap1 + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml index 7f4ab4ba7c..05de6151fc 100644 --- a/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/exported-services/default-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/exportedservices-default -patches: -- path: patch.yaml + - ../../../../bases/exportedservices-default + +patchesStrategicMerge: +- patch.yaml diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml index 096edd19ed..227f223c9f 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition-tproxy/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml index 096edd19ed..227f223c9f 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/ap1-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml index 096edd19ed..227f223c9f 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition-tproxy/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml index 096edd19ed..227f223c9f 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-client/default-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-client -patches: -- path: patch.yaml + - ../../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml index e03603d26d..c15bfe7ba7 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-default/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml index e03603d26d..c15bfe7ba7 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc1-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml index e03603d26d..c15bfe7ba7 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc2/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml index e03603d26d..c15bfe7ba7 100644 --- a/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/sameness/static-server/dc3/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../../bases/static-server -patches: -- path: patch.yaml + - ../../../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject-multiport/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-inject/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-multi-dc/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml index 564d02a68f..97a00c6466 100644 --- a/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-namespaces/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-inject/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-openshift-tproxy/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-default-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/default-ns-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-default-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-partitions/ns-partition/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default-namespace/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/default/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml index 0ae44380dd..38bc36bffd 100644 --- a/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-peers/non-default-namespace/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../../bases/static-client -patches: -- path: patch.yaml + - ../../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml index 564d02a68f..4d4a53b87f 100644 --- a/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-client-tproxy/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-client -patches: -- path: patch.yaml + - ../../bases/static-client + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml index bd2c22ff5f..bc50c78adf 100644 --- a/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-inject/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-server -patches: -- path: patch.yaml + - ../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml index bd2c22ff5f..bc50c78adf 100644 --- a/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml +++ b/acceptance/tests/fixtures/cases/static-server-openshift/kustomization.yaml @@ -1,9 +1,8 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization resources: -- ../../bases/static-server -patches: -- path: patch.yaml + - ../../bases/static-server + +patchesStrategicMerge: + - patch.yaml \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml b/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml deleted file mode 100644 index e1220bcba5..0000000000 --- a/acceptance/tests/fixtures/cases/trafficpermissions-deny/patch.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: client-to-server -spec: - action: ACTION_DENY diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml deleted file mode 100644 index 564d02a68f..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml deleted file mode 100644 index aa96c39398..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject-tproxy/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml deleted file mode 100644 index 564d02a68f..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject/kustomization.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: -- ../../bases/static-client -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml b/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml deleted file mode 100644 index 41b3f192f8..0000000000 --- a/acceptance/tests/fixtures/cases/v2-static-client-inject/patch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/mesh-service-destinations": "web.port.multiport.svc:1234,admin.port.multiport.svc:2345" \ No newline at end of file diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml deleted file mode 100644 index c4f181ce7d..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-ns2-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="ns2" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml deleted file mode 100644 index 60c1219e33..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc1-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="dc1" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml deleted file mode 100644 index 8fa56a3448..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-server - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml deleted file mode 100644 index b167f50c9a..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/dc2-static-server/patch.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-server -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - containers: - - name: static-server - image: docker.mirror.hashicorp.services/kschoche/http-echo:latest - args: - - -text="dc2" - - -listen=:8080 - ports: - - containerPort: 8080 - name: http - livenessProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - startupProbe: - httpGet: - port: 8080 - initialDelaySeconds: 1 - failureThreshold: 30 - periodSeconds: 1 - readinessProbe: - exec: - command: ['sh', '-c', 'test ! -f /tmp/unhealthy'] - initialDelaySeconds: 1 - failureThreshold: 1 - periodSeconds: 1 - serviceAccountName: static-server diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml deleted file mode 100644 index 6be0f308c5..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/service-resolver - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml deleted file mode 100644 index e89156f605..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/service-resolver/patch.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: static-server -spec: - connectTimeout: 15s - failover: - '*': - targets: - - datacenter: "dc2" - - namespace: "ns2" - diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml deleted file mode 100644 index 583889f5d8..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/static-client/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -resources: -- ../../../bases/static-client - -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patch.yaml diff --git a/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml b/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml deleted file mode 100644 index f2f8981601..0000000000 --- a/acceptance/tests/fixtures/cases/wan-federation/static-client/patch.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: static-client -spec: - template: - metadata: - annotations: - 'consul.hashicorp.com/connect-inject': 'true' - "consul.hashicorp.com/connect-service-upstreams": "static-server:1234" - spec: - containers: - - name: static-client - image: anubhavmishra/tiny-tools:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - # If ACLs are enabled, the serviceAccountName must match the Consul service name. - serviceAccountName: static-client \ No newline at end of file diff --git a/acceptance/tests/mesh_v2/main_test.go b/acceptance/tests/mesh_v2/main_test.go deleted file mode 100644 index d510056a10..0000000000 --- a/acceptance/tests/mesh_v2/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package mesh_v2 - -import ( - "os" - "testing" - - testsuite "github.com/hashicorp/consul-k8s/acceptance/framework/suite" -) - -var suite testsuite.Suite - -func TestMain(m *testing.M) { - suite = testsuite.NewSuite(m) - os.Exit(suite.Run()) -} diff --git a/acceptance/tests/mesh_v2/mesh_inject_test.go b/acceptance/tests/mesh_v2/mesh_inject_test.go deleted file mode 100644 index d54229d84b..0000000000 --- a/acceptance/tests/mesh_v2/mesh_inject_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package mesh_v2 - -import ( - "context" - "fmt" - "strconv" - "testing" - "time" - - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" - "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" - "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" - "github.com/hashicorp/consul-k8s/acceptance/framework/logger" -) - -const multiport = "multiport" - -// Test that mesh sidecar proxies work for an application with multiple ports. The multiport application is a Pod listening on -// two ports. This tests inbound connections to each port of the multiport app, and outbound connections from the -// multiport app to static-server. -func TestMeshInject_MultiportService(t *testing.T) { - for _, secure := range []bool{false, true} { - name := fmt.Sprintf("secure: %t", secure) - - t.Run(name, func(t *testing.T) { - cfg := suite.Config() - cfg.SkipWhenOpenshiftAndCNI(t) - ctx := suite.Environment().DefaultContext(t) - - helmValues := map[string]string{ - "global.experiments[0]": "resource-apis", - "global.image": "ndhanushkodi/consul-dev:expose2", - // The UI is not supported for v2 in 1.17, so for now it must be disabled. - "ui.enabled": "false", - "connectInject.enabled": "true", - // Enable DNS so we can test that DNS redirection _isn't_ set in the pod. - "dns.enabled": "true", - - "global.tls.enabled": strconv.FormatBool(secure), - "global.acls.manageSystemACLs": strconv.FormatBool(secure), - } - - releaseName := helpers.RandomName() - consulCluster := consul.NewHelmCluster(t, helmValues, ctx, cfg, releaseName) - - consulCluster.Create(t) - - consulClient, _ := consulCluster.SetupConsulClient(t, secure) - - // Check that the ACL token is deleted. - if secure { - // We need to register the cleanup function before we create the deployments - // because golang will execute them in reverse order i.e. the last registered - // cleanup function will be executed first. - t.Cleanup(func() { - retrier := &retry.Timer{Timeout: 5 * time.Minute, Wait: 1 * time.Second} - retry.RunWith(retrier, t, func(r *retry.R) { - tokens, _, err := consulClient.ACL().TokenList(nil) - require.NoError(r, err) - for _, token := range tokens { - require.NotContains(r, token.Description, multiport) - require.NotContains(r, token.Description, connhelper.StaticClientName) - } - }) - }) - } - - logger.Log(t, "creating multiport static-server and static-client deployments") - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/bases/v2-multiport-app") - if cfg.EnableTransparentProxy { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject-tproxy") - } else { - k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../../tests/fixtures/cases/v2-static-client-inject") - } - - // Check that static-client has been injected and now has 2 containers. - podList, err := ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: "app=static-client", - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 2) - - // Check that multiport has been injected and now has 3 containers. - podList, err = ctx.KubernetesClient(t).CoreV1().Pods(ctx.KubectlOptions(t).Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: "app=multiport", - }) - require.NoError(t, err) - require.Len(t, podList.Items, 1) - require.Len(t, podList.Items[0].Spec.Containers, 3) - - if !secure { - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/cases/trafficpermissions-deny") - } - - // Now test that traffic is denied between the source and the destination. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } - k8s.KubectlApplyK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, ctx.KubectlOptions(t), "../../tests/fixtures/bases/trafficpermissions") - }) - - // TODO: add a trafficpermission to a particular port and validate - - // Check connection from static-client to multiport. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://multiport:8080") - } else { - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "http://localhost:1234") - } - - // Check connection from static-client to multiport-admin. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionSuccessfulWithMessage(t, ctx.KubectlOptions(t), connhelper.StaticClientName, "hello world from 9090 admin", "http://localhost:2345") - } - - // Test that kubernetes readiness status is synced to Consul. This will make the multi port pods unhealthy - // and check inbound connections to the multi port pods' services. - // Create the files so that the readiness probes of the multi port pod fails. - logger.Log(t, "testing k8s -> consul health checks sync by making the multiport unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport", "--", "touch", "/tmp/unhealthy-multiport") - logger.Log(t, "testing k8s -> consul health checks sync by making the multiport-admin unhealthy") - k8s.RunKubectl(t, ctx.KubectlOptions(t), "exec", "deploy/"+multiport, "-c", "multiport-admin", "--", "touch", "/tmp/unhealthy-multiport-admin") - - // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry - // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. - // We are expecting a "connection reset by peer" error because in a case of health checks, - // there will be no healthy proxy host to connect to. That's why we can't assert that we receive an empty reply - // from server, which is the case when a connection is unsuccessful due to intentions in other tests. - if cfg.EnableTransparentProxy { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:8080") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://multiport:9090") - } else { - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:1234") - k8s.CheckStaticServerConnectionMultipleFailureMessages(t, ctx.KubectlOptions(t), connhelper.StaticClientName, false, []string{"curl: (56) Recv failure: Connection reset by peer", "curl: (52) Empty reply from server"}, "", "http://localhost:2345") - } - }) - } -} diff --git a/acceptance/tests/metrics/metrics_test.go b/acceptance/tests/metrics/metrics_test.go index ca80d38b75..3d40841e36 100644 --- a/acceptance/tests/metrics/metrics_test.go +++ b/acceptance/tests/metrics/metrics_test.go @@ -74,12 +74,12 @@ func TestComponentMetrics(t *testing.T) { k8s.DeployKustomize(t, ctx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-client") // Server Metrics - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:8500/v1/agent/metrics?format=prometheus", fmt.Sprintf("%s-consul-server.%s.svc", releaseName, ns))) require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) // Client Metrics - metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") + metricsOutput, err = k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "sh", "-c", "curl --silent --show-error http://$HOST_IP:8500/v1/agent/metrics?format=prometheus") require.NoError(t, err) require.Contains(t, metricsOutput, `consul_acl_ResolveToken{quantile="0.5"}`) @@ -133,7 +133,7 @@ func TestAppMetrics(t *testing.T) { // Retry because sometimes the merged metrics server takes a couple hundred milliseconds // to start. retry.RunWith(&retry.Counter{Count: 20, Wait: 2 * time.Second}, t, func(r *retry.R) { - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(r, err) // This assertion represents the metrics from the envoy sidecar. require.Contains(r, metricsOutput, `envoy_cluster_assignment_stale{local_cluster="server",consul_source_service="server"`) @@ -147,7 +147,7 @@ func assertGatewayMetricsEnabled(t *testing.T, ctx environment.TestContext, ns, require.NoError(t, err) for _, pod := range pods.Items { podIP := pod.Status.PodIP - metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "-c", "static-client", "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) + metricsOutput, err := k8s.RunKubectlAndGetOutputE(t, ctx.KubectlOptions(t), "exec", "deploy/"+StaticClientName, "--", "curl", "--silent", "--show-error", fmt.Sprintf("http://%s:20200/metrics", podIP)) require.NoError(t, err) require.Contains(t, metricsOutput, metricsAssertion) } diff --git a/acceptance/tests/partitions/partitions_connect_test.go b/acceptance/tests/partitions/partitions_connect_test.go index 3a3824fade..c53e5b6171 100644 --- a/acceptance/tests/partitions/partitions_connect_test.go +++ b/acceptance/tests/partitions/partitions_connect_test.go @@ -30,11 +30,6 @@ func TestPartitions_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() - // Currently there is a bug which causes flakes when CNI is enabled - if cfg.EnableCNI { - t.Skipf("TODO(flaky): NET-5819") - } - if !cfg.EnableEnterprise { t.Skipf("skipping this test because -enable-enterprise is not set") } @@ -410,8 +405,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. @@ -583,8 +578,8 @@ func TestPartitions_Connect(t *testing.T) { // Test that kubernetes readiness status is synced to Consul. // Create the file so that the readiness probe of the static-server pod fails. logger.Log(t, "testing k8s -> consul health checks sync by making the static-server unhealthy") - k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") - k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "-c", "static-server", "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, defaultPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") + k8s.RunKubectl(t, secondaryPartitionClusterStaticServerOpts, "exec", "deploy/"+staticServerName, "--", "touch", "/tmp/unhealthy") // The readiness probe should take a moment to be reflected in Consul, CheckStaticServerConnection will retry // until Consul marks the service instance unavailable for mesh traffic, causing the connection to fail. diff --git a/acceptance/tests/peering/peering_connect_test.go b/acceptance/tests/peering/peering_connect_test.go index d92ab59990..60d038d5b8 100644 --- a/acceptance/tests/peering/peering_connect_test.go +++ b/acceptance/tests/peering/peering_connect_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" + terminatinggateway "github.com/hashicorp/consul-k8s/acceptance/tests/terminating-gateway" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-version" @@ -22,7 +23,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Test that Connect works in installations for X-Peers networking. +// TestPeering_Connect validates that service mesh works properly across two peered clusters. +// It deploys a static client in one cluster and a static server in another and checks that requests from the client +// can reach the server. +// It also deploys a static server pod that is not connected to the mesh, but added as a +// destination for a terminating gateway. It checks that requests from the client can reach this server through the +// terminating gateway via the mesh gateways. func TestPeering_Connect(t *testing.T) { env := suite.Environment() cfg := suite.Config() @@ -35,6 +41,7 @@ func TestPeering_Connect(t *testing.T) { const staticServerPeer = "server" const staticClientPeer = "client" + cases := []struct { name string ACLsEnabled bool @@ -72,7 +79,10 @@ func TestPeering_Connect(t *testing.T) { } staticServerPeerHelmValues := map[string]string{ - "global.datacenter": staticServerPeer, + "global.datacenter": staticServerPeer, + "terminatingGateways.enabled": "true", + "terminatingGateways.gateways[0].name": "terminating-gateway", + "terminatingGateways.gateways[0].replicas": "1", } if !cfg.UseKind { @@ -185,20 +195,19 @@ func TestPeering_Connect(t *testing.T) { Namespace: staticClientNamespace, } - logger.Logf(t, "creating namespaces %s in server peer", staticServerNamespace) + logger.Logf(t, "creating namespace %s in server peer", staticServerNamespace) k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", staticServerNamespace) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", staticServerNamespace) }) - logger.Logf(t, "creating namespaces %s in client peer", staticClientNamespace) + logger.Logf(t, "creating namespace %s in client peer", staticClientNamespace) k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "create", "ns", staticClientNamespace) helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { k8s.RunKubectl(t, staticClientPeerClusterContext.KubectlOptions(t), "delete", "ns", staticClientNamespace) }) - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. + // Create a ProxyDefaults resource to configure services to use the mesh gateways. logger.Log(t, "creating proxy-defaults config") kustomizeDir := "../fixtures/bases/mesh-gateway" @@ -287,6 +296,70 @@ func TestPeering_Connect(t *testing.T) { k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, "http://localhost:1234") } + // Check that requests can reach the external static-server from the static client cluster. + if cfg.EnableTransparentProxy { + const ( + externalServerK8sNamespace = "external" + externalServerServiceName = "static-server" + externalServerHostnameID = "static-server-hostname" + terminatingGatewayRules = `service_prefix "static-server" {policy = "write"}` + ) + + // Create the namespace for the "external" static server. + externalServerOpts := &terratestk8s.KubectlOptions{ + ContextName: staticServerOpts.ContextName, + ConfigPath: staticServerOpts.ConfigPath, + Namespace: externalServerK8sNamespace, + } + logger.Logf(t, "creating namespace %s in server peer", externalServerK8sNamespace) + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "create", "ns", externalServerK8sNamespace) + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, staticServerPeerClusterContext.KubectlOptions(t), "delete", "ns", externalServerK8sNamespace) + }) + + // Create the external server in the server Kubernetes cluster, outside the mesh in the "external" namespace + logger.Log(t, "creating static-server deployment in server peer outside of mesh") + k8s.DeployKustomize(t, externalServerOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") + + // Prevent dialing the server directly through the sidecar. + terminatinggateway.CreateMeshConfigEntry(t, staticServerPeerClient, "") + terminatinggateway.CreateMeshConfigEntry(t, staticClientPeerClient, "") + + // Create the config entry for the terminating gateway + terminatinggateway.CreateTerminatingGatewayConfigEntry(t, staticServerPeerClient, "", "", externalServerHostnameID) + if c.ACLsEnabled { + // Allow the terminating gateway write access to services prefixed with "static-server". + terminatinggateway.UpdateTerminatingGatewayRole(t, staticServerPeerClient, terminatingGatewayRules) + } + + // This is the URL that the static-client will use to dial the external static server in the server peer. + externalServerHostnameURL := fmt.Sprintf("http://%s.virtual.%s.consul", externalServerHostnameID, staticServerPeer) + + // Register the external service. + terminatinggateway.CreateServiceDefaultDestination(t, staticServerPeerClient, "", externalServerHostnameID, "http", 80, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace)) + // (t-eckert) this shouldn't be required but currently is with HTTP services. It works around a bug. + helpers.RegisterExternalService(t, staticServerPeerClient, "", externalServerHostnameID, fmt.Sprintf("%s.%s", externalServerServiceName, externalServerK8sNamespace), 80) + + // Export the external service to the client peer. + logger.Log(t, "creating exported external services") + k8s.KubectlApplyK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.KubectlDeleteK(t, staticServerPeerClusterContext.KubectlOptions(t), "../fixtures/cases/crd-peers/external") + }) + + // If ACLs are enabled, test that deny intentions prevent connections. + if c.ACLsEnabled { + logger.Log(t, "testing intentions prevent connections through the terminating gateway") + k8s.CheckStaticServerConnectionFailing(t, staticClientOpts, staticClientName, externalServerHostnameURL) + + logger.Log(t, "adding intentions to allow traffic from client ==> server") + terminatinggateway.AddIntention(t, staticServerPeerClient, staticClientPeer, "", staticClientName, "", externalServerHostnameID) + } + + // Test that we can make a call to the terminating gateway. + logger.Log(t, "trying calls to terminating gateway") + k8s.CheckStaticServerConnectionSuccessful(t, staticClientOpts, staticClientName, externalServerHostnameURL) + } }) } } diff --git a/acceptance/tests/sameness/sameness_test.go b/acceptance/tests/sameness/sameness_test.go index 9688ff9855..baf967b24d 100644 --- a/acceptance/tests/sameness/sameness_test.go +++ b/acceptance/tests/sameness/sameness_test.go @@ -1,14 +1,10 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package sameness import ( - ctx "context" + "context" "fmt" "strconv" "strings" - "sync" "testing" "time" @@ -23,7 +19,6 @@ import ( "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -53,10 +48,6 @@ const ( samenessGroupName = "group-01" - cluster01Region = "us-east-1" - cluster02Region = "us-west-1" - cluster03Region = "us-east-2" - retryTimeout = 5 * time.Minute ) @@ -122,8 +113,8 @@ func TestFailover_Connect(t *testing.T) { | | +------------------+ | | | | | +------------------+ | | | Admin Partitions: Default | | | | | | | | Name: cluster-01-a | | | | | Admin Partitions: Default | - | | Region: us-east-1 | | | | | Name: cluster-02-a | - | +-----------------------------+ | | | | Region: us-west-1 | + | | | | | | | Name: cluster-02-a | + | +-----------------------------+ | | | | | | | | | +-----------------------------------+ | Failover 1| | Failover 3 | | +-------------------------------+ | | | +-----------------------------------+ @@ -138,31 +129,25 @@ func TestFailover_Connect(t *testing.T) { | | +------------------+ | | | | | | Admin Partitions: ap1 | | | Admin Partitions: Default | | | Name: cluster-01-b | | | Name: cluster-03-a | - | | Region: us-east-1 | | | Region: us-east-2 | + | | | | | | | +-------------------------------+ | | | | | +-----------------------------------+ +-------------------------------------------+ */ testClusters := clusters{ - keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, - keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}, locality: localityForRegion(cluster01Region)}, - keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}, locality: localityForRegion(cluster02Region)}, - keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true, locality: localityForRegion(cluster03Region)}, + keyCluster01a: {name: peerName1a, context: env.DefaultContext(t), hasServer: true, acceptors: []string{peerName2a, peerName3a}}, + keyCluster01b: {name: peerName1b, context: env.Context(t, 1), partition: cluster01Partition, hasServer: false, acceptors: []string{peerName2a, peerName3a}}, + keyCluster02a: {name: peerName2a, context: env.Context(t, 2), hasServer: true, acceptors: []string{peerName3a}}, + keyCluster03a: {name: peerName3a, context: env.Context(t, 3), hasServer: true}, } - // Set primary clusters per cluster - // This is helpful for cases like DNS with partitions where many aspects of the primary cluster must be used - testClusters[keyCluster01a].primaryCluster = testClusters[keyCluster01a] - testClusters[keyCluster01b].primaryCluster = testClusters[keyCluster01a] - testClusters[keyCluster02a].primaryCluster = testClusters[keyCluster02a] - testClusters[keyCluster03a].primaryCluster = testClusters[keyCluster03a] - // Setup Namespaces. for _, v := range testClusters { createNamespaces(t, cfg, v.context) } + // Create the cluster-01-a. commonHelmValues := map[string]string{ "global.peering.enabled": "true", @@ -187,127 +172,106 @@ func TestFailover_Connect(t *testing.T) { "connectInject.sidecarProxy.lifecycle.defaultEnabled": "false", } - releaseName := helpers.RandomName() - - var wg sync.WaitGroup - - // Create the cluster-01-a and cluster-01-b - // create in same routine as 01-b depends on 01-a being created first - wg.Add(1) - go func() { - // Create the cluster-01-a - defaultPartitionHelmValues := map[string]string{ - "global.datacenter": cluster01Datacenter, - } + defaultPartitionHelmValues := map[string]string{ + "global.datacenter": cluster01Datacenter, + } - // On Kind, there are no load balancers but since all clusters - // share the same node network (docker bridge), we can use - // a NodePort service so that we can access node(s) in a different Kind cluster. - if cfg.UseKind { - defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" - defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" - defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" - defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" - } - helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) + // On Kind, there are no load balancers but since all clusters + // share the same node network (docker bridge), we can use + // a NodePort service so that we can access node(s) in a different Kind cluster. + if cfg.UseKind { + defaultPartitionHelmValues["meshGateway.service.type"] = "NodePort" + defaultPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + defaultPartitionHelmValues["server.exposeService.type"] = "NodePort" + defaultPartitionHelmValues["server.exposeService.nodePort.https"] = "30000" + defaultPartitionHelmValues["server.exposeService.nodePort.grpc"] = "30100" + } + helpers.MergeMaps(defaultPartitionHelmValues, commonHelmValues) - testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) - testClusters[keyCluster01a].helmCluster.Create(t) + releaseName := helpers.RandomName() + testClusters[keyCluster01a].helmCluster = consul.NewHelmCluster(t, defaultPartitionHelmValues, testClusters[keyCluster01a].context, cfg, releaseName) + testClusters[keyCluster01a].helmCluster.Create(t) - // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. - caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) + // Get the TLS CA certificate and key secret from the server cluster and apply it to the client cluster. + caCertSecretName := fmt.Sprintf("%s-consul-ca-cert", releaseName) - logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) + logger.Logf(t, "retrieving ca cert secret %s from the server cluster and applying to the client cluster", caCertSecretName) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, caCertSecretName) - // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. - partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) - if c.ACLsEnabled { - logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) - k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) - } + // Create Secondary Partition Cluster (cluster-01-b) which will apply the primary (dc1) datacenter. + partitionToken := fmt.Sprintf("%s-consul-partitions-acl-token", releaseName) + if c.ACLsEnabled { + logger.Logf(t, "retrieving partition token secret %s from the server cluster and applying to the client cluster", partitionToken) + k8s.CopySecret(t, testClusters[keyCluster01a].context, testClusters[keyCluster01b].context, partitionToken) + } - partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) - partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) + partitionServiceName := fmt.Sprintf("%s-consul-expose-servers", releaseName) + partitionSvcAddress := k8s.ServiceHost(t, cfg, testClusters[keyCluster01a].context, partitionServiceName) - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) + k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, testClusters[keyCluster01b].context) - secondaryPartitionHelmValues := map[string]string{ - "global.enabled": "false", - "global.datacenter": cluster01Datacenter, + secondaryPartitionHelmValues := map[string]string{ + "global.enabled": "false", + "global.datacenter": cluster01Datacenter, - "global.adminPartitions.name": cluster01Partition, + "global.adminPartitions.name": cluster01Partition, - "global.tls.caCert.secretName": caCertSecretName, - "global.tls.caCert.secretKey": "tls.crt", + "global.tls.caCert.secretName": caCertSecretName, + "global.tls.caCert.secretKey": "tls.crt", - "externalServers.enabled": "true", - "externalServers.hosts[0]": partitionSvcAddress, - "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), - "global.server.enabled": "false", - } + "externalServers.enabled": "true", + "externalServers.hosts[0]": partitionSvcAddress, + "externalServers.tlsServerName": fmt.Sprintf("server.%s.consul", cluster01Datacenter), + "global.server.enabled": "false", + } - if c.ACLsEnabled { - // Setup partition token and auth method host if ACLs enabled. - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken - secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" - secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost - } + if c.ACLsEnabled { + // Setup partition token and auth method host if ACLs enabled. + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretName"] = partitionToken + secondaryPartitionHelmValues["global.acls.bootstrapToken.secretKey"] = "token" + secondaryPartitionHelmValues["externalServers.k8sAuthMethodHost"] = k8sAuthMethodHost + } - if cfg.UseKind { - secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" - secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" - secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" - secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" - } - helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) + if cfg.UseKind { + secondaryPartitionHelmValues["externalServers.httpsPort"] = "30000" + secondaryPartitionHelmValues["externalServers.grpcPort"] = "30100" + secondaryPartitionHelmValues["meshGateway.service.type"] = "NodePort" + secondaryPartitionHelmValues["meshGateway.service.nodePort"] = "30200" + } + helpers.MergeMaps(secondaryPartitionHelmValues, commonHelmValues) - testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) - testClusters[keyCluster01b].helmCluster.Create(t) - wg.Done() - }() + testClusters[keyCluster01b].helmCluster = consul.NewHelmCluster(t, secondaryPartitionHelmValues, testClusters[keyCluster01b].context, cfg, releaseName) + testClusters[keyCluster01b].helmCluster.Create(t) // Create cluster-02-a Cluster. - wg.Add(1) - go func() { - PeerOneHelmValues := map[string]string{ - "global.datacenter": cluster02Datacenter, - } + PeerOneHelmValues := map[string]string{ + "global.datacenter": cluster02Datacenter, + } - if cfg.UseKind { - PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerOneHelmValues["meshGateway.service.type"] = "NodePort" - PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) + if cfg.UseKind { + PeerOneHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerOneHelmValues["meshGateway.service.type"] = "NodePort" + PeerOneHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerOneHelmValues, commonHelmValues) - testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) - testClusters[keyCluster02a].helmCluster.Create(t) - wg.Done() - }() + testClusters[keyCluster02a].helmCluster = consul.NewHelmCluster(t, PeerOneHelmValues, testClusters[keyCluster02a].context, cfg, releaseName) + testClusters[keyCluster02a].helmCluster.Create(t) // Create cluster-03-a Cluster. - wg.Add(1) - go func() { - PeerTwoHelmValues := map[string]string{ - "global.datacenter": cluster03Datacenter, - } - - if cfg.UseKind { - PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" - PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" - PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" - } - helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) + PeerTwoHelmValues := map[string]string{ + "global.datacenter": cluster03Datacenter, + } - testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) - testClusters[keyCluster03a].helmCluster.Create(t) - wg.Done() - }() + if cfg.UseKind { + PeerTwoHelmValues["server.exposeGossipAndRPCPorts"] = "true" + PeerTwoHelmValues["meshGateway.service.type"] = "NodePort" + PeerTwoHelmValues["meshGateway.service.nodePort"] = "30100" + } + helpers.MergeMaps(PeerTwoHelmValues, commonHelmValues) - // Wait for the clusters to start up - wg.Wait() + testClusters[keyCluster03a].helmCluster = consul.NewHelmCluster(t, PeerTwoHelmValues, testClusters[keyCluster03a].context, cfg, releaseName) + testClusters[keyCluster03a].helmCluster.Create(t) // Create a ProxyDefaults resource to configure services to use the mesh // gateways and set server and client opts. @@ -348,11 +312,6 @@ func TestFailover_Connect(t *testing.T) { } } - // Apply locality to clusters - for _, v := range testClusters { - setK8sNodeLocality(t, v.context, v) - } - // Peering/Dialer relationship /* cluster-01-a cluster-02-a @@ -373,7 +332,7 @@ func TestFailover_Connect(t *testing.T) { // Copy secrets to the necessary peers to be used for dialing later for _, vv := range testClusters { if isAcceptor(v.name, vv.acceptors) { - acceptorSecretName := v.getPeeringAcceptorSecret(t, cfg, vv.name) + acceptorSecretName := getPeeringAcceptorSecret(t, cfg, v, vv.name) logger.Logf(t, "acceptor %s created on %s", acceptorSecretName, v.name) logger.Logf(t, "copying acceptor token %s from %s to %s", acceptorSecretName, v.name, vv.name) @@ -426,44 +385,44 @@ func TestFailover_Connect(t *testing.T) { // Setup DNS. for _, v := range testClusters { - dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(ctx.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) + dnsService, err := v.context.KubernetesClient(t).CoreV1().Services("default").Get(context.Background(), fmt.Sprintf("%s-%s", releaseName, "consul-dns"), metav1.GetOptions{}) require.NoError(t, err) v.dnsIP = &dnsService.Spec.ClusterIP logger.Logf(t, "%s dnsIP: %s", v.name, *v.dnsIP) } // Setup Prepared Query. + definition := &api.PreparedQueryDefinition{ + Name: "my-query", + Service: api.ServiceQuery{ + Service: staticServerName, + SamenessGroup: samenessGroupName, + Namespace: staticServerNamespace, + OnlyPassing: false, + }, + } for k, v := range testClusters { - definition := &api.PreparedQueryDefinition{ - Name: fmt.Sprintf("my-query-%s", v.fullTextPartition()), - Service: api.ServiceQuery{ - Service: staticServerName, - SamenessGroup: samenessGroupName, - Namespace: staticServerNamespace, - OnlyPassing: false, - Partition: v.fullTextPartition(), - }, + if v.hasServer { + pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) + require.NoError(t, err) + logger.Logf(t, "%s PQ ID: %s", v.name, pqID) + testClusters[k].pqID = &pqID + testClusters[k].pqName = &definition.Name } - - pqID, _, err := v.client.PreparedQuery().Create(definition, &api.WriteOptions{}) - require.NoError(t, err) - logger.Logf(t, "%s PQ ID: %s", v.name, pqID) - testClusters[k].pqID = &pqID - testClusters[k].pqName = &definition.Name } // Create static server/client after the rest of the config is setup for a more stable testing experience // Create static server deployments. logger.Log(t, "creating static-server and static-client deployments") - deployCustomizeAsync(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-default", &wg) - deployCustomizeAsync(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc1-partition", &wg) - deployCustomizeAsync(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc2", &wg) - deployCustomizeAsync(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - "../fixtures/cases/sameness/static-server/dc3", &wg) + k8s.DeployKustomize(t, testClusters[keyCluster01a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-default") + k8s.DeployKustomize(t, testClusters[keyCluster01b].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc1-partition") + k8s.DeployKustomize(t, testClusters[keyCluster02a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc2") + k8s.DeployKustomize(t, testClusters[keyCluster03a].serverOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + "../fixtures/cases/sameness/static-server/dc3") // Create static client deployments. staticClientKustomizeDirDefault := "../fixtures/cases/sameness/static-client/default-partition" @@ -475,15 +434,14 @@ func TestFailover_Connect(t *testing.T) { staticClientKustomizeDirAP1 = fmt.Sprintf("%s-%s", staticClientKustomizeDirAP1, "tproxy") } - deployCustomizeAsync(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirDefault, &wg) - deployCustomizeAsync(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, - staticClientKustomizeDirAP1, &wg) - wg.Wait() + k8s.DeployKustomize(t, testClusters[keyCluster01a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault) + k8s.DeployKustomize(t, testClusters[keyCluster02a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault) + k8s.DeployKustomize(t, testClusters[keyCluster03a].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirDefault) + k8s.DeployKustomize(t, testClusters[keyCluster01b].clientOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, + staticClientKustomizeDirAP1) // Verify that both static-server and static-client have been injected and now have 2 containers in each cluster. // Also get the server IP @@ -493,15 +451,6 @@ func TestFailover_Connect(t *testing.T) { testClusters.verifyServerUpState(t, cfg.EnableTransparentProxy) logger.Log(t, "all infrastructure up and running") - // Verify locality is set on services based on node labels previously applied. - // - // This is currently the only locality testing we do for k8s and ensures that single-partition - // locality-aware routing will function in consul-k8s. In the future, this test will be expanded - // to test multi-cluster locality-based failover with sameness groups. - for _, v := range testClusters { - v.checkLocalities(t) - } - // Verify all the failover Scenarios logger.Log(t, "verifying failover scenarios") @@ -512,6 +461,7 @@ func TestFailover_Connect(t *testing.T) { failoverServer *cluster expectedPQ expectedPQ } + checkDNSPQ bool }{ { name: "cluster-01-a perspective", // This matches the diagram at the beginning of the test @@ -525,6 +475,7 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, + checkDNSPQ: true, }, { name: "cluster-01-b partition perspective", @@ -538,6 +489,7 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, + checkDNSPQ: false, }, { name: "cluster-02-a perspective", @@ -551,6 +503,7 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster03a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster03a].name, namespace: "ns2"}}, }, + checkDNSPQ: true, }, { name: "cluster-03-a perspective", @@ -564,6 +517,7 @@ func TestFailover_Connect(t *testing.T) { {failoverServer: testClusters[keyCluster01b], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster01b].name, namespace: "ns2"}}, {failoverServer: testClusters[keyCluster02a], expectedPQ: expectedPQ{partition: "default", peerName: testClusters[keyCluster02a].name, namespace: "ns2"}}, }, + checkDNSPQ: true, }, } for _, sc := range subCases { @@ -579,21 +533,28 @@ func TestFailover_Connect(t *testing.T) { logger.Log(t, "checking service failover") if cfg.EnableTransparentProxy { - sc.server.serviceTargetCheck(t, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) + serviceFailoverCheck(t, sc.server, v.failoverServer.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", sc.server.fullTextPartition())) } else { - sc.server.serviceTargetCheck(t, v.failoverServer.name, "localhost:8080") + serviceFailoverCheck(t, sc.server, v.failoverServer.name, "localhost:8080") } - // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster - // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition - // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ - // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul - // Verify DNS. - logger.Log(t, "verifying dns") - sc.server.dnsFailoverCheck(t, cfg, releaseName, v.failoverServer) + // Verify DNS + if sc.checkDNSPQ { + logger.Log(t, "verifying dns") + dnsFailoverCheck(t, cfg, releaseName, *sc.server.dnsIP, sc.server, v.failoverServer) - logger.Log(t, "verifying prepared query") - sc.server.preparedQueryFailoverCheck(t, releaseName, v.expectedPQ, v.failoverServer) + // Verify PQ + logger.Log(t, "verifying prepared query") + preparedQueryFailoverCheck(t, releaseName, *sc.server.dnsIP, v.expectedPQ, sc.server, v.failoverServer) + } else { + // We currently skip running DNS and PQ tests for a couple of reasons + // 1. The admin partition does not contain a server, so DNS service will not resolve on the admin partition cluster + // 2. A workaround to perform the DNS and PQ queries on the primary datacenter cluster by specifying the admin partition + // e.g kubectl --context kind-dc1 --namespace ns1 exec -i deploy/static-client -c static-client \ + // -- dig @test-3lmypr-consul-dns.default static-server.service.ns2.ns.mine.sg.ap1.ap.consul + // is not possible at the moment due to a bug. The workaround will be used once this bug is fixed. + logger.Logf(t, "skipping DNS and PQ checks for %s", sc.name) + } // Scale down static-server on the current failover, will fail over to the next. logger.Logf(t, "scaling server down on %s", v.failoverServer.name) @@ -614,7 +575,6 @@ type expectedPQ struct { type cluster struct { name string partition string - locality api.Locality context environment.TestContext helmCluster *consul.HelmCluster client *api.Client @@ -626,10 +586,9 @@ type cluster struct { pqName *string dnsIP *string acceptors []string - primaryCluster *cluster } -func (c *cluster) fullTextPartition() string { +func (c cluster) fullTextPartition() string { if c.partition == "" { return "default" } else { @@ -637,121 +596,6 @@ func (c *cluster) fullTextPartition() string { } } -// serviceTargetCheck verifies that curling the `static-server` using the `static-client` responds with the expected -// cluster name. Each static-server responds with a unique name so that we can verify failover occured as expected. -func (c *cluster) serviceTargetCheck(t *testing.T, expectedName string, curlAddress string) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - var resp string - var err error - retry.RunWith(timer, t, func(r *retry.R) { - // Use -s/--silent and -S/--show-error flags w/ curl to reduce noise during retries. - // This silences extra output like the request progress bar, but preserves errors. - resp, err = k8s.RunKubectlAndGetOutputE(t, c.clientOpts, "exec", "-i", - staticClientDeployment, "-c", staticClientName, "--", "curl", "-sS", curlAddress) - require.NoError(r, err) - assert.Contains(r, resp, expectedName) - }) - logger.Log(t, resp) -} - -// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that -// executing the prepared query via DNS also provides expected results. -func (c *cluster) preparedQueryFailoverCheck(t *testing.T, releaseName string, epq expectedPQ, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - resp, _, err := c.client.PreparedQuery().Execute(*c.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: c.partition}) - require.NoError(t, err) - require.Len(t, resp.Nodes, 1) - - assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) - assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) - assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) - assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) - - // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is - // just verifying that we can query using DNS and that the ip address is correct. It does not however prove - // that failover occurred, that is left to client `Execute` - dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *c.pqName)} - retry.RunWith(timer, t, func(r *retry.R) { - logs := dnsQuery(t, releaseName, dnsPQLookup, c.primaryCluster, failover) - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - }) -} - -// DNS failover check verifies that failover occurred when querying the DNS. -func (c *cluster) dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, failover *cluster) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.%s.ap.consul", samenessGroupName, c.fullTextPartition()), "+tcp", "SRV"} - retry.RunWith(timer, t, func(r *retry.R) { - // Use the primary cluster when performing a DNS lookup, this mostly affects cases - // where we are verifying DNS for a partition - logs := dnsQuery(t, releaseName, dnsLookup, c.primaryCluster, failover) - - assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", *c.primaryCluster.dnsIP)) - assert.Contains(r, logs, "ANSWER SECTION:") - assert.Contains(r, logs, *failover.staticServerIP) - - // Additional checks - // When accessing the SRV record for DNS we can get more information. In the case of Kind, - // the context can be used to determine that failover occured to the expected kubernetes cluster - // hosting Consul - assert.Contains(r, logs, "ADDITIONAL SECTION:") - expectedName := failover.context.KubectlOptions(t).ContextName - if cfg.UseKind { - expectedName = strings.Replace(expectedName, "kind-", "", -1) - } - assert.Contains(r, logs, expectedName) - }) -} - -// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. -func (c *cluster) getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, acceptorName string) string { - // Ensure the secrets are created. - var acceptorSecretName string - timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} - retry.RunWith(timer, t, func(r *retry.R) { - var err error - acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, c.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") - require.NoError(r, err) - require.NotEmpty(r, acceptorSecretName) - }) - - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.RunKubectl(t, c.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) - }) - - return acceptorSecretName -} - -// checkLocalities checks the given cluster for `static-client` and `static-server` instances matching the locality -// expected for the cluster. -func (c *cluster) checkLocalities(t *testing.T) { - for ns, svcs := range map[string][]string{ - staticClientNamespace: { - staticClientName, - staticClientName + "-sidecar-proxy", - }, - staticServerNamespace: { - staticServerName, - staticServerName + "-sidecar-proxy", - }, - } { - for _, svc := range svcs { - cs := c.getCatalogService(t, svc, ns, c.partition) - assert.NotNil(t, cs.ServiceLocality, "service %s in %s did not have locality set", svc, c.name) - assert.Equal(t, c.locality, *cs.ServiceLocality, "locality for service %s in %s did not match expected", svc, c.name) - } - } -} - -func (c *cluster) getCatalogService(t *testing.T, svc, ns, partition string) *api.CatalogService { - resp, _, err := c.client.Catalog().Service(svc, "", &api.QueryOptions{Namespace: ns, Partition: partition}) - require.NoError(t, err) - assert.NotEmpty(t, resp, "did not find service %s in cluster %s (partition=%s ns=%s)", svc, c.name, partition, ns) - return resp[0] -} - type clusters map[string]*cluster func (c clusters) resetScale(t *testing.T) { @@ -766,7 +610,7 @@ func (c clusters) resetScale(t *testing.T) { func (c clusters) setServerIP(t *testing.T) { for _, labelSelector := range []string{"app=static-server", "app=static-client"} { for k, v := range c { - podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(ctx.Background(), + podList, err := v.context.KubernetesClient(t).CoreV1().Pods(metav1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) require.NoError(t, err) require.Len(t, podList.Items, 1) @@ -788,9 +632,9 @@ func (c clusters) verifyServerUpState(t *testing.T, isTproxyEnabled bool) { for _, v := range c { // Query using a client and expect its own name, no failover should occur if isTproxyEnabled { - v.serviceTargetCheck(t, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) + serviceFailoverCheck(t, v, v.name, fmt.Sprintf("http://static-server.virtual.ns2.ns.%s.ap.consul", v.fullTextPartition())) } else { - v.serviceTargetCheck(t, v.name, "localhost:8080") + serviceFailoverCheck(t, v, v.name, "localhost:8080") } } } @@ -818,19 +662,73 @@ func applyResources(t *testing.T, cfg *config.TestConfig, kustomizeDir string, o }) } -// setK8sNodeLocality labels the k8s node corresponding to the given cluster with standard labels indicating the -// locality of that node. These are propagated by connect-inject to registered Consul services. -func setK8sNodeLocality(t *testing.T, context environment.TestContext, c *cluster) { - nodeList, err := context.KubernetesClient(t).CoreV1().Nodes().List(ctx.Background(), metav1.ListOptions{}) +// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` +// using the `static-client` responds with the expected cluster name. Each static-server responds with a uniquue +// name so that we can verify failover occured as expected. +func serviceFailoverCheck(t *testing.T, server *cluster, expectedName string, curlAddress string) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + var resp string + var err error + retry.RunWith(timer, t, func(r *retry.R) { + resp, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, "exec", "-i", + staticClientDeployment, "-c", staticClientName, "--", "curl", curlAddress) + require.NoError(r, err) + assert.Contains(r, resp, expectedName) + }) + logger.Log(t, resp) +} + +// preparedQueryFailoverCheck verifies that failover occurs when executing the prepared query. It also assures that +// executing the prepared query via DNS also provides expected results. +func preparedQueryFailoverCheck(t *testing.T, releaseName string, dnsIP string, epq expectedPQ, server, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + resp, _, err := server.client.PreparedQuery().Execute(*server.pqID, &api.QueryOptions{Namespace: staticServerNamespace, Partition: server.partition}) require.NoError(t, err) - // Get the name of the (only) node from the Kind cluster. - node := nodeList.Items[0].Name - k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyRegion, c.locality.Region) - k8s.KubectlLabel(t, context.KubectlOptions(t), "node", node, corev1.LabelTopologyZone, c.locality.Zone) + require.Len(t, resp.Nodes, 1) + + assert.Equal(t, epq.partition, resp.Nodes[0].Service.Partition) + assert.Equal(t, epq.peerName, resp.Nodes[0].Service.PeerName) + assert.Equal(t, epq.namespace, resp.Nodes[0].Service.Namespace) + assert.Equal(t, *failover.staticServerIP, resp.Nodes[0].Service.Address) + + // Verify that dns lookup is successful, there is no guarantee that the ip address is unique, so for PQ this is + // just verifying that we can query using DNS and that the ip address is correct. It does not however prove + // that failover occured, that is left to client `Execute` + dnsPQLookup := []string{fmt.Sprintf("%s.query.consul", *server.pqName)} + retry.RunWith(timer, t, func(r *retry.R) { + logs := dnsQuery(t, releaseName, dnsPQLookup, server, failover) + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + }) +} + +// DNS failover check verifies that failover occurred when querying the DNS. +func dnsFailoverCheck(t *testing.T, cfg *config.TestConfig, releaseName string, dnsIP string, server, failover *cluster) { + timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} + dnsLookup := []string{fmt.Sprintf("static-server.service.ns2.ns.%s.sg.consul", samenessGroupName), "+tcp", "SRV"} + retry.RunWith(timer, t, func(r *retry.R) { + logs := dnsQuery(t, releaseName, dnsLookup, server, failover) + + assert.Contains(r, logs, fmt.Sprintf("SERVER: %s", dnsIP)) + assert.Contains(r, logs, "ANSWER SECTION:") + assert.Contains(r, logs, *failover.staticServerIP) + + // Additional checks + // When accessing the SRV record for DNS we can get more information. In the case of Kind, + // the context can be used to determine that failover occured to the expected kubernetes cluster + // hosting Consul + assert.Contains(r, logs, "ADDITIONAL SECTION:") + expectedName := failover.context.KubectlOptions(t).ContextName + if cfg.UseKind { + expectedName = strings.Replace(expectedName, "kind-", "", -1) + } + assert.Contains(r, logs, expectedName) + }) } // dnsQuery performs a dns query with the provided query string. -func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, dnsServer, failover *cluster) string { +func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, server, failover *cluster) string { timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} var logs string retry.RunWith(timer, t, func(r *retry.R) { @@ -839,7 +737,7 @@ func dnsQuery(t *testing.T, releaseName string, dnsQuery []string, dnsServer, fa releaseName)} args = append(args, dnsQuery...) var err error - logs, err = k8s.RunKubectlAndGetOutputE(t, dnsServer.clientOpts, args...) + logs, err = k8s.RunKubectlAndGetOutputE(t, server.clientOpts, args...) require.NoError(r, err) }) logger.Logf(t, "%s: %s", failover.name, logs) @@ -857,18 +755,21 @@ func isAcceptor(name string, acceptorList []string) bool { return false } -// localityForRegion returns the full api.Locality to use in tests for a given region string. -func localityForRegion(r string) api.Locality { - return api.Locality{ - Region: r, - Zone: r + "a", - } -} +// getPeeringAcceptorSecret assures that the secret is created and retrieves the secret from the provided acceptor. +func getPeeringAcceptorSecret(t *testing.T, cfg *config.TestConfig, server *cluster, acceptorName string) string { + // Ensure the secrets are created. + var acceptorSecretName string + timer := &retry.Timer{Timeout: retryTimeout, Wait: 1 * time.Second} + retry.RunWith(timer, t, func(r *retry.R) { + var err error + acceptorSecretName, err = k8s.RunKubectlAndGetOutputE(t, server.context.KubectlOptions(t), "get", "peeringacceptor", acceptorName, "-o", "jsonpath={.status.secret.name}") + require.NoError(r, err) + require.NotEmpty(r, acceptorSecretName) + }) -func deployCustomizeAsync(t *testing.T, opts *terratestk8s.KubectlOptions, noCleanupOnFailure bool, noCleanup bool, debugDirectory string, kustomizeDir string, wg *sync.WaitGroup) { - wg.Add(1) - go func() { - k8s.DeployKustomize(t, opts, noCleanupOnFailure, noCleanup, debugDirectory, kustomizeDir) - wg.Done() - }() + helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { + k8s.RunKubectl(t, server.context.KubectlOptions(t), "delete", "secret", acceptorSecretName) + }) + + return acceptorSecretName } diff --git a/acceptance/tests/terminating-gateway/common.go b/acceptance/tests/terminating-gateway/common.go index 908e6cbec1..65dd7545a8 100644 --- a/acceptance/tests/terminating-gateway/common.go +++ b/acceptance/tests/terminating-gateway/common.go @@ -19,7 +19,7 @@ const ( staticServerLocalAddress = "http://localhost:1234" ) -func addIntention(t *testing.T, consulClient *api.Client, sourceNS, sourceService, destinationNS, destinationsService string) { +func AddIntention(t *testing.T, consulClient *api.Client, sourcePeer, sourceNS, sourceService, destinationNS, destinationsService string) { t.Helper() logger.Log(t, fmt.Sprintf("creating %s => %s intention", sourceService, destinationsService)) @@ -32,13 +32,14 @@ func addIntention(t *testing.T, consulClient *api.Client, sourceNS, sourceServic Name: sourceService, Namespace: sourceNS, Action: api.IntentionActionAllow, + Peer: sourcePeer, }, }, }, nil) require.NoError(t, err) } -func createTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { +func CreateTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, gwNamespace, serviceNamespace string, serviceNames ...string) { t.Helper() logger.Log(t, "creating config entry") @@ -69,7 +70,7 @@ func createTerminatingGatewayConfigEntry(t *testing.T, consulClient *api.Client, require.True(t, created, "failed to create config entry") } -func updateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { +func UpdateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules string) { t.Helper() logger.Log(t, "creating a write policy for the static-server") @@ -97,3 +98,46 @@ func updateTerminatingGatewayRole(t *testing.T, consulClient *api.Client, rules _, _, err = consulClient.ACL().RoleUpdate(termGwRole, nil) require.NoError(t, err) } + +func CreateServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { + t.Helper() + + logger.Log(t, "creating config entry") + + if serviceNamespace != "" { + logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) + _, _, err := consulClient.Namespaces().Create(&api.Namespace{ + Name: serviceNamespace, + }, nil) + require.NoError(t, err) + } + + configEntry := &api.ServiceConfigEntry{ + Kind: api.ServiceDefaults, + Name: name, + Namespace: serviceNamespace, + Protocol: protocol, + Destination: &api.DestinationConfig{ + Addresses: addresses, + Port: port, + }, + } + + created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} + +func CreateMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { + t.Helper() + + logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") + created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ + Namespace: namespace, + TransparentProxy: api.TransparentProxyMeshConfig{ + MeshDestinationsOnly: true, + }, + }, nil) + require.NoError(t, err) + require.True(t, created, "failed to create config entry") +} diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go index 62485c6e44..67097b7648 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_destinations_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" "github.com/stretchr/testify/require" ) @@ -79,15 +78,15 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) + UpdateTerminatingGatewayRole(t, consulClient, terminatingGatewayRules) } // Since we are using the transparent kube DNS, disable the ability // of the service to dial the server directly through the sidecar - createMeshConfigEntry(t, consulClient, "") + CreateMeshConfigEntry(t, consulClient, "") // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerHostnameID, staticServerIPID) // Deploy the static client logger.Log(t, "deploying static client") @@ -102,8 +101,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { // Create the service default declaring the external service (aka Destination) logger.Log(t, "creating tcp-based service defaults") - createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) - createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) + CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "", 443, staticServerServiceName) + CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "", 80, staticServerIP) // If ACLs are enabled, test that intentions prevent connections. if c.secure { @@ -115,8 +114,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, "-k", staticServerHostnameURL) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, "", staticClientName, "", staticServerHostnameID) - addIntention(t, consulClient, "", staticClientName, "", staticServerIPID) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerHostnameID) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerIPID) } // Test that we can make a call to the terminating gateway. @@ -132,8 +131,8 @@ func TestTerminatingGatewayDestinations(t *testing.T) { logger.Log(t, "updating service defaults to try other scenarios") // You can't use TLS w/ protocol set to anything L7; Envoy can't snoop the traffic when the client encrypts it - createServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) - createServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) + CreateServiceDefaultDestination(t, consulClient, "", staticServerHostnameID, "http", 80, staticServerServiceName) + CreateServiceDefaultDestination(t, consulClient, "", staticServerIPID, "http", 80, staticServerIP) logger.Log(t, "trying calls to terminating gateway") k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), staticClientName, staticServerIPURL) @@ -141,45 +140,3 @@ func TestTerminatingGatewayDestinations(t *testing.T) { }) } } -func createServiceDefaultDestination(t *testing.T, consulClient *api.Client, serviceNamespace string, name string, protocol string, port int, addresses ...string) { - t.Helper() - - logger.Log(t, "creating config entry") - - if serviceNamespace != "" { - logger.Logf(t, "creating the %s namespace in Consul", serviceNamespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: serviceNamespace, - }, nil) - require.NoError(t, err) - } - - configEntry := &api.ServiceConfigEntry{ - Kind: api.ServiceDefaults, - Name: name, - Namespace: serviceNamespace, - Protocol: protocol, - Destination: &api.DestinationConfig{ - Addresses: addresses, - Port: port, - }, - } - - created, _, err := consulClient.ConfigEntries().Set(configEntry, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} - -func createMeshConfigEntry(t *testing.T, consulClient *api.Client, namespace string) { - t.Helper() - - logger.Log(t, "creating mesh config entry to enable MeshDestinationOnly") - created, _, err := consulClient.ConfigEntries().Set(&api.MeshConfigEntry{ - Namespace: namespace, - TransparentProxy: api.TransparentProxyMeshConfig{ - MeshDestinationsOnly: true, - }, - }, nil) - require.NoError(t, err) - require.True(t, created, "failed to create config entry") -} diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go index 73250ea810..ee51a64c0d 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_namespaces_test.go @@ -77,17 +77,17 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { k8s.DeployKustomize(t, nsK8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service. - registerExternalService(t, consulClient, testNamespace) + helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, testNamespace, testNamespace, staticServerName) // Deploy the static client. logger.Log(t, "deploying static client") @@ -102,7 +102,7 @@ func TestTerminatingGatewaySingleNamespace(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, nsK8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, testNamespace, staticClientName, testNamespace, staticServerName) + AddIntention(t, consulClient, "", testNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway. @@ -186,17 +186,17 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { k8s.DeployKustomize(t, ns1K8SOptions, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/bases/static-server") // Register the external service - registerExternalService(t, consulClient, testNamespace) + helpers.RegisterExternalService(t, consulClient, testNamespace, staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) + UpdateTerminatingGatewayRole(t, consulClient, fmt.Sprintf(staticServerPolicyRulesNamespace, testNamespace)) } // Create the config entry for the terminating gateway - createTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", testNamespace, staticServerName) // Deploy the static client logger.Log(t, "deploying static client") @@ -211,7 +211,7 @@ func TestTerminatingGatewayNamespaceMirroring(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ns2K8SOptions, staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, StaticClientNamespace, staticClientName, testNamespace, staticServerName) + AddIntention(t, consulClient, "", StaticClientNamespace, staticClientName, testNamespace, staticServerName) } // Test that we can make a call to the terminating gateway diff --git a/acceptance/tests/terminating-gateway/terminating_gateway_test.go b/acceptance/tests/terminating-gateway/terminating_gateway_test.go index 6e7e95032f..acd0232227 100644 --- a/acceptance/tests/terminating-gateway/terminating_gateway_test.go +++ b/acceptance/tests/terminating-gateway/terminating_gateway_test.go @@ -12,8 +12,6 @@ import ( "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" ) // Test that terminating gateways work in a default and secure installations. @@ -57,17 +55,17 @@ func TestTerminatingGateway(t *testing.T) { consulClient, _ := consulCluster.SetupConsulClient(t, c.secure) // Register the external service - registerExternalService(t, consulClient, "") + helpers.RegisterExternalService(t, consulClient, "", staticServerName, staticServerName, 80) // If ACLs are enabled we need to update the role of the terminating gateway // with service:write permissions to the static-server service // so that it can request Connect certificates for it. if c.secure { - updateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) + UpdateTerminatingGatewayRole(t, consulClient, staticServerPolicyRules) } // Create the config entry for the terminating gateway. - createTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) + CreateTerminatingGatewayConfigEntry(t, consulClient, "", "", staticServerName) // Deploy the static client logger.Log(t, "deploying static client") @@ -82,7 +80,7 @@ func TestTerminatingGateway(t *testing.T) { k8s.CheckStaticServerConnectionFailing(t, ctx.KubectlOptions(t), staticClientName, staticServerLocalAddress) logger.Log(t, "adding intentions to allow traffic from client ==> server") - addIntention(t, consulClient, "", staticClientName, "", staticServerName) + AddIntention(t, consulClient, "", "", staticClientName, "", staticServerName) } // Test that we can make a call to the terminating gateway. @@ -95,34 +93,3 @@ func TestTerminatingGateway(t *testing.T) { const staticServerPolicyRules = `service "static-server" { policy = "write" }` - -func registerExternalService(t *testing.T, consulClient *api.Client, namespace string) { - t.Helper() - - address := staticServerName - service := &api.AgentService{ - ID: staticServerName, - Service: staticServerName, - Port: 80, - } - - if namespace != "" { - address = fmt.Sprintf("%s.%s", staticServerName, namespace) - service.Namespace = namespace - - logger.Logf(t, "creating the %s namespace in Consul", namespace) - _, _, err := consulClient.Namespaces().Create(&api.Namespace{ - Name: namespace, - }, nil) - require.NoError(t, err) - } - - logger.Log(t, "registering the external service") - _, err := consulClient.Catalog().Register(&api.CatalogRegistration{ - Node: "legacy_node", - Address: address, - NodeMeta: map[string]string{"external-node": "true", "external-probe": "true"}, - Service: service, - }, nil) - require.NoError(t, err) -} diff --git a/acceptance/tests/vault/vault_namespaces_test.go b/acceptance/tests/vault/vault_namespaces_test.go index 939795f199..a6605acc46 100644 --- a/acceptance/tests/vault/vault_namespaces_test.go +++ b/acceptance/tests/vault/vault_namespaces_test.go @@ -25,8 +25,6 @@ import ( // It then configures Consul to use vault as the backend and checks that it works // with the vault namespace. Namespace is added in this via global.secretsBackend.vault.vaultNamespace. func TestVault_VaultNamespace(t *testing.T) { - t.Skipf("TODO(flaky): NET-5682") - cfg := suite.Config() ctx := suite.Environment().DefaultContext(t) ns := ctx.KubectlOptions(t).Namespace diff --git a/acceptance/tests/vault/vault_wan_fed_test.go b/acceptance/tests/vault/vault_wan_fed_test.go index fa63c4d5fb..a2a18842b5 100644 --- a/acceptance/tests/vault/vault_wan_fed_test.go +++ b/acceptance/tests/vault/vault_wan_fed_test.go @@ -31,7 +31,6 @@ import ( // in the secondary that will treat the Vault server in the primary as an external server. func TestVault_WANFederationViaGateways(t *testing.T) { cfg := suite.Config() - if cfg.UseKind { t.Skipf("Skipping this test because it's currently flaky on kind") } @@ -498,7 +497,6 @@ func TestVault_WANFederationViaGateways(t *testing.T) { }) // Check that we can connect services over the mesh gateways. - logger.Log(t, "creating static-server in dc2") k8s.DeployKustomize(t, secondaryCtx.KubectlOptions(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-server-inject") @@ -520,7 +518,6 @@ func TestVault_WANFederationViaGateways(t *testing.T) { logger.Log(t, "checking that connection is successful") k8s.CheckStaticServerConnectionSuccessful(t, primaryCtx.KubectlOptions(t), StaticClientName, "http://localhost:1234") - } // vaultAddress returns Vault's server URL depending on test configuration. diff --git a/acceptance/tests/wan-federation/wan_federation_gateway_test.go b/acceptance/tests/wan-federation/wan_federation_gateway_test.go index ec466c93ec..6abacda438 100644 --- a/acceptance/tests/wan-federation/wan_federation_gateway_test.go +++ b/acceptance/tests/wan-federation/wan_federation_gateway_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" @@ -61,6 +60,15 @@ func TestWANFederation_Gateway(t *testing.T) { primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) primaryConsulCluster.Create(t) + // Get the federation secret from the primary cluster and apply it to secondary cluster + federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) + logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) + federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) + require.NoError(t, err) + federationSecret.ResourceVersion = "" + _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) + require.NoError(t, err) + var k8sAuthMethodHost string // When running on kind, the kube API address in kubeconfig will have a localhost address // which will not work from inside the container. That's why we need to use the endpoints address instead @@ -74,8 +82,6 @@ func TestWANFederation_Gateway(t *testing.T) { k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) } - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - // Create secondary cluster secondaryHelmValues := map[string]string{ "global.datacenter": "dc2", @@ -211,7 +217,7 @@ func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Cl targetAddress := fmt.Sprintf("http://%s/", gatewayAddress) logger.Log(t, "checking that the connection is not successful because there's no intention") - k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) + k8s.CheckStaticServerHTTPConnectionFailing(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) logger.Log(t, "creating intention") _, _, err := client.ConfigEntries().Set(&api.ServiceIntentionsConfigEntry{ @@ -231,5 +237,5 @@ func checkConnectivity(t *testing.T, ctx environment.TestContext, client *api.Cl }() logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), connhelper.StaticClientName, targetAddress) + k8s.CheckStaticServerConnectionSuccessful(t, ctx.KubectlOptions(t), StaticClientName, targetAddress) } diff --git a/acceptance/tests/wan-federation/wan_federation_test.go b/acceptance/tests/wan-federation/wan_federation_test.go index 9ca520ab06..8edc1f5d03 100644 --- a/acceptance/tests/wan-federation/wan_federation_test.go +++ b/acceptance/tests/wan-federation/wan_federation_test.go @@ -8,35 +8,17 @@ import ( "fmt" "strconv" "testing" - "time" - terratestK8s "github.com/gruntwork-io/terratest/modules/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/connhelper" "github.com/hashicorp/consul-k8s/acceptance/framework/consul" - "github.com/hashicorp/consul-k8s/acceptance/framework/environment" "github.com/hashicorp/consul-k8s/acceptance/framework/helpers" "github.com/hashicorp/consul-k8s/acceptance/framework/k8s" "github.com/hashicorp/consul-k8s/acceptance/framework/logger" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - staticClientDeployment = "deploy/static-client" - staticServerDeployment = "deploy/static-server" - - retryTimeout = 5 * time.Minute - - primaryDatacenter = "dc1" - secondaryDatacenter = "dc2" - - localServerPort = "1234" - - primaryNamespace = "ns1" - secondaryNamespace = "ns2" -) +const StaticClientName = "static-client" // Test that Connect and wan federation over mesh gateways work in a default installation // i.e. without ACLs because TLS is required for WAN federation over mesh gateways. @@ -65,7 +47,7 @@ func TestWANFederation(t *testing.T) { secondaryContext := env.Context(t, 1) primaryHelmValues := map[string]string{ - "global.datacenter": primaryDatacenter, + "global.datacenter": "dc1", "global.tls.enabled": "true", "global.tls.httpsOnly": strconv.FormatBool(c.secure), @@ -95,13 +77,31 @@ func TestWANFederation(t *testing.T) { primaryConsulCluster.Create(t) // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) + federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) + logger.Logf(t, "retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) + federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) + require.NoError(t, err) + federationSecret.ResourceVersion = "" + federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace + _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + var k8sAuthMethodHost string + // When running on kind, the kube API address in kubeconfig will have a localhost address + // which will not work from inside the container. That's why we need to use the endpoints address instead + // which will point the node IP. + if cfg.UseKind { + // The Kubernetes AuthMethod host is read from the endpoints for the Kubernetes service. + kubernetesEndpoint, err := secondaryContext.KubernetesClient(t).CoreV1().Endpoints("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) + require.NoError(t, err) + k8sAuthMethodHost = fmt.Sprintf("%s:%d", kubernetesEndpoint.Subsets[0].Addresses[0].IP, kubernetesEndpoint.Subsets[0].Ports[0].Port) + } else { + k8sAuthMethodHost = k8s.KubernetesAPIServerHostFromOptions(t, secondaryContext.KubectlOptions(t)) + } // Create secondary cluster secondaryHelmValues := map[string]string{ - "global.datacenter": secondaryDatacenter, + "global.datacenter": "dc2", "global.tls.enabled": "true", "global.tls.httpsOnly": "false", @@ -130,7 +130,7 @@ func TestWANFederation(t *testing.T) { secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost - secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter + secondaryHelmValues["global.federation.primaryDatacenter"] = "dc1" } if cfg.UseKind { @@ -190,274 +190,11 @@ func TestWANFederation(t *testing.T) { k8s.DeployKustomize(t, primaryHelper.KubectlOptsForApp(t), cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/static-client-multi-dc") if c.secure { - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{}) + primaryHelper.CreateIntention(t) } logger.Log(t, "checking that connection is successful") - k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), connhelper.StaticClientName, "http://localhost:1234") - }) - } -} - -// Test failover scenarios with a static-server in dc1 and a static-server -// in dc2. Use the static-client on dc1 to reach static-server on dc1 in the -// nominal scenario, then cause a failure in dc1 static-server to see the static-client failover to -// the static-server in dc2 -/* - dc1-static-client -- nominal -- > dc1-static-server in namespace ns1 - dc1-static-client -- failover --> dc2-static-server in namespace ns1 - dc1-static-client -- failover --> dc1-static-server in namespace ns2 -*/ -func TestWANFederationFailover(t *testing.T) { - cases := []struct { - name string - secure bool - }{ - { - name: "secure", - secure: true, - }, - { - name: "default", - secure: false, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - env := suite.Environment() - cfg := suite.Config() - - if cfg.EnableRestrictedPSAEnforcement { - t.Skip("This test case is not run with enable restricted PSA enforcement enabled") - } - - primaryContext := env.DefaultContext(t) - secondaryContext := env.Context(t, 1) - - primaryHelmValues := map[string]string{ - "global.datacenter": primaryDatacenter, - - "global.tls.enabled": "true", - "global.tls.httpsOnly": strconv.FormatBool(c.secure), - - "global.federation.enabled": "true", - "global.federation.createFederationSecret": "true", - - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.acls.createReplicationToken": strconv.FormatBool(c.secure), - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "global.enableConsulNamespaces": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - } - - if cfg.UseKind { - primaryHelmValues["meshGateway.service.type"] = "NodePort" - primaryHelmValues["meshGateway.service.nodePort"] = "30000" - } - - releaseName := helpers.RandomName() - - // Install the primary consul cluster in the default kubernetes context - primaryConsulCluster := consul.NewHelmCluster(t, primaryHelmValues, primaryContext, cfg, releaseName) - primaryConsulCluster.Create(t) - - // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := copyFederationSecret(t, releaseName, primaryContext, secondaryContext) - - k8sAuthMethodHost := k8s.KubernetesAPIServerHost(t, cfg, secondaryContext) - - // Create secondary cluster - secondaryHelmValues := map[string]string{ - "global.datacenter": secondaryDatacenter, - - "global.tls.enabled": "true", - "global.tls.httpsOnly": "false", - "global.acls.manageSystemACLs": strconv.FormatBool(c.secure), - "global.tls.caCert.secretName": federationSecretName, - "global.tls.caCert.secretKey": "caCert", - "global.tls.caKey.secretName": federationSecretName, - "global.tls.caKey.secretKey": "caKey", - - "global.federation.enabled": "true", - - "server.extraVolumes[0].type": "secret", - "server.extraVolumes[0].name": federationSecretName, - "server.extraVolumes[0].load": "true", - "server.extraVolumes[0].items[0].key": "serverConfigJSON", - "server.extraVolumes[0].items[0].path": "config.json", - - "connectInject.enabled": "true", - "connectInject.replicas": "1", - - "meshGateway.enabled": "true", - "meshGateway.replicas": "1", - - "global.enableConsulNamespaces": "true", - "connectInject.consulNamespaces.mirroringK8S": "true", - } - - if c.secure { - secondaryHelmValues["global.acls.replicationToken.secretName"] = federationSecretName - secondaryHelmValues["global.acls.replicationToken.secretKey"] = "replicationToken" - secondaryHelmValues["global.federation.k8sAuthMethodHost"] = k8sAuthMethodHost - secondaryHelmValues["global.federation.primaryDatacenter"] = primaryDatacenter - } - - if cfg.UseKind { - secondaryHelmValues["meshGateway.service.type"] = "NodePort" - secondaryHelmValues["meshGateway.service.nodePort"] = "30000" - } - - // Install the secondary consul cluster in the secondary kubernetes context - secondaryConsulCluster := consul.NewHelmCluster(t, secondaryHelmValues, secondaryContext, cfg, releaseName) - secondaryConsulCluster.Create(t) - - primaryClient, _ := primaryConsulCluster.SetupConsulClient(t, c.secure) - secondaryClient, _ := secondaryConsulCluster.SetupConsulClient(t, c.secure) - - // Verify federation between servers - logger.Log(t, "Verifying federation was successful") - helpers.VerifyFederation(t, primaryClient, secondaryClient, releaseName, c.secure) - - // Create a ProxyDefaults resource to configure services to use the mesh - // gateways. - logger.Log(t, "Creating proxy-defaults config") - kustomizeDir := "../fixtures/bases/mesh-gateway" - k8s.KubectlApplyK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - helpers.Cleanup(t, cfg.NoCleanupOnFailure, cfg.NoCleanup, func() { - k8s.KubectlDeleteK(t, secondaryContext.KubectlOptions(t), kustomizeDir) - }) - - primaryHelper := connhelper.ConnectHelper{ - Secure: c.secure, - ReleaseName: releaseName, - Ctx: primaryContext, - UseAppNamespace: false, - Cfg: cfg, - ConsulClient: primaryClient, - } - secondaryHelper := connhelper.ConnectHelper{ - Secure: c.secure, - ReleaseName: releaseName, - Ctx: secondaryContext, - UseAppNamespace: false, - Cfg: cfg, - ConsulClient: secondaryClient, - } - - // Create Namespaces - // We create a namespace (ns1) in both the primary and secondary datacenters (dc1, dc2) - // We then create a secondary namespace (ns2) in the primary datacenter (dc1) - primaryNamespaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) - primaryHelper.CreateNamespace(t, primaryNamespaceOpts.Namespace) - primarySecondaryNamepsaceOpts := primaryHelper.Ctx.KubectlOptionsForNamespace(secondaryNamespace) - primaryHelper.CreateNamespace(t, primarySecondaryNamepsaceOpts.Namespace) - secondaryNamespaceOpts := secondaryHelper.Ctx.KubectlOptionsForNamespace(primaryNamespace) - secondaryHelper.CreateNamespace(t, secondaryNamespaceOpts.Namespace) - - // Create a static-server in dc2 to respond with its own name for checking failover. - logger.Log(t, "Creating static-server in dc2") - k8s.DeployKustomize(t, secondaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc2-static-server") - - // Spin up a server on dc1 which will be the primary upstream for our client - logger.Log(t, "Creating static-server in dc1") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-static-server") - logger.Log(t, "Creating static-client in dc1") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/static-client") - - // Spin up a second server on dc1 in a separate namespace - logger.Logf(t, "Creating server on dc1 in namespace %s", primarySecondaryNamepsaceOpts.Namespace) - k8s.DeployKustomize(t, primarySecondaryNamepsaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/dc1-ns2-static-server") - - // There is currently an issue that requires the intentions and resolvers to be created after - // the static-server/clients when using namespaces. When created before, Consul gives a "namespace does not exist" - // error - if c.secure { - // Only need to create intentions in the primary datacenter as they will be replicated to the secondary - // ns1 static-client (source) -> ns1 static-server (destination) - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primaryNamespaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) - - // ns1 static-client (source) -> ns2 static-server (destination) - primaryHelper.CreateIntention(t, connhelper.IntentionOpts{DestinationNamespace: primarySecondaryNamepsaceOpts.Namespace, SourceNamespace: primaryNamespaceOpts.Namespace}) - } - - // Create a service resolver for failover - logger.Log(t, "Creating service resolver") - k8s.DeployKustomize(t, primaryNamespaceOpts, cfg.NoCleanupOnFailure, cfg.NoCleanup, cfg.DebugDirectory, "../fixtures/cases/wan-federation/service-resolver") - - // Verify that we respond with the static-server in the primary datacenter - logger.Log(t, "Verifying static-server in dc1 responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, primaryDatacenter) - - // Scale down the primary datacenter static-server and see the failover - logger.Log(t, "Scale down dc1 static-server") - k8s.KubectlScale(t, primaryNamespaceOpts, staticServerDeployment, 0) - - // Verify that we respond with the static-server in the secondary datacenter - logger.Log(t, "Verifying static-server in dc2 responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryDatacenter) - - // scale down the primary datacenter static-server and see the failover - logger.Log(t, "Scale down dc2 static-server") - k8s.KubectlScale(t, secondaryNamespaceOpts, staticServerDeployment, 0) - - // Verify that we respond with the static-server in the secondary datacenter - logger.Log(t, "Verifying static-server in secondary namespace (ns2) responds") - serviceFailoverCheck(t, primaryNamespaceOpts, localServerPort, secondaryNamespace) + k8s.CheckStaticServerConnectionSuccessful(t, primaryHelper.KubectlOptsForApp(t), StaticClientName, "http://localhost:1234") }) } } - -// serviceFailoverCheck verifies that the server failed over as expected by checking that curling the `static-server` -// using the `static-client` responds with the expected cluster name. Each static-server responds with a unique -// name so that we can verify failover occurred as expected. -func serviceFailoverCheck(t *testing.T, options *terratestK8s.KubectlOptions, port string, expectedName string) { - timer := &retry.Timer{Timeout: retryTimeout, Wait: 5 * time.Second} - var resp string - var err error - - // Retry until we get the response we expect, sometimes you get back the previous server until things stabalize - logger.Log(t, "Initial failover check") - retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", - staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) - assert.NoError(r, err) - assert.Contains(r, resp, expectedName) - }) - - // Try again to rule out load-balancing. Errors can still happen so retry - logger.Log(t, "Check failover again to rule out load balancing") - for i := 0; i < 10; i++ { - time.Sleep(500 * time.Millisecond) - resp = "" - retry.RunWith(timer, t, func(r *retry.R) { - resp, err = k8s.RunKubectlAndGetOutputE(t, options, "exec", "-i", - staticClientDeployment, "-c", connhelper.StaticClientName, "--", "curl", fmt.Sprintf("localhost:%s", port)) - assert.NoError(r, err) - }) - require.Contains(t, resp, expectedName) - } - - logger.Log(t, resp) -} - -func copyFederationSecret(t *testing.T, releaseName string, primaryContext, secondaryContext environment.TestContext) string { - // Get the federation secret from the primary cluster and apply it to secondary cluster - federationSecretName := fmt.Sprintf("%s-consul-federation", releaseName) - logger.Logf(t, "Retrieving federation secret %s from the primary cluster and applying to the secondary", federationSecretName) - federationSecret, err := primaryContext.KubernetesClient(t).CoreV1().Secrets(primaryContext.KubectlOptions(t).Namespace).Get(context.Background(), federationSecretName, metav1.GetOptions{}) - require.NoError(t, err) - federationSecret.ResourceVersion = "" - federationSecret.Namespace = secondaryContext.KubectlOptions(t).Namespace - _, err = secondaryContext.KubernetesClient(t).CoreV1().Secrets(secondaryContext.KubectlOptions(t).Namespace).Create(context.Background(), federationSecret, metav1.CreateOptions{}) - require.NoError(t, err) - - return federationSecretName -} diff --git a/charts/consul/Chart.yaml b/charts/consul/Chart.yaml index f899b6309d..8405059bd1 100644 --- a/charts/consul/Chart.yaml +++ b/charts/consul/Chart.yaml @@ -3,9 +3,9 @@ apiVersion: v2 name: consul -version: 1.4.0-dev -appVersion: 1.18-dev -kubeVersion: ">=1.25.0-0" +version: 1.2.4-dev +appVersion: 1.16-dev +kubeVersion: ">=1.22.0-0" description: Official HashiCorp Consul Chart home: https://www.consul.io icon: https://raw.githubusercontent.com/hashicorp/consul-k8s/main/assets/icon.png @@ -16,11 +16,11 @@ annotations: artifacthub.io/prerelease: true artifacthub.io/images: | - name: consul - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.18-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.16-dev - name: consul-k8s-control-plane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.4-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.4-dev - name: consul-dataplane - image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.2-dev - name: envoy image: envoyproxy/envoy:v1.25.11 artifacthub.io/license: MPL-2.0 diff --git a/charts/consul/templates/_helpers.tpl b/charts/consul/templates/_helpers.tpl index 5f06839923..5cefb9ec81 100644 --- a/charts/consul/templates/_helpers.tpl +++ b/charts/consul/templates/_helpers.tpl @@ -448,55 +448,3 @@ Usage: {{ template "consul.validateTelemetryCollectorCloud" . }} {{fail "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set"}} {{- end }} {{- end -}} - -{{/* -Fails if global.experiments.resourceAPIs is set along with any of these unsupported features. -- global.peering.enabled -- global.federation.enabled -- global.cloud.enabled -- client.enabled -- ui.enabled -- syncCatalog.enabled -- meshGateway.enabled -- ingressGateways.enabled -- terminatingGateways.enabled -- apiGateway.enabled - -Usage: {{ template "consul.validateResourceAPIs" . }} - -*/}} -{{- define "consul.validateResourceAPIs" -}} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.peering.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.adminPartitions.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.federation.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.global.cloud.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.client.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ui.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.syncCatalog.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.meshGateway.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.ingressGateways.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.terminatingGateways.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported."}} -{{- end }} -{{- if (and (mustHas "resource-apis" .Values.global.experiments) .Values.apiGateway.enabled ) }} -{{fail "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported."}} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/charts/consul/templates/api-gateway-controller-deployment.yaml b/charts/consul/templates/api-gateway-controller-deployment.yaml index 1bd1f8500a..11396c8a03 100644 --- a/charts/consul/templates/api-gateway-controller-deployment.yaml +++ b/charts/consul/templates/api-gateway-controller-deployment.yaml @@ -30,7 +30,6 @@ spec: metadata: annotations: consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} "vault.hashicorp.com/agent-init-first": "true" "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/client-daemonset.yaml b/charts/consul/templates/client-daemonset.yaml index e7dd83ef26..61425cfdb8 100644 --- a/charts/consul/templates/client-daemonset.yaml +++ b/charts/consul/templates/client-daemonset.yaml @@ -86,7 +86,6 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/client-config-configmap.yaml") . | sha256sum }} {{- if .Values.client.annotations }} {{- tpl .Values.client.annotations . | nindent 8 }} diff --git a/charts/consul/templates/cni-daemonset.yaml b/charts/consul/templates/cni-daemonset.yaml index 258924f449..ae04d9e657 100644 --- a/charts/consul/templates/cni-daemonset.yaml +++ b/charts/consul/templates/cni-daemonset.yaml @@ -37,7 +37,6 @@ spec: {{- end }} annotations: consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" spec: # consul-cni only runs on linux operating systems nodeSelector: diff --git a/charts/consul/templates/connect-inject-clusterrole.yaml b/charts/consul/templates/connect-inject-clusterrole.yaml index 2506637949..f1f6b3878f 100644 --- a/charts/consul/templates/connect-inject-clusterrole.yaml +++ b/charts/consul/templates/connect-inject-clusterrole.yaml @@ -28,16 +28,11 @@ rules: - meshservices - samenessgroups - controlplanerequestlimits - - routeretryfilters - - routetimeoutfilters - - routeauthfilters - - gatewaypolicies {{- if .Values.global.peering.enabled }} - peeringacceptors - peeringdialers {{- end }} - jwtproviders - - routeauthfilters verbs: - create - delete @@ -66,60 +61,10 @@ rules: - peeringdialers/status {{- end }} - jwtproviders/status - - routeauthfilters/status - - gatewaypolicies/status verbs: - get - patch - update -{{- if (mustHas "resource-apis" .Values.global.experiments) }} -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroutes - - httproutes - - tcproutes - - proxyconfigurations - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroutes/status - - httproutes/status - - tcproutes/status - - proxyconfigurations/status - verbs: - - get - - patch - - update -{{- end }} - apiGroups: [ "" ] resources: [ "secrets", "serviceaccounts", "endpoints", "services", "namespaces", "nodes" ] verbs: @@ -160,7 +105,6 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations - - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/templates/connect-inject-deployment.yaml b/charts/consul/templates/connect-inject-deployment.yaml index 53f894035a..eebdc00baf 100644 --- a/charts/consul/templates/connect-inject-deployment.yaml +++ b/charts/consul/templates/connect-inject-deployment.yaml @@ -11,7 +11,6 @@ {{- $dnsRedirectionEnabled := (or (and (ne (.Values.dns.enableRedirection | toString) "-") .Values.dns.enableRedirection) (and (eq (.Values.dns.enableRedirection | toString) "-") .Values.connectInject.transparentProxy.defaultEnabled)) -}} {{ template "consul.validateRequiredCloudSecretsExist" . }} {{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateResourceAPIs" . }} # The deployment for running the Connect sidecar injector apiVersion: apps/v1 kind: Deployment @@ -50,7 +49,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.connectInject.annotations }} {{- tpl .Values.connectInject.annotations . | nindent 8 }} {{- end }} @@ -151,9 +149,6 @@ spec: -release-namespace="{{ .Release.Namespace }}" \ -resource-prefix={{ template "consul.fullname" . }} \ -listen=:8080 \ - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true \ - {{- end }} {{- range $k, $v := .Values.connectInject.consulNode.meta }} -node-meta={{ $k }}={{ $v }} \ {{- end }} diff --git a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml index e65c386636..e4fe79f621 100644 --- a/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml +++ b/charts/consul/templates/connect-inject-mutatingwebhookconfiguration.yaml @@ -333,29 +333,6 @@ webhooks: resources: - samenessgroups sideEffects: None -{{- if (mustHas "resource-apis" .Values.global.experiments) }} -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /mutate-v2beta1-trafficpermissions - failurePolicy: Fail - name: mutate-trafficpermissions.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - trafficpermissions - sideEffects: None -{{- end }} {{- end }} - admissionReviewVersions: - v1beta1 diff --git a/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml b/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml deleted file mode 100644 index 8d01ace911..0000000000 --- a/charts/consul/templates/connect-inject-validatingwebhookconfiguration.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if (or (and (ne (.Values.connectInject.enabled | toString) "-") .Values.connectInject.enabled) (and (eq (.Values.connectInject.enabled | toString) "-") .Values.global.enabled)) }} -# The ValidatingWebhookConfiguration to enable the Connect injector. -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: connect-injector -webhooks: -- name: validate-gatewaypolicy.consul.hashicorp.com - matchPolicy: Equivalent - rules: - - operations: [ "CREATE" , "UPDATE" ] - apiGroups: [ "consul.hashicorp.com" ] - apiVersions: [ "v1alpha1" ] - resources: [ "gatewaypolicies" ] - failurePolicy: Fail - sideEffects: None - admissionReviewVersions: - - v1 - clientConfig: - service: - name: {{ template "consul.fullname" . }}-connect-injector - namespace: {{ .Release.Namespace }} - path: /validate-v1alpha1-gatewaypolicy -{{- end }} diff --git a/charts/consul/templates/crd-controlplanerequestlimits.yaml b/charts/consul/templates/crd-controlplanerequestlimits.yaml index 1939a8d373..326445e05b 100644 --- a/charts/consul/templates/crd-controlplanerequestlimits.yaml +++ b/charts/consul/templates/crd-controlplanerequestlimits.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -192,4 +197,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-exportedservices.yaml b/charts/consul/templates/crd-exportedservices.yaml index 081a2b0cf0..dd6b6ba3b8 100644 --- a/charts/consul/templates/crd-exportedservices.yaml +++ b/charts/consul/templates/crd-exportedservices.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -136,4 +141,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclassconfigs.yaml b/charts/consul/templates/crd-gatewayclassconfigs.yaml index 130db72a22..98ecb345f3 100644 --- a/charts/consul/templates/crd-gatewayclassconfigs.yaml +++ b/charts/consul/templates/crd-gatewayclassconfigs.yaml @@ -1,15 +1,19 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -198,4 +202,10 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-gatewayclasses-external.yaml b/charts/consul/templates/crd-gatewayclasses.yaml similarity index 99% rename from charts/consul/templates/crd-gatewayclasses-external.yaml rename to charts/consul/templates/crd-gatewayclasses.yaml index 391637b5f7..7b3677a8aa 100644 --- a/charts/consul/templates/crd-gatewayclasses-external.yaml +++ b/charts/consul/templates/crd-gatewayclasses.yaml @@ -1,7 +1,6 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-gatewaypolicies.yaml b/charts/consul/templates/crd-gatewaypolicies.yaml deleted file mode 100644 index 1cdfa331f5..0000000000 --- a/charts/consul/templates/crd-gatewaypolicies.yaml +++ /dev/null @@ -1,282 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: gatewaypolicies.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayPolicy - listKind: GatewayPolicyList - plural: gatewaypolicies - singular: gatewaypolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayPolicy is the Schema for the gatewaypolicies API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayPolicySpec defines the desired state of GatewayPolicy. - properties: - default: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - override: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - targetRef: - description: TargetRef identifies an API object to apply policy to. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - minLength: 1 - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 253 - minLength: 1 - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. When - unspecified, the local namespace is inferred. Even when policy - targets a resource in a different namespace, it may only apply - to traffic originating from the same namespace as the policy. - maxLength: 253 - minLength: 1 - type: string - sectionName: - description: SectionName refers to the listener targeted by this - policy. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - group - - kind - - name - type: object - required: - - targetRef - type: object - status: - description: GatewayPolicyStatus defines the observed state of the gateway. - properties: - conditions: - description: "Conditions describe the current conditions of the Policy. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-gateways-external.yaml b/charts/consul/templates/crd-gateways.yaml similarity index 99% rename from charts/consul/templates/crd-gateways-external.yaml rename to charts/consul/templates/crd-gateways.yaml index ab56d4f5fb..cc8ca2dbbe 100644 --- a/charts/consul/templates/crd-gateways-external.yaml +++ b/charts/consul/templates/crd-gateways.yaml @@ -1,7 +1,6 @@ {{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 - apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/charts/consul/templates/crd-grpcroutes-external.yaml b/charts/consul/templates/crd-grpcroutes-external.yaml deleted file mode 100644 index 3e4aa75853..0000000000 --- a/charts/consul/templates/crd-grpcroutes-external.yaml +++ /dev/null @@ -1,769 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: grpcroutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of GRPCRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - method: - type: Exact - description: Rules are a list of GRPC matchers, filters and actions. - items: - description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. - properties: - filters: - description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" - items: - description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " - enum: - - ResponseHeaderModifier - - RequestHeaderModifier - - RequestMirror - - ExtensionRef - type: string - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - method: - type: Exact - description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." - items: - description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" - properties: - headers: - description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. - items: - description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. - properties: - name: - description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: Type specifies how to match against the value of the header. - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of the gRPC Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - default: - type: Exact - description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. - properties: - method: - description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." - maxLength: 1024 - pattern: ^[A-Za-z_][A-Za-z_0-9]*$ - type: string - service: - description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." - maxLength: 1024 - pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ - type: string - type: - default: Exact - description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - RegularExpression - type: string - type: object - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of GRPCRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-grpcroutes.yaml b/charts/consul/templates/crd-grpcroutes.yaml index 31812fff35..642f3547c8 100644 --- a/charts/consul/templates/crd-grpcroutes.yaml +++ b/charts/consul/templates/crd-grpcroutes.yaml @@ -1,617 +1,768 @@ -{{- if .Values.connectInject.enabled }} +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: grpcroutes.mesh.consul.hashicorp.com + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io spec: - group: mesh.consul.hashicorp.com + group: gateway.networking.k8s.io names: + categories: + - gateway-api kind: GRPCRoute listKind: GRPCRouteList plural: grpcroutes - shortNames: - - grpc-route singular: grpcroute scope: Namespaced versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GRPCRoute is the Schema for the GRPC Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this GRPCRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed. \n GRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated. \n Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1. \n Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge. \n Support: Extended" + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label MUST appear by itself as the first label. \n If a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches GRPCRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `test.example.com` and `*.example.com` would both match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match. \n If both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n If a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n The rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - method: + type: Exact + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: GRPCRouteRule defines the semantics for matching an gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status. \n See the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of GRPC matchers, filters and actions. - items: - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. Failure behavior here depends on - how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the GRPCBackendRef definition for the rules about what - makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. + filters: + description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string + required: + - group + - kind + - name type: object - type: - description: Type identifies the resource's type. + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" properties: - name: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - value: + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string - value: + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations that support GRPCRoute. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. Support: Core" + items: + description: GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" properties: - name: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - value: + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string - value: + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies gRPC request header matchers. - Multiple match values are ANDed together, meaning, a - request MUST match all the specified headers to select - the route. - items: + required: + - backendRef + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " properties: - name: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations supporting GRPCRoute MUST support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` MUST be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n " + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - method: + type: Exact + description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - method: service: foo.bar headers: values: version: 2 - method: service: foo.bar.v2 ``` \n For a request to match against this rule, it MUST satisfy EITHER of the two conditions: \n - service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2 \n See the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together. \n If no matches are specified, the implementation MUST match every gRPC request. \n Proxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria." + items: + description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header: \n ``` matches: - method: type: Exact service: \"foo\" headers: - name: \"version\" value \"v1\" \n ```" + properties: + headers: + description: Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route. + items: + description: GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers. + properties: + name: + description: "Name is the name of the gRPC Header to be matched. \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + default: + type: Exact + description: Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match. + properties: + method: + description: "Value of the method to match against. If left empty or omitted, will match all services. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Method must be a valid Protobuf Method (https://protobuf.com/docs/language-spec#methods)." + maxLength: 1024 + pattern: ^[A-Za-z_][A-Za-z_0-9]*$ + type: string + service: + description: "Value of the service to match against. If left empty or omitted, will match any service. \n At least one of Service and Method MUST be a non-empty string. \n A GRPC Service must be a valid Protobuf Type Name (https://protobuf.com/docs/language-spec#type-references)." + maxLength: 1024 + pattern: ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$ type: string type: - description: "HeaderMatchType specifies the semantics - of how HTTP header values should be compared. - Valid HeaderMatchType values, along with their - conformance levels, are: \n Note that values may - be added to this enum, implementations must ensure - that unknown values will not cause a crash. \n - Unknown values here must result in the implementation - setting the Accepted Condition for the Route to - status: False, with a Reason of UnsupportedValue." + default: Exact + description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified) \n Support: Implementation-specific (Exact with method specified but no service specified) \n Support: Implementation-specific (RegularExpression)" enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: + - Exact + - RegularExpression type: string type: object - type: array - method: - description: Method specifies a gRPC request service/method - matcher. If this field is not specified, all services - and methods will match. - properties: - method: - description: "Value of the method to match against. - If left empty or omitted, will match all services. - \n At least one of Service and Method MUST be a - non-empty string.}" - type: string - service: - description: "Value of the service to match against. - If left empty or omitted, will match any service. - \n At least one of Service and Method MUST be a - non-empty string." - type: string - type: - description: 'Type specifies how to match against - the service and/or method. Support: Core (Exact - with service and method specified)' - enum: - - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED - - GRPC_METHOD_MATCH_TYPE_EXACT - - GRPC_METHOD_MATCH_TYPE_REGEX - format: int32 - type: string - type: object - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" properties: - value: - description: The uint32 value. - format: int32 + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 + maximum: 65535 + minimum: 1 type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-httproutes-external.yaml b/charts/consul/templates/crd-httproutes-external.yaml deleted file mode 100644 index c89591376a..0000000000 --- a/charts/consul/templates/crd-httproutes-external.yaml +++ /dev/null @@ -1,1917 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: httproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. - name: v1alpha2 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.hostnames - name: Hostnames - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of HTTPRoute. - properties: - hostnames: - description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" - items: - description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." - maxLength: 253 - minLength: 1 - pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - maxItems: 16 - type: array - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - default: - - matches: - - path: - type: PathPrefix - value: / - description: Rules are a list of HTTP matchers, filters and actions. - items: - description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" - items: - description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. - properties: - filters: - description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - type: array - filters: - description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" - items: - description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. - properties: - extensionRef: - description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" - properties: - group: - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - requestHeaderModifier: - description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - requestMirror: - description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" - properties: - backendRef: - description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - required: - - name - type: object - required: - - backendRef - type: object - requestRedirect: - description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" - properties: - hostname: - description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - port: - description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" - format: int32 - maximum: 65535 - minimum: 1 - type: integer - scheme: - description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" - enum: - - http - - https - type: string - statusCode: - default: 302 - description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" - enum: - - 301 - - 302 - type: integer - type: object - responseHeaderModifier: - description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " - properties: - add: - description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - remove: - description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" - items: - type: string - maxItems: 16 - type: array - set: - description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" - items: - description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - type: - description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - RequestHeaderModifier - - ResponseHeaderModifier - - RequestMirror - - RequestRedirect - - URLRewrite - - ExtensionRef - type: string - urlRewrite: - description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " - properties: - hostname: - description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - path: - description: "Path defines a path rewrite. \n Support: Extended \n " - properties: - replaceFullPath: - description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " - maxLength: 1024 - type: string - replacePrefixMatch: - description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " - maxLength: 1024 - type: string - type: - description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " - enum: - - ReplaceFullPath - - ReplacePrefixMatch - type: string - required: - - type - type: object - type: object - required: - - type - type: object - maxItems: 16 - type: array - matches: - default: - - path: - type: PathPrefix - value: / - description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." - items: - description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" - properties: - headers: - description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. - items: - description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. - properties: - name: - description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." - maxLength: 256 - minLength: 1 - pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP Header to be matched. - maxLength: 4096 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - method: - description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" - enum: - - GET - - HEAD - - POST - - PUT - - DELETE - - CONNECT - - OPTIONS - - TRACE - - PATCH - type: string - path: - default: - type: PathPrefix - value: / - description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. - properties: - type: - default: PathPrefix - description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" - enum: - - Exact - - PathPrefix - - RegularExpression - type: string - value: - default: / - description: Value of the HTTP path to match against. - maxLength: 1024 - type: string - type: object - queryParams: - description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" - items: - description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. - properties: - name: - description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." - maxLength: 256 - minLength: 1 - type: string - type: - default: Exact - description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." - enum: - - Exact - - RegularExpression - type: string - value: - description: Value is the value of HTTP query param to be matched. - maxLength: 1024 - minLength: 1 - type: string - required: - - name - - value - type: object - maxItems: 16 - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - type: object - maxItems: 8 - type: array - type: object - maxItems: 16 - type: array - type: object - status: - description: Status defines the current state of HTTPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-httproutes.yaml b/charts/consul/templates/crd-httproutes.yaml index 3da6e1e637..69663dd208 100644 --- a/charts/consul/templates/crd-httproutes.yaml +++ b/charts/consul/templates/crd-httproutes.yaml @@ -1,673 +1,1916 @@ -{{- if .Values.connectInject.enabled }} +{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: httproutes.mesh.consul.hashicorp.com + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io spec: - group: mesh.consul.hashicorp.com + group: gateway.networking.k8s.io names: + categories: + - gateway-api kind: HTTPRoute listKind: HTTPRouteList plural: httproutes - shortNames: - - http-route singular: httproute scope: Namespaced versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: HTTPRoute is the Schema for the HTTP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this HTTPRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + deprecated: true + deprecationWarning: The v1alpha2 version of HTTPRoute has been deprecated and will be removed in a future release of the API. Please upgrade to v1beta1. + name: v1alpha2 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" + properties: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https + type: string + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " + properties: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 + type: string + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map type: object - type: - description: Type identifies the resource's type. + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 type: string - kind: - description: Kind identifies the specific resource type - within the group. + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - type: object - type: object - type: array - rules: - description: Rules are a list of HTTP-based routing rules that this - route should use for constructing a routing table. - items: - description: HTTPRouteRule specifies the routing rules used to determine - what upstream service an HTTP request is routed to. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. \n Failure behavior here depends - on how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the HTTPBackendRef definition for the rules about what - makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: "Hostnames defines a set of hostname that should match against the HTTP Host header to select a HTTPRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n If a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example: \n * A Listener with `test.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames, or have specified at least one of `test.example.com` or `*.example.com`. * A Listener with `*.example.com` as the hostname matches HTTPRoutes that have either not specified any hostnames or have specified at least one hostname that matches the Listener hostname. For example, `*.example.com`, `test.example.com`, and `foo.test.example.com` would all match. On the other hand, `example.com` and `test.example.net` would not match. \n Hostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`. \n If both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match. \n If both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus. \n In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of: \n * Characters in a matching non-wildcard hostname. * Characters in a matching hostname. \n If ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over. \n Support: Core" + items: + description: "Hostname is the fully qualified domain name of a network host. This matches the RFC 1123 definition of a hostname with 2 notable exceptions: \n 1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard label must appear by itself as the first label. \n Hostname can be \"precise\" which is a domain name without the terminating dot of a network host (e.g. \"foo.example.com\") or \"wildcard\", which is a domain name prefixed with a single wildcard label (e.g. `*.example.com`). \n Note that as per RFC1035 and RFC1123, a *label* must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character. No other punctuation is allowed." + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. \n Failure behavior here depends on how many BackendRefs are specified and how many are invalid. \n If *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code. \n See the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code. \n For example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Core" + items: + description: HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. + properties: + filters: + description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here. \n Support: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string + required: + - group + - kind + - name type: object - type: - description: Type identifies the resource's type. + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" properties: - name: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - value: + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string - value: + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " properties: - name: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 type: string - value: + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch type: string + required: + - type type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " properties: - name: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 type: string - value: + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch type: string + required: + - type type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object + type: object + required: + - type + type: object + maxItems: 16 + type: array + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + maxItems: 16 + type: array + filters: + description: "Filters define the filters that are applied to requests that match this rule. \n The effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage. \n Conformance-levels at this level are defined based on the type of filter: \n - ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across implementations. \n Specifying a core filter multiple times has unspecified or implementation-specific conformance. \n All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation can not support other combinations of filters, they must clearly document that limitation. In all cases where incompatible or unsupported filters are specified, implementations MUST add a warning condition to status. \n Support: Core" + items: + description: HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters. \n Support: Implementation-specific" + properties: + group: + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: + requestHeaderModifier: + description: "RequestHeaderModifier defines a schema for a filter that modifies request headers. \n Support: Core" + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. \n Support: Extended" + properties: + backendRef: + description: "BackendRef references a resource where mirrored requests are sent. \n If the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation. \n If there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation. \n In either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem. \n Support: Extended for Kubernetes Service \n Support: Implementation-specific for any other resource" properties: - name: + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - value: + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 type: string - value: + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: + required: + - backendRef + type: object + requestRedirect: + description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection. \n Support: Core" + properties: + hostname: + description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname of the request is used. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is. \n Support: Extended \n " properties: - name: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 type: string - value: + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch type: string + required: + - type type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: + port: + description: "Port is the port to be used in the value of the `Location` header in the response. When empty, port (if specified) of the request is used. \n Support: Extended" + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Extended" + enum: + - http + - https type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: + statusCode: + default: 302 + description: "StatusCode is the HTTP status code to be used in response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n Support: Core" + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers. \n Support: Extended \n " + properties: + add: + description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: add: - name: \"my-header\" value: \"bar,baz\" \n Output: GET /foo HTTP/1.1 my-header: foo,bar,baz" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). \n Input: GET /foo HTTP/1.1 my-header1: foo my-header2: bar my-header3: baz \n Config: remove: [\"my-header1\", \"my-header3\"] \n Output: GET /foo HTTP/1.1 my-header2: bar" + items: + type: string + maxItems: 16 + type: array + set: + description: "Set overwrites the request with the given header (name, value) before the action. \n Input: GET /foo HTTP/1.1 my-header: foo \n Config: set: - name: \"my-header\" value: \"bar\" \n Output: GET /foo HTTP/1.1 my-header: bar" + items: + description: HTTPHeader represents an HTTP Header name and value as defined by RFC 7230. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: \n - Core: Filter types and their corresponding configuration defined by \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All implementations must support core filters. \n - Extended: Filter types and their corresponding configuration defined by \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers are encouraged to support extended filters. \n - Implementation-specific: Filters that are defined and supported by specific vendors. In the future, filters showing convergence in behavior across multiple implementations will be considered for inclusion in extended or core conformance levels. Filter-specific configuration for such filters is specified using the ExtensionRef field. `Type` should be set to \"ExtensionRef\" for custom filters. \n Implementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior. \n If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: "URLRewrite defines a schema for a filter that modifies a request during forwarding. \n Support: Extended \n " + properties: + hostname: + description: "Hostname is the value to be used to replace the Host header value during forwarding. \n Support: Extended \n " + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: "Path defines a path rewrite. \n Support: Extended \n " properties: - name: + replaceFullPath: + description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect. \n " + maxLength: 1024 + type: string + replacePrefixMatch: + description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" would be modified to \"/bar\". \n Note that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not. \n " + maxLength: 1024 type: string - value: + type: + description: "Type defines the type of path modifier. Additional types may be added in a future release of the API. \n Note that values may be added to this enum, implementations must ensure that unknown values will not cause a crash. \n Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. \n " + enum: + - ReplaceFullPath + - ReplacePrefixMatch type: string + required: + - type type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies HTTP request header matchers. - Multiple match values are ANDed together, meaning, a - request must match all the specified headers to select - the route. - items: - properties: - invert: - description: 'NOTE: not in gamma; service-router - compat' - type: boolean - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case insensitive. - (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent header - names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST be - ignored. Due to the case-insensitivity of header - names, “foo” and “Foo” are considered equivalent. - \n When a header is repeated in an HTTP request, - it is implementation-specific behavior as to how - this is represented. Generally, proxies should - follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 - regarding processing a repeated header, with special - handling for “Set-Cookie”." - type: string - type: - description: Type specifies how to match against - the value of the header. - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - description: Value is the value of HTTP Header to - be matched. - type: string type: object - type: array - method: - description: Method specifies HTTP method matcher. When - specified, this route will be matched only if the request - has the specified method. - type: string - path: - description: Path specifies a HTTP request path matcher. - If this field is not specified, a default prefix match - on the “/” path is provided. - properties: - type: - description: Type specifies how to match against the - path Value. - enum: - - PATH_MATCH_TYPE_UNSPECIFIED - - PATH_MATCH_TYPE_EXACT - - PATH_MATCH_TYPE_PREFIX - - PATH_MATCH_TYPE_REGEX - format: int32 - type: string - value: - description: Value of the HTTP path to match against. - type: string - type: object - queryParams: - description: QueryParams specifies HTTP query parameter - matchers. Multiple match values are ANDed together, - meaning, a request must match all the specified query - parameters to select the route. - items: + required: + - type + type: object + maxItems: 16 + type: array + matches: + default: + - path: + type: PathPrefix + value: / + description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied. \n For example, take the following matches configuration: \n ``` matches: - path: value: \"/foo\" headers: - name: \"version\" value: \"v2\" - path: value: \"/v2/foo\" ``` \n For a request to match against this rule, a request must satisfy EITHER of the two conditions: \n - path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo` \n See the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together. \n If no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request. \n Proxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match with the largest number of: \n * Characters in a matching path. * Header matches. * Query param matches. \n If ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties: \n * The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by \"{namespace}/{name}\". \n If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria. \n When no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned." + items: + description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied. \n For example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header: \n ``` match: \n \tpath: \t value: \"/foo\" \theaders: \t- name: \"version\" \t value \"v1\" \n ```" + properties: + headers: + description: Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route. + items: + description: HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers. + properties: + name: + description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). \n If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent. \n When a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\"." + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the header. \n Support: Core (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method. \n Support: Extended" + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the "/" path is provided. properties: - name: - description: "Name is the name of the HTTP query - param to be matched. This must be an exact string - match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). - \n If multiple entries specify equivalent query - param names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it is recommended that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - \n Users SHOULD NOT route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." - type: string type: - description: Type specifies how to match against - the value of the query parameter. + default: PathPrefix + description: "Type specifies how to match against the path Value. \n Support: Core (Exact, PathPrefix) \n Support: Implementation-specific (RegularExpression)" enum: - - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED - - QUERY_PARAM_MATCH_TYPE_EXACT - - QUERY_PARAM_MATCH_TYPE_REGEX - - QUERY_PARAM_MATCH_TYPE_PRESENT - format: int32 + - Exact + - PathPrefix + - RegularExpression type: string value: - description: Value is the value of HTTP query param - to be matched. + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 type: string type: object - type: array - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. + queryParams: + description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route. \n Support: Extended" + items: + description: HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters. + properties: + name: + description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). \n If multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored. \n If a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API. \n Users SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations." + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: "Type specifies how to match against the value of the query parameter. \n Support: Extended (Exact) \n Support: Implementation-specific (RegularExpression) \n Since RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect." + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 8 + type: array + type: object + maxItems: 16 + type: array + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" properties: - value: - description: The uint32 value. - format: int32 + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 + maximum: 65535 + minimum: 1 type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-ingressgateways.yaml b/charts/consul/templates/crd-ingressgateways.yaml index dcbc543525..51c02422b2 100644 --- a/charts/consul/templates/crd-ingressgateways.yaml +++ b/charts/consul/templates/crd-ingressgateways.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -444,4 +449,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-jwtproviders.yaml b/charts/consul/templates/crd-jwtproviders.yaml index 94c9697b33..9f97922eb5 100644 --- a/charts/consul/templates/crd-jwtproviders.yaml +++ b/charts/consul/templates/crd-jwtproviders.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -109,7 +114,8 @@ spec: cacheDuration: description: "CacheDuration is the duration after which cached keys should be expired. \n Default value is 5 minutes." - type: string + format: int64 + type: integer fetchAsynchronously: description: "FetchAsynchronously indicates that the JWKS should be fetched when a client request arrives. Client @@ -118,60 +124,61 @@ spec: before being activated. \n Default value is false." type: boolean jwksCluster: - description: JWKSCluster defines how the specified Remote - JWKS URI is to be fetched. + description: "JWKSCluster defines how the specified Remote JWKS + URI is to be fetched." properties: connectTimeout: - description: The timeout for new network connections to - hosts in the cluster. If not set, a default value of - 5s will be used. - type: string + description: "The timeout for new network connections to hosts + in the cluster. \n If not set, a default value of 5s will be + used." + format: int64 + type: integer discoveryType: - description: "DiscoveryType refers to the service discovery - type to use for resolving the cluster. \n This defaults - to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, - EDS or ORIGINAL_DST." + description: "DiscoveryType refers to the service discovery type + to use for resolving the cluster. \n Defaults to STRICT_DNS." type: string tlsCertificates: description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying - a presented peer certificate. If not specified and a - peer certificate is presented it will not be verified. - \n Must be either CaCertificateProviderInstance or TrustedCA." + certificate authority certificates to use in verifying a presented + peer certificate." properties: caCertificateProviderInstance: - description: CaCertificateProviderInstance Certificate - provider instance for fetching TLS certificates. + description: "CaCertificateProviderInstance Certificate provider + instance for fetching TLS certificates." properties: - certificateName: - description: "CertificateName is used to specify - certificate instances or types. For example, - \"ROOTCA\" to specify a root-certificate (validation - context) or \"example.com\" to specify a certificate - for a particular domain. \n The default value - is the empty string." - type: string instanceName: - description: "InstanceName refers to the certificate - provider instance name. \n The default value - is \"default\"." + description: "InstanceName refers to the certificate provider + instance name. \n The default value is 'default'." + type: string + certificateName: + description: "CertificateName is used to specify certificate + instances or types. For example, \"ROOTCA\" to specify a + root-certificate (validation context) or \"example.com\" + to specify a certificate for a particular domain. \n + The default value is the empty string." type: string type: object trustedCA: - description: "TrustedCA defines TLS certificate data - containing certificate authority certificates to - use in verifying a presented peer certificate. \n - Exactly one of Filename, EnvironmentVariable, InlineString - or InlineBytes must be specified." + description: "TrustedCA defines TLS certificate data containing + certificate authority certificates to use in verifying a presented + peer certificate. \n Exactly one of Filename, EnvironmentVariable, + InlineString or InlineBytes must be specified." properties: + filename: + description: "The name of the file on the local system to use a + data source for trusted CA certificates." + type: string environmentVariable: + description: "The environment variable on the local system to use + a data source for trusted CA certificates." type: string - filename: + inlineString: + description: "A string to inline in the configuration for use as + a data source for trusted CA certificates." type: string inlineBytes: - format: byte - type: string - inlineString: + description: "A sequence of bytes to inline in the configuration + for use as a data source for trusted CA certificates." type: string type: object type: object @@ -191,20 +198,22 @@ spec: of 10s. \n Default value is 0." type: integer retryPolicyBackOff: - description: "Retry's backoff policy. \n Defaults to Envoy's - backoff policy." + description: "Backoff policy \n Defaults to Envoy's backoff + policy" properties: baseInterval: description: "BaseInterval to be used for the next - back off computation. \n The default value from - envoy is 1s." - type: string + back off computation \n The default value from envoy + is 1s" + format: int64 + type: integer maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval." - type: string + to 10 times BaseInterval" + format: int64 + type: integer type: object type: object uri: @@ -310,4 +319,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshes.yaml b/charts/consul/templates/crd-meshes.yaml index f8ce4fc12e..b1b2319579 100644 --- a/charts/consul/templates/crd-meshes.yaml +++ b/charts/consul/templates/crd-meshes.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -204,4 +209,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-meshservices.yaml b/charts/consul/templates/crd-meshservices.yaml index a5d36fb966..d52da5a028 100644 --- a/charts/consul/templates/crd-meshservices.yaml +++ b/charts/consul/templates/crd-meshservices.yaml @@ -1,15 +1,19 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -53,4 +57,10 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringacceptors.yaml b/charts/consul/templates/crd-peeringacceptors.yaml index 2352ba7ad3..6f335e83a2 100644 --- a/charts/consul/templates/crd-peeringacceptors.yaml +++ b/charts/consul/templates/crd-peeringacceptors.yaml @@ -1,15 +1,20 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -143,4 +148,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-peeringdialers.yaml b/charts/consul/templates/crd-peeringdialers.yaml index 09991d2091..5fa49f1eed 100644 --- a/charts/consul/templates/crd-peeringdialers.yaml +++ b/charts/consul/templates/crd-peeringdialers.yaml @@ -1,15 +1,20 @@ {{- if and .Values.connectInject.enabled .Values.global.peering.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -143,4 +148,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-proxyconfigurations.yaml b/charts/consul/templates/crd-proxyconfigurations.yaml deleted file mode 100644 index 9a33bd2bab..0000000000 --- a/charts/consul/templates/crd-proxyconfigurations.yaml +++ /dev/null @@ -1,423 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: proxyconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: ProxyConfiguration - listKind: ProxyConfigurationList - plural: proxyconfigurations - shortNames: - - proxy-configuration - singular: proxyconfiguration - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: ProxyConfiguration is the Schema for the TCP Routes API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: This is a Resource type. - properties: - bootstrapConfig: - description: bootstrap_config is the configuration that requires proxies - to be restarted to be applied. - properties: - dogstatsdUrl: - type: string - overrideJsonTpl: - type: string - prometheusBindAddr: - type: string - readyBindAddr: - type: string - staticClustersJson: - type: string - staticListenersJson: - type: string - statsBindAddr: - type: string - statsConfigJson: - type: string - statsFlushInterval: - type: string - statsSinksJson: - type: string - statsTags: - items: - type: string - type: array - statsdUrl: - type: string - telemetryCollectorBindSocketDir: - type: string - tracingConfigJson: - type: string - type: object - dynamicConfig: - description: dynamic_config is the configuration that could be changed - dynamically (i.e. without needing restart). - properties: - accessLogs: - description: AccessLogs configures the output and format of Envoy - access logs - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have - a matching listener filter. - type: boolean - enabled: - description: Enabled turns off all access logging - type: boolean - jsonFormat: - description: The presence of one format string or the other - implies the access log string encoding. Defining both is - invalid. - type: string - path: - description: Path is the output file to write logs - type: string - textFormat: - type: string - type: - description: 'Type selects the output for logs: "file", "stderr". - "stdout"' - enum: - - LOG_SINK_TYPE_DEFAULT - - LOG_SINK_TYPE_FILE - - LOG_SINK_TYPE_STDERR - - LOG_SINK_TYPE_STDOUT - format: int32 - type: string - type: object - envoyExtensions: - items: - description: EnvoyExtension has configuration for an extension - that patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - consulVersion: - type: string - envoyVersion: - type: string - name: - type: string - required: - type: boolean - type: object - type: array - exposeConfig: - properties: - exposePaths: - items: - properties: - listenerPort: - format: int32 - type: integer - localPathPort: - format: int32 - type: integer - path: - type: string - protocol: - enum: - - EXPOSE_PATH_PROTOCOL_HTTP - - EXPOSE_PATH_PROTOCOL_HTTP2 - format: int32 - type: string - type: object - type: array - type: object - inboundConnections: - description: inbound_connections configures inbound connections - to the proxy. - properties: - balanceInboundConnections: - enum: - - BALANCE_CONNECTIONS_DEFAULT - - BALANCE_CONNECTIONS_EXACT - format: int32 - type: string - maxInboundConnections: - format: int64 - type: integer - type: object - listenerTracingJson: - type: string - localClusterJson: - type: string - localConnection: - additionalProperties: - description: Referenced by ProxyConfiguration - properties: - connectTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - requestTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - description: local_connection is the configuration that should - be used to connect to the local application provided per-port. - The map keys should correspond to port names on the workload. - type: object - localWorkloadAddress: - description: "deprecated: local_workload_address, local_workload_port, - and local_workload_socket_path are deprecated and are only needed - for migration of existing resources. \n Deprecated: Marked as - deprecated in pbmesh/v2beta1/proxy_configuration.proto." - type: string - localWorkloadPort: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - format: int32 - type: integer - localWorkloadSocketPath: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - type: string - meshGatewayMode: - enum: - - MESH_GATEWAY_MODE_UNSPECIFIED - - MESH_GATEWAY_MODE_NONE - - MESH_GATEWAY_MODE_LOCAL - - MESH_GATEWAY_MODE_REMOTE - format: int32 - type: string - mode: - description: mode indicates the proxy's mode. This will default - to 'transparent'. - enum: - - PROXY_MODE_DEFAULT - - PROXY_MODE_TRANSPARENT - - PROXY_MODE_DIRECT - format: int32 - type: string - mutualTlsMode: - enum: - - MUTUAL_TLS_MODE_DEFAULT - - MUTUAL_TLS_MODE_STRICT - - MUTUAL_TLS_MODE_PERMISSIVE - format: int32 - type: string - publicListenerJson: - type: string - transparentProxy: - properties: - dialedDirectly: - description: dialed_directly indicates whether this proxy - should be dialed using original destination IP in the connection - rather than load balance between all endpoints. - type: boolean - outboundListenerPort: - description: outbound_listener_port is the port for the proxy's - outbound listener. This defaults to 15001. - format: int32 - type: integer - type: object - type: object - opaqueConfig: - description: "deprecated: prevent usage when using v2 APIs directly. - needed for backwards compatibility \n Deprecated: Marked as deprecated - in pbmesh/v2beta1/proxy_configuration.proto." - type: object - x-kubernetes-preserve-unknown-fields: true - workloads: - description: Selection of workloads this proxy configuration should - apply to. These can be prefixes or specific workload names. - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-proxydefaults.yaml b/charts/consul/templates/crd-proxydefaults.yaml index ce49c9149a..7bc5c6078a 100644 --- a/charts/consul/templates/crd-proxydefaults.yaml +++ b/charts/consul/templates/crd-proxydefaults.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -190,16 +195,6 @@ spec: your services secure, we recommend using "strict" mode whenever possible and enabling "permissive" mode only when necessary.' type: string - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -262,4 +257,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-referencegrants-external.yaml b/charts/consul/templates/crd-referencegrants.yaml similarity index 100% rename from charts/consul/templates/crd-referencegrants-external.yaml rename to charts/consul/templates/crd-referencegrants.yaml index 6ae177d987..d50211291d 100644 --- a/charts/consul/templates/crd-referencegrants-external.yaml +++ b/charts/consul/templates/crd-referencegrants.yaml @@ -7,15 +7,15 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null name: referencegrants.gateway.networking.k8s.io spec: group: gateway.networking.k8s.io diff --git a/charts/consul/templates/crd-routeauthfilters.yaml b/charts/consul/templates/crd-routeauthfilters.yaml deleted file mode 100644 index a51bf226cd..0000000000 --- a/charts/consul/templates/crd-routeauthfilters.yaml +++ /dev/null @@ -1,199 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routeauthfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteAuthFilter - listKind: RouteAuthFilterList - plural: routeauthfilters - singular: routeauthfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteAuthFilter is the Schema for the routeauthfilters API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. - properties: - jwt: - description: This re-uses the JWT requirement type from Gateway Policy - Types. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the actual - claim information to be verified. - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the given - path: - If the type at the path is a list then we - verify that this value is contained in the list. - \n - If the type at the path is a string then we - verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - status: - description: RouteAuthFilterStatus defines the observed state of the gateway. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs - description: "Conditions describe the current conditions of the Filter. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-routeretryfilters.yaml b/charts/consul/templates/crd-routeretryfilters.yaml deleted file mode 100644 index 14b6062f60..0000000000 --- a/charts/consul/templates/crd-routeretryfilters.yaml +++ /dev/null @@ -1,115 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routeretryfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteRetryFilter - listKind: RouteRetryFilterList - plural: routeretryfilters - singular: routeretryfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteRetryFilter is the Schema for the routeretryfilters API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. - properties: - numRetries: - format: int32 - minimum: 0 - type: integer - retryOn: - items: - type: string - type: array - retryOnConnectFailure: - type: boolean - retryOnStatusCodes: - items: - format: int32 - type: integer - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-routetimeoutfilters.yaml b/charts/consul/templates/crd-routetimeoutfilters.yaml deleted file mode 100644 index 95ab50320d..0000000000 --- a/charts/consul/templates/crd-routetimeoutfilters.yaml +++ /dev/null @@ -1,105 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: routetimeoutfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteTimeoutFilter - listKind: RouteTimeoutFilterList - plural: routetimeoutfilters - singular: routetimeoutfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. - properties: - idleTimeout: - type: string - requestTimeout: - type: string - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-samenessgroups.yaml b/charts/consul/templates/crd-samenessgroups.yaml index ea0ad7c8a0..179972a9d6 100644 --- a/charts/consul/templates/crd-samenessgroups.yaml +++ b/charts/consul/templates/crd-samenessgroups.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -126,4 +131,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicedefaults.yaml b/charts/consul/templates/crd-servicedefaults.yaml index c7e2b5bb2b..9e6c304bec 100644 --- a/charts/consul/templates/crd-servicedefaults.yaml +++ b/charts/consul/templates/crd-servicedefaults.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -187,69 +192,6 @@ spec: unlock usage of the service-splitter and service-router config entries for a service. type: string - rateLimits: - description: RateLimits is rate limiting configuration that is applied - to inbound traffic for a service. Rate limiting is a Consul enterprise - feature. - properties: - instanceLevel: - description: InstanceLevel represents rate limit configuration - that is applied per service instance. - properties: - requestsMaxBurst: - description: "RequestsMaxBurst is the maximum number of requests - that can be sent in a burst. Should be equal to or greater - than RequestsPerSecond. If unset, defaults to RequestsPerSecond. - \n Internally, this is the maximum size of the token bucket - used for rate limiting." - type: integer - requestsPerSecond: - description: "RequestsPerSecond is the average number of requests - per second that can be made without being throttled. This - field is required if RequestsMaxBurst is set. The allowed - number of requests may exceed RequestsPerSecond up to the - value specified in RequestsMaxBurst. \n Internally, this - is the refill rate of the token bucket used for rate limiting." - type: integer - routes: - description: Routes is a list of rate limits applied to specific - routes. For a given request, the first matching route will - be applied, if any. Overrides any top-level configuration. - items: - properties: - pathExact: - description: Exact path to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathPrefix: - description: Prefix to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathRegex: - description: Regex to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - requestsMaxBurst: - description: RequestsMaxBurst is the maximum number - of requests that can be sent in a burst. Should be - equal to or greater than RequestsPerSecond. If unset, - defaults to RequestsPerSecond. Internally, this is - the maximum size of the token bucket used for rate - limiting. - type: integer - requestsPerSecond: - description: RequestsPerSecond is the average number - of requests per second that can be made without being - throttled. This field is required if RequestsMaxBurst - is set. The allowed number of requests may exceed - RequestsPerSecond up to the value specified in RequestsMaxBurst. - Internally, this is the refill rate of the token bucket - used for rate limiting. - type: integer - type: object - type: array - type: object - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -562,4 +504,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceintentions.yaml b/charts/consul/templates/crd-serviceintentions.yaml index 75299f016e..edc7c7078b 100644 --- a/charts/consul/templates/crd-serviceintentions.yaml +++ b/charts/consul/templates/crd-serviceintentions.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -308,4 +313,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-serviceresolvers.yaml b/charts/consul/templates/crd-serviceresolvers.yaml index 6d89125216..bbc2e5b650 100644 --- a/charts/consul/templates/crd-serviceresolvers.yaml +++ b/charts/consul/templates/crd-serviceresolvers.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -225,16 +230,6 @@ spec: type: integer type: object type: object - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied @@ -345,4 +340,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicerouters.yaml b/charts/consul/templates/crd-servicerouters.yaml index 72690c60e4..d36e8028b5 100644 --- a/charts/consul/templates/crd-servicerouters.yaml +++ b/charts/consul/templates/crd-servicerouters.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -309,4 +314,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-servicesplitters.yaml b/charts/consul/templates/crd-servicesplitters.yaml index 8d5ed58023..15f7714a84 100644 --- a/charts/consul/templates/crd-servicesplitters.yaml +++ b/charts/consul/templates/crd-servicesplitters.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -183,4 +188,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tcproutes-external.yaml b/charts/consul/templates/crd-tcproutes-external.yaml deleted file mode 100644 index 91989135e2..0000000000 --- a/charts/consul/templates/crd-tcproutes-external.yaml +++ /dev/null @@ -1,284 +0,0 @@ -{{- if and .Values.connectInject.enabled .Values.connectInject.apiGateway.manageExternalCRDs }} -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - gateway.networking.k8s.io/bundle-version: v0.6.2 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - name: tcproutes.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of TCPRoute. - properties: - parentRefs: - description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - maxItems: 32 - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - description: TCPRouteRule is the configuration for a given rule. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" - items: - description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." - properties: - group: - default: "" - description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Service - description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. - format: int32 - maximum: 65535 - minimum: 1 - type: integer - weight: - default: 1 - description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." - format: int32 - maximum: 1000000 - minimum: 0 - type: integer - required: - - name - type: object - maxItems: 16 - minItems: 1 - type: array - type: object - maxItems: 16 - minItems: 1 - type: array - required: - - rules - type: object - status: - description: Status defines the current state of TCPRoute. - properties: - parents: - description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." - items: - description: RouteParentStatus describes the status of a route with respect to an associated Parent. - properties: - conditions: - description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." - items: - description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - parentRef: - description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: "Name is the name of the referent. \n Support: Core" - maxLength: 253 - minLength: 1 - type: string - namespace: - description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - required: - - controllerName - - parentRef - type: object - maxItems: 32 - type: array - required: - - parents - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] -{{- end }} diff --git a/charts/consul/templates/crd-tcproutes.yaml b/charts/consul/templates/crd-tcproutes.yaml index ae9d2cd080..ba21ccd58a 100644 --- a/charts/consul/templates/crd-tcproutes.yaml +++ b/charts/consul/templates/crd-tcproutes.yaml @@ -1,278 +1,284 @@ -{{- if .Values.connectInject.enabled }} +{{- if and .Values.connectInject.enabled (or .Values.connectInject.apiGateway.manageExternalCRDs .Values.connectInject.apiGateway.manageNonStandardCRDs ) }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/1538 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd - name: tcproutes.mesh.consul.hashicorp.com + gateway.networking.k8s.io/bundle-version: v0.6.2 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io spec: - group: mesh.consul.hashicorp.com + group: gateway.networking.k8s.io names: + categories: + - gateway-api kind: TCPRoute listKind: TCPRouteList plural: tcproutes - shortNames: - - tcp-route singular: tcproute scope: Namespaced versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TCPRoute is the Schema for the TCP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute - \n This is a Resource type." - properties: - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: TCPRoute provides a way to route TCP requests. When combined with a Gateway listener, it can be used to forward connections on the port specified by the listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. \n The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources such as one of the route kinds. \n It is invalid to reference an identical parent more than once. It is valid to reference multiple distinct sections within the same parent resource, such as 2 Listeners within a Gateway. \n It is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged. \n Note that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference." + items: + description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." + properties: + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: "BackendRefs defines the backend(s) where matching requests should be sent. If unspecified or invalid (refers to a non-existent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of connections, then 80% of connections must be rejected instead. \n Support: Core for Kubernetes Service \n Support: Implementation-specific for any other resource \n Support for weight: Extended" + items: + description: "BackendRef defines how a Route should forward a request to a Kubernetes resource. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details." properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." + group: + default: "" + description: Group is the group of the referent. For example, "gateway.networking.k8s.io". When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." + kind: + default: Service + description: Kind is kind of the referent. For example "HTTPRoute" or "Service". Defaults to "Service" when not specified. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred. \n Note that when a namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ type: string + port: + description: Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100. \n If only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1. \n Support for this field varies based on the context where used." + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name type: object - type: - description: Type identifies the resource's type. + maxItems: 16 + minItems: 1 + type: array + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified. \n Note that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for. \n A maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway." + items: + description: RouteParentStatus describes the status of a route with respect to an associated Parent. + properties: + conditions: + description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status. \n If the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why. \n A Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway. \n There are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when: \n * The Route refers to a non-existent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to." + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n \ttype FooStatus struct{ \t // Represents the observations of a foo's current state. \t // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" \t // +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map \t // +listMapKey=type \t Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields \t}" properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 type: string - kind: - description: Kind identifies the specific resource type - within the group. + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string + required: + - lastTransitionTime + - message + - reason + - status + - type type: object - type: object - type: object - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - properties: - backendRefs: - description: BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the underlying implementation MUST actively reject connection - attempts to this backend. Connection rejections must respect - weight; if an invalid backend is requested to have 80% of - connections, then 80% of connections must be rejected instead. - items: + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass. \n Example: \"example.net/gateway-controller\". \n The format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). \n Controllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary." + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of. properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." + group: + default: gateway.networking.k8s.io + description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string). \n Support: Core" + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Implementation-specific (Other Resources)" + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: "Name is the name of the referent. \n Support: Core" + maxLength: 253 + minLength: 1 + type: string + namespace: + description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route. \n Note that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference. \n Support: Core" + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " format: int32 + maximum: 65535 + minimum: 1 type: integer + sectionName: + description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-terminatinggateways.yaml b/charts/consul/templates/crd-terminatinggateways.yaml index 565aa63381..fae09bff53 100644 --- a/charts/consul/templates/crd-terminatinggateways.yaml +++ b/charts/consul/templates/crd-terminatinggateways.yaml @@ -1,15 +1,20 @@ {{- if .Values.connectInject.enabled }} +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 labels: app: {{ template "consul.name" . }} chart: {{ template "consul.chart" . }} heritage: {{ .Release.Service }} release: {{ .Release.Name }} component: crd + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -134,4 +139,10 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] {{- end }} diff --git a/charts/consul/templates/crd-tlsroutes-external.yaml b/charts/consul/templates/crd-tlsroutes.yaml similarity index 100% rename from charts/consul/templates/crd-tlsroutes-external.yaml rename to charts/consul/templates/crd-tlsroutes.yaml diff --git a/charts/consul/templates/crd-trafficpermissions.yaml b/charts/consul/templates/crd-trafficpermissions.yaml deleted file mode 100644 index 27ab6f5e3d..0000000000 --- a/charts/consul/templates/crd-trafficpermissions.yaml +++ /dev/null @@ -1,261 +0,0 @@ -{{- if .Values.connectInject.enabled }} -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: crd - name: trafficpermissions.auth.consul.hashicorp.com -spec: - group: auth.consul.hashicorp.com - names: - kind: TrafficPermissions - listKind: TrafficPermissionsList - plural: trafficpermissions - shortNames: - - traffic-permissions - singular: trafficpermissions - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TrafficPermissions is the Schema for the traffic-permissions - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - action: - description: "Action can be either allow or deny for the entire object. - It will default to allow. \n If action is allow, we will allow the - connection if one of the rules in Rules matches, in other words, - we will deny all requests except for the ones that match Rules. - If Consul is in default allow mode, then allow actions have no effect - without a deny permission as everything is allowed by default. \n - If action is deny, we will deny the connection if one of the rules - in Rules match, in other words, we will allow all requests except - for the ones that match Rules. If Consul is default deny mode, then - deny permissions have no effect without an allow permission as everything - is denied by default. \n Action unspecified is reserved for compatibility - with the addition of future actions." - enum: - - ACTION_ALLOW - - ACTION_DENY - - ACTION_UNKNOWN - format: int32 - type: string - destination: - description: Destination is a configuration of the destination proxies - where these traffic permissions should apply. - properties: - identityName: - type: string - type: object - permissions: - description: Permissions is a list of permissions to match on. They - are applied using OR semantics. - items: - description: Permissions is a list of permissions to match on. - properties: - destinationRules: - description: DestinationRules is a list of rules to apply for - matching sources in this Permission. These rules are specific - to the request or connection that is going to the destination(s) - selected by the TrafficPermissions resource. - items: - description: DestinationRule contains rules rules to apply - to the incoming connection. - properties: - exclude: - description: Exclude contains a list of rules to exclude - when evaluating rules for the incoming connection. - items: - properties: - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - description: PortNames is a list of workload ports - to apply this rule to. The ports specified here - must be the ports used in the connection. - items: - type: string - type: array - type: object - type: array - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. If no - methods are specified, this rule will apply to all methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - items: - type: string - type: array - type: object - type: array - sources: - description: Sources is a list of sources in this traffic permission. - items: - description: Source represents the source identity. To specify - any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identity_name - should be omitted. - properties: - exclude: - description: Exclude is a list of sources to exclude from - this source. - items: - description: ExcludeSource is almost the same as source - but it prevents the addition of matching sources. - properties: - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -{{- end }} diff --git a/charts/consul/templates/crd-udproutes-external.yaml b/charts/consul/templates/crd-udproutes.yaml similarity index 100% rename from charts/consul/templates/crd-udproutes-external.yaml rename to charts/consul/templates/crd-udproutes.yaml diff --git a/charts/consul/templates/create-federation-secret-job.yaml b/charts/consul/templates/create-federation-secret-job.yaml index f0d5a1c821..678a2af3ba 100644 --- a/charts/consul/templates/create-federation-secret-job.yaml +++ b/charts/consul/templates/create-federation-secret-job.yaml @@ -37,7 +37,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-create-federation-secret diff --git a/charts/consul/templates/enterprise-license-job.yaml b/charts/consul/templates/enterprise-license-job.yaml index 80ae582152..0122690104 100644 --- a/charts/consul/templates/enterprise-license-job.yaml +++ b/charts/consul/templates/enterprise-license-job.yaml @@ -39,7 +39,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-enterprise-license diff --git a/charts/consul/templates/gateway-cleanup-job.yaml b/charts/consul/templates/gateway-cleanup-job.yaml index df6c22fd30..20d2f8116e 100644 --- a/charts/consul/templates/gateway-cleanup-job.yaml +++ b/charts/consul/templates/gateway-cleanup-job.yaml @@ -31,7 +31,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-cleanup diff --git a/charts/consul/templates/gateway-resources-job.yaml b/charts/consul/templates/gateway-resources-job.yaml index 1136d2e0fe..de510d9dc4 100644 --- a/charts/consul/templates/gateway-resources-job.yaml +++ b/charts/consul/templates/gateway-resources-job.yaml @@ -31,7 +31,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gateway-resources diff --git a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml index cc5b5397c2..02fb3ea168 100644 --- a/charts/consul/templates/gossip-encryption-autogenerate-job.yaml +++ b/charts/consul/templates/gossip-encryption-autogenerate-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" spec: restartPolicy: Never serviceAccountName: {{ template "consul.fullname" . }}-gossip-encryption-autogenerate diff --git a/charts/consul/templates/ingress-gateways-deployment.yaml b/charts/consul/templates/ingress-gateways-deployment.yaml index 4eef2e96ac..df9f500e3c 100644 --- a/charts/consul/templates/ingress-gateways-deployment.yaml +++ b/charts/consul/templates/ingress-gateways-deployment.yaml @@ -74,7 +74,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "ingress-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/mesh-gateway-deployment.yaml b/charts/consul/templates/mesh-gateway-deployment.yaml index afb5d44a0e..ac050e7199 100644 --- a/charts/consul/templates/mesh-gateway-deployment.yaml +++ b/charts/consul/templates/mesh-gateway-deployment.yaml @@ -43,7 +43,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "mesh-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .Values.meshGateway.consulServiceName }}" "consul.hashicorp.com/mesh-gateway-container-port": "{{ .Values.meshGateway.containerPort }}" diff --git a/charts/consul/templates/partition-init-job.yaml b/charts/consul/templates/partition-init-job.yaml index 59a119e0c7..6e21289f22 100644 --- a/charts/consul/templates/partition-init-job.yaml +++ b/charts/consul/templates/partition-init-job.yaml @@ -36,7 +36,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if (and .Values.global.secretsBackend.vault.enabled (or .Values.global.tls.enabled .Values.global.acls.manageSystemACLs)) }} "vault.hashicorp.com/agent-pre-populate-only": "true" "vault.hashicorp.com/agent-inject": "true" diff --git a/charts/consul/templates/prometheus.yaml b/charts/consul/templates/prometheus.yaml index a708708daf..4dcede1745 100644 --- a/charts/consul/templates/prometheus.yaml +++ b/charts/consul/templates/prometheus.yaml @@ -410,8 +410,8 @@ spec: template: metadata: annotations: + consul.hashicorp.com/connect-inject: "false" - consul.hashicorp.com/mesh-inject: "false" labels: component: "server" app: prometheus diff --git a/charts/consul/templates/server-acl-init-cleanup-job.yaml b/charts/consul/templates/server-acl-init-cleanup-job.yaml index b47e04188f..39754d6c6f 100644 --- a/charts/consul/templates/server-acl-init-cleanup-job.yaml +++ b/charts/consul/templates/server-acl-init-cleanup-job.yaml @@ -47,7 +47,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/server-acl-init-job.yaml b/charts/consul/templates/server-acl-init-job.yaml index aca1444a9b..0b46697f31 100644 --- a/charts/consul/templates/server-acl-init-job.yaml +++ b/charts/consul/templates/server-acl-init-job.yaml @@ -46,7 +46,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.acls.annotations }} {{- tpl .Values.global.acls.annotations . | nindent 8 }} {{- end }} @@ -192,10 +191,6 @@ spec: {{- else }} -secrets-backend=kubernetes \ {{- end }} - - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -enable-resource-apis=true \ - {{- end }} {{- if .Values.global.acls.bootstrapToken.secretName }} -bootstrap-token-secret-name={{ .Values.global.acls.bootstrapToken.secretName }} \ diff --git a/charts/consul/templates/server-statefulset.yaml b/charts/consul/templates/server-statefulset.yaml index d2785369c7..a5dd04e904 100644 --- a/charts/consul/templates/server-statefulset.yaml +++ b/charts/consul/templates/server-statefulset.yaml @@ -115,7 +115,6 @@ spec: {{- end }} {{- end }} "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/server-config-configmap.yaml") . | sha256sum }} {{- if .Values.server.annotations }} {{- tpl .Values.server.annotations . | nindent 8 }} @@ -252,7 +251,6 @@ spec: containers: - name: consul image: "{{ default .Values.global.image .Values.server.image }}" - imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - name: ADVERTISE_IP valueFrom: @@ -421,13 +419,10 @@ spec: {{- end }} {{- end }} -config-file=/consul/extra-config/extra-from-values.json \ - -config-file=/consul/extra-config/locality.json \ + -config-file=/consul/extra-config/locality.json {{- if and .Values.global.cloud.enabled .Values.global.cloud.resourceId.secretName }} -hcl="cloud { resource_id = \"${HCP_RESOURCE_ID}\" }" {{- end }} - {{- if (mustHas "resource-apis" .Values.global.experiments) }} - -hcl="experiments=[\"resource-apis\"]" - {{- end }} volumeMounts: - name: data-{{ .Release.Namespace | trunc 58 | trimSuffix "-" }} mountPath: /consul/data diff --git a/charts/consul/templates/sync-catalog-deployment.yaml b/charts/consul/templates/sync-catalog-deployment.yaml index f81b999e79..f4aeb1cdb8 100644 --- a/charts/consul/templates/sync-catalog-deployment.yaml +++ b/charts/consul/templates/sync-catalog-deployment.yaml @@ -40,7 +40,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.syncCatalog.annotations }} {{- tpl .Values.syncCatalog.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/telemetry-collector-deployment.yaml b/charts/consul/templates/telemetry-collector-deployment.yaml index 396cc147ab..6c7824b55e 100644 --- a/charts/consul/templates/telemetry-collector-deployment.yaml +++ b/charts/consul/templates/telemetry-collector-deployment.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.telemetryCollector.enabled (not (mustHas "resource-apis" .Values.global.experiments)) }} +{{- if .Values.telemetryCollector.enabled }} {{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} {{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} {{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} @@ -153,7 +153,7 @@ spec: containers: - name: consul-telemetry-collector image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} + imagePullPolicy: Always ports: - containerPort: 9090 name: metrics diff --git a/charts/consul/templates/telemetry-collector-v2-deployment.yaml b/charts/consul/templates/telemetry-collector-v2-deployment.yaml deleted file mode 100644 index a88277f3b2..0000000000 --- a/charts/consul/templates/telemetry-collector-v2-deployment.yaml +++ /dev/null @@ -1,378 +0,0 @@ -{{- if and .Values.telemetryCollector.enabled (mustHas "resource-apis" .Values.global.experiments) }} -{{- if not .Values.telemetryCollector.image}}{{ fail "telemetryCollector.image must be set to enable consul-telemetry-collector" }}{{ end }} -{{- if not .Values.connectInject.enabled }}{{ fail "connectInject.enabled must be true" }}{{ end -}} -{{- if and .Values.global.adminPartitions.enabled (not .Values.global.enableConsulNamespaces) }}{{ fail "global.enableConsulNamespaces must be true if global.adminPartitions.enabled=true" }}{{ end }} -{{ template "consul.validateCloudSecretKeys" . }} -{{ template "consul.validateTelemetryCollectorCloud" . }} -{{ template "consul.validateTelemetryCollectorCloudSecretKeys" . }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ template "consul.fullname" . }}-telemetry-collector - namespace: {{ .Release.Namespace }} - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - heritage: {{ .Release.Service }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 4 }} - {{- end }} -spec: - replicas: {{ .Values.telemetryCollector.replicas }} - selector: - matchLabels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "false" - # This annotation tells the pod controller that this pod was injected even though it wasn't. - # This ensures the pod controller will sync a workload for the pod into Consul - "consul.hashicorp.com/mesh-inject-status": "injected" - # We aren't using tproxy and we don't have an original pod. This would be simpler if we made a path similar - # to gateways - "consul.hashicorp.com/transparent-proxy": "false" - "consul.hashicorp.com/transparent-proxy-overwrite-probes": "false" - "consul.hashicorp.com/consul-k8s-version": {{ $.Chart.Version }} - {{- if .Values.telemetryCollector.customExporterConfig }} - # configmap checksum - "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/telemetry-collector-configmap.yaml") . | sha256sum }} - {{- end }} - # vault annotations - {{- if (and .Values.global.secretsBackend.vault.enabled .Values.global.tls.enabled) }} - "vault.hashicorp.com/agent-init-first": "true" - "vault.hashicorp.com/agent-inject": "true" - "vault.hashicorp.com/role": {{ .Values.global.secretsBackend.vault.consulCARole }} - "vault.hashicorp.com/agent-inject-secret-serverca.crt": {{ .Values.global.tls.caCert.secretName }} - "vault.hashicorp.com/agent-inject-template-serverca.crt": {{ template "consul.serverTLSCATemplate" . }} - {{- if and .Values.global.secretsBackend.vault.ca.secretName .Values.global.secretsBackend.vault.ca.secretKey }} - "vault.hashicorp.com/agent-extra-secret": "{{ .Values.global.secretsBackend.vault.ca.secretName }}" - "vault.hashicorp.com/ca-cert": "/vault/custom/{{ .Values.global.secretsBackend.vault.ca.secretKey }}" - {{- end }} - {{- if .Values.global.secretsBackend.vault.agentAnnotations }} - {{ tpl .Values.global.secretsBackend.vault.agentAnnotations . | nindent 8 | trim }} - {{- end }} - {{- if (and (.Values.global.secretsBackend.vault.vaultNamespace) (not (hasKey (default "" .Values.global.secretsBackend.vault.agentAnnotations | fromYaml) "vault.hashicorp.com/namespace")))}} - "vault.hashicorp.com/namespace": "{{ .Values.global.secretsBackend.vault.vaultNamespace }}" - {{- end }} - {{- end }} - - labels: - app: {{ template "consul.name" . }} - chart: {{ template "consul.chart" . }} - release: {{ .Release.Name }} - component: consul-telemetry-collector - {{- if .Values.global.extraLabels }} - {{- toYaml .Values.global.extraLabels | nindent 8 }} - {{- end }} - spec: - # This needs to explicitly be consul-telemetry-collector because we look this up from each service consul-dataplane - # to forward metrics to it. - serviceAccountName: consul-telemetry-collector - initContainers: - # We're manually managing this init container instead of using the mesh injector so that we don't run into - # any race conditions on the mesh-injector deployment or upgrade - - name: consul-mesh-init - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - {{- if .Values.global.acls.manageSystemACLs }} - - name: CONSUL_LOGIN_AUTH_METHOD - value: {{ template "consul.fullname" . }}-k8s-auth-method - - name: CONSUL_LOGIN_META - value: "component=consul-telemetry-collector,pod=$(NAMESPACE)/$(POD_NAME)" - {{- end }} - {{- include "consul.consulK8sConsulServerEnvVars" . | nindent 10 }} - {{- if .Values.global.enableConsulNamespaces }} - - name: CONSUL_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} - - name: CONSUL_LOGIN_NAMESPACE - value: "default" - {{- else }} - - name: CONSUL_LOGIN_NAMESPACE - value: {{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - command: - - /bin/sh - - -ec - - |- - consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} \ - -log-json={{ .Values.global.logJSON }} - - image: {{ .Values.global.imageK8S }} - imagePullPolicy: IfNotPresent - {{- if .Values.telemetryCollector.initContainer.resources }} - resources: - {{- toYaml .Values.telemetryCollector.initContainer.resources | nindent 12 }} - {{- else }} - resources: - limits: - cpu: 50m - memory: 150Mi - requests: - cpu: 50m - memory: 25Mi - {{- end }} - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /consul/mesh-inject - name: consul-mesh-inject-data - {{- if .Values.global.tls.enabled }} - {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} - - name: consul-ca-cert - mountPath: /consul/tls/ca - readOnly: true - {{- end }} - {{- end }} - containers: - - name: consul-telemetry-collector - image: {{ .Values.telemetryCollector.image }} - imagePullPolicy: {{ .Values.global.imagePullPolicy }} - ports: - - containerPort: 9090 - name: metrics - protocol: TCP - - containerPort: 9356 - name: metricsserver - protocol: TCP - env: - # These are mounted as secrets so that the telemetry-collector can use them when cloud is enabled. - # - the hcp-go-sdk in consul agent will already look for HCP_CLIENT_ID, HCP_CLIENT_SECRET, HCP_AUTH_URL, - # HCP_SCADA_ADDRESS, and HCP_API_HOST. so nothing more needs to be done. - # - HCP_RESOURCE_ID is created for use in the global cloud section but we will share it here - {{- if .Values.telemetryCollector.cloud.clientId.secretName }} - - name: HCP_CLIENT_ID - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientId.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientId.secretKey }} - {{- end }} - {{- if .Values.telemetryCollector.cloud.clientSecret.secretName }} - - name: HCP_CLIENT_SECRET - valueFrom: - secretKeyRef: - name: {{ .Values.telemetryCollector.cloud.clientSecret.secretName }} - key: {{ .Values.telemetryCollector.cloud.clientSecret.secretKey }} - {{- end}} - {{- if .Values.global.cloud.resourceId.secretName }} - - name: HCP_RESOURCE_ID - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.resourceId.secretName }} - key: {{ .Values.global.cloud.resourceId.secretKey }} - {{- end }} - {{- if .Values.global.cloud.authUrl.secretName }} - - name: HCP_AUTH_URL - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.authUrl.secretName }} - key: {{ .Values.global.cloud.authUrl.secretKey }} - {{- end}} - {{- if .Values.global.cloud.apiHost.secretName }} - - name: HCP_API_HOST - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.apiHost.secretName }} - key: {{ .Values.global.cloud.apiHost.secretKey }} - {{- end}} - {{- if .Values.global.cloud.scadaAddress.secretName }} - - name: HCP_SCADA_ADDRESS - valueFrom: - secretKeyRef: - name: {{ .Values.global.cloud.scadaAddress.secretName }} - key: {{ .Values.global.cloud.scadaAddress.secretKey }} - {{- end}} - {{- if .Values.global.trustedCAs }} - - name: SSL_CERT_DIR - value: "/etc/ssl/certs:/trusted-cas" - {{- end }} - {{- include "consul.extraEnvironmentVars" .Values.telemetryCollector | nindent 12 }} - command: - - "/bin/sh" - - "-ec" - - | - {{- if .Values.global.trustedCAs }} - {{- range $i, $cert := .Values.global.trustedCAs }} - cat < /trusted-cas/custom-ca-{{$i}}.pem - {{- $cert | nindent 10 }} - EOF - {{- end }} - {{- end }} - - consul-telemetry-collector agent \ - {{- if .Values.telemetryCollector.customExporterConfig }} - -config-file-path /consul/config/config.json \ - {{ end }} - volumeMounts: - {{- if .Values.telemetryCollector.customExporterConfig }} - - name: config - mountPath: /consul/config - {{- end }} - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - mountPath: /trusted-cas - readOnly: false - {{- end }} - resources: - {{- if .Values.telemetryCollector.resources }} - {{- toYaml .Values.telemetryCollector.resources | nindent 12 }} - {{- end }} - # consul-dataplane container - - name: consul-dataplane - image: "{{ .Values.global.imageConsulDataplane }}" - imagePullPolicy: IfNotPresent - command: - - consul-dataplane - args: - # addresses - {{- if .Values.externalServers.enabled }} - - -addresses={{ .Values.externalServers.hosts | first }} - {{- else }} - - -addresses={{ template "consul.fullname" . }}-server.{{ .Release.Namespace }}.svc - {{- end }} - # grpc - {{- if .Values.externalServers.enabled }} - - -grpc-port={{ .Values.externalServers.grpcPort }} - {{- else }} - - -grpc-port=8502 - {{- end }} - # tls - {{- if .Values.global.tls.enabled }} - {{- if (not (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots)) }} - {{- if .Values.global.secretsBackend.vault.enabled }} - - -ca-certs=/vault/secrets/serverca.crt - {{- else }} - - -ca-certs=/consul/tls/ca/tls.crt - {{- end }} - {{- end }} - {{- if and .Values.externalServers.enabled .Values.externalServers.tlsServerName }} - - -tls-server-name={{.Values.externalServers.tlsServerName }} - {{- else if .Values.global.cloud.enabled }} - - -tls-server-name=server.{{ .Values.global.datacenter}}.{{ .Values.global.domain}} - {{- end }} - {{- else }} - - -tls-disabled - {{- end }} - # credentials - {{- if .Values.global.acls.manageSystemACLs }} - - -credential-type=login - - -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token - - -login-auth-method={{ template "consul.fullname" . }}-k8s-auth-method - {{- if .Values.global.enableConsulNamespaces }} - {{- if .Values.syncCatalog.consulNamespaces.mirroringK8S }} - - -login-namespace="default" - {{- else }} - - -login-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - - foo - - -login-partition={{ .Values.global.adminPartitions.name }} - {{- end }} - {{- end }} - {{- if .Values.global.enableConsulNamespaces }} - - -service-namespace={{ .Values.syncCatalog.consulNamespaces.consulDestinationNamespace }} - {{- end }} - {{- if .Values.global.adminPartitions.enabled }} - - -service-partition={{ .Values.global.adminPartitions.name }} - {{- end }} - {{- if .Values.global.metrics.enabled }} - - -telemetry-prom-scrape-path=/metrics - {{- end }} - - -log-level={{ default .Values.global.logLevel .Values.telemetryCollector.logLevel }} - - -log-json={{ .Values.global.logJSON }} - - -envoy-concurrency=2 - {{- if and .Values.externalServers.enabled .Values.externalServers.skipServerWatch }} - - -server-watch-disabled=true - {{- end }} - env: - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: DP_PROXY_ID - value: $(POD_NAME) - - name: DP_CREDENTIAL_LOGIN_META1 - value: pod=$(NAMESPACE)/$(POD_NAME) - - name: DP_CREDENTIAL_LOGIN_META2 - value: component=consul-telemetry-collector - - name: TMPDIR - value: /consul/mesh-inject - readinessProbe: - failureThreshold: 3 - initialDelaySeconds: 1 - periodSeconds: 10 - successThreshold: 1 - tcpSocket: - port: 20000 - timeoutSeconds: 1 - securityContext: - readOnlyRootFilesystem: true - runAsGroup: 5995 - runAsNonRoot: true - runAsUser: 5995 - # dataplane volume mounts - volumeMounts: - - mountPath: /consul/mesh-inject - name: consul-mesh-inject-data - {{- if .Values.global.tls.enabled }} - {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} - - name: consul-ca-cert - mountPath: /consul/tls/ca - readOnly: true - {{- end }} - {{- end }} - - {{- if .Values.telemetryCollector.nodeSelector }} - nodeSelector: - {{ tpl .Values.telemetryCollector.nodeSelector . | indent 8 | trim }} - {{- end }} - {{- if .Values.telemetryCollector.priorityClassName }} - priorityClassName: {{ .Values.telemetryCollector.priorityClassName }} - {{- end }} - volumes: - - emptyDir: - medium: Memory - name: consul-mesh-inject-data - {{- if .Values.global.trustedCAs }} - - name: trusted-cas - emptyDir: - medium: "Memory" - {{- end }} - {{- if .Values.global.tls.enabled }} - {{- if not (or (and .Values.externalServers.enabled .Values.externalServers.useSystemRoots) .Values.global.secretsBackend.vault.enabled) }} - - name: consul-ca-cert - secret: - {{- if .Values.global.tls.caCert.secretName }} - secretName: {{ .Values.global.tls.caCert.secretName }} - {{- else }} - secretName: {{ template "consul.fullname" . }}-ca-cert - {{- end }} - items: - - key: {{ default "tls.crt" .Values.global.tls.caCert.secretKey }} - path: tls.crt - {{- end }} - {{- end }} - - name: config - configMap: - name: {{ template "consul.fullname" . }}-telemetry-collector -{{- end }} diff --git a/charts/consul/templates/terminating-gateways-deployment.yaml b/charts/consul/templates/terminating-gateways-deployment.yaml index 3a1a23fad3..ea2131b8a2 100644 --- a/charts/consul/templates/terminating-gateways-deployment.yaml +++ b/charts/consul/templates/terminating-gateways-deployment.yaml @@ -76,7 +76,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/gateway-kind": "terminating-gateway" "consul.hashicorp.com/gateway-consul-service-name": "{{ .name }}" {{- if $root.Values.global.enableConsulNamespaces }} diff --git a/charts/consul/templates/tls-init-cleanup-job.yaml b/charts/consul/templates/tls-init-cleanup-job.yaml index 9500410a53..2254a38ed2 100644 --- a/charts/consul/templates/tls-init-cleanup-job.yaml +++ b/charts/consul/templates/tls-init-cleanup-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/tls-init-job.yaml b/charts/consul/templates/tls-init-job.yaml index 54727e03dd..47651fe14b 100644 --- a/charts/consul/templates/tls-init-job.yaml +++ b/charts/consul/templates/tls-init-job.yaml @@ -35,7 +35,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" {{- if .Values.global.tls.annotations }} {{- tpl .Values.global.tls.annotations . | nindent 8 }} {{- end }} diff --git a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml index 2a5c80d94c..e13e2dc741 100644 --- a/charts/consul/templates/webhook-cert-manager-clusterrole.yaml +++ b/charts/consul/templates/webhook-cert-manager-clusterrole.yaml @@ -27,7 +27,6 @@ rules: - admissionregistration.k8s.io resources: - mutatingwebhookconfigurations - - validatingwebhookconfigurations verbs: - get - list diff --git a/charts/consul/templates/webhook-cert-manager-deployment.yaml b/charts/consul/templates/webhook-cert-manager-deployment.yaml index 29b85d7079..7ba25b330c 100644 --- a/charts/consul/templates/webhook-cert-manager-deployment.yaml +++ b/charts/consul/templates/webhook-cert-manager-deployment.yaml @@ -36,7 +36,6 @@ spec: {{- end }} annotations: "consul.hashicorp.com/connect-inject": "false" - "consul.hashicorp.com/mesh-inject": "false" "consul.hashicorp.com/config-checksum": {{ include (print $.Template.BasePath "/webhook-cert-manager-configmap.yaml") . | sha256sum }} spec: containers: diff --git a/charts/consul/test/terraform/eks/main.tf b/charts/consul/test/terraform/eks/main.tf index 3bc8b40451..efbab0e833 100644 --- a/charts/consul/test/terraform/eks/main.tf +++ b/charts/consul/test/terraform/eks/main.tf @@ -124,13 +124,12 @@ resource "aws_iam_role_policy_attachment" "csi" { } resource "aws_eks_addon" "csi-driver" { - count = var.cluster_count - cluster_name = module.eks[count.index].cluster_id - addon_name = "aws-ebs-csi-driver" - addon_version = "v1.15.0-eksbuild.1" - service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn - resolve_conflicts_on_create = "OVERWRITE" - resolve_conflicts_on_update = "OVERWRITE" + count = var.cluster_count + cluster_name = module.eks[count.index].cluster_id + addon_name = "aws-ebs-csi-driver" + addon_version = "v1.15.0-eksbuild.1" + service_account_role_arn = aws_iam_role.csi-driver-role[count.index].arn + resolve_conflicts = "OVERWRITE" } data "aws_eks_cluster" "cluster" { diff --git a/charts/consul/test/terraform/gke/main.tf b/charts/consul/test/terraform/gke/main.tf index 800aca5246..34bb07906f 100644 --- a/charts/consul/test/terraform/gke/main.tf +++ b/charts/consul/test/terraform/gke/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { google = { - version = "~> 5.3.0" + version = "~> 4.58.0" } } } @@ -21,7 +21,7 @@ resource "random_id" "suffix" { data "google_container_engine_versions" "main" { location = var.zone - version_prefix = "1.27." + version_prefix = "1.25.9" } # We assume that the subnets are already created to save time. @@ -37,17 +37,14 @@ resource "google_container_cluster" "cluster" { project = var.project initial_node_count = 3 location = var.zone - # 2023-10-30 - There is a bug with the terraform provider where lastest_master_version is not being returned by the - # api. Hardcode GKE version for now. min_master_version = data.google_container_engine_versions.main.latest_master_version node_version = data.google_container_engine_versions.main.latest_master_version node_config { tags = ["consul-k8s-${random_id.suffix[count.index].dec}"] machine_type = "e2-standard-8" } - subnetwork = data.google_compute_subnetwork.subnet.name - resource_labels = var.labels - deletion_protection = false + subnetwork = data.google_compute_subnetwork.subnet.name + resource_labels = var.labels } resource "google_compute_firewall" "firewall-rules" { diff --git a/charts/consul/test/terraform/gke/outputs.tf b/charts/consul/test/terraform/gke/outputs.tf index b1c1343e9e..a0ffac907f 100644 --- a/charts/consul/test/terraform/gke/outputs.tf +++ b/charts/consul/test/terraform/gke/outputs.tf @@ -12,7 +12,3 @@ output "cluster_names" { output "kubeconfigs" { value = [for cl in google_container_cluster.cluster : format("$HOME/.kube/%s", cl.name)] } - -output "versions" { - value = data.google_container_engine_versions.main -} diff --git a/charts/consul/test/unit/client-daemonset.bats b/charts/consul/test/unit/client-daemonset.bats index c9889c2ffd..ff9288a51d 100755 --- a/charts/consul/test/unit/client-daemonset.bats +++ b/charts/consul/test/unit/client-daemonset.bats @@ -530,11 +530,7 @@ load _helpers -s templates/client-daemonset.yaml \ --set 'client.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2728,14 +2724,7 @@ rollingUpdate: --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role") | - del(."vault.hashicorp.com/agent-init-first")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-init-first")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/connect-inject-deployment.bats b/charts/consul/test/unit/connect-inject-deployment.bats index 748b75de5d..5d7f0b7084 100755 --- a/charts/consul/test/unit/connect-inject-deployment.bats +++ b/charts/consul/test/unit/connect-inject-deployment.bats @@ -1461,10 +1461,7 @@ load _helpers -s templates/connect-inject-deployment.yaml \ --set 'connectInject.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2220,12 +2217,7 @@ load _helpers --set 'global.tls.caCert.secretName=foo' \ --set 'global.secretsBackend.vault.consulCARole=carole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2661,29 +2653,3 @@ reservedNameTest() { [ "${actual}" = "server.dc1.consul" ] } -#-------------------------------------------------------------------- -# resource-apis - -@test "connectInject/Deployment: resource-apis is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - - [ "${actual}" = "false" ] -} - -@test "connectInject/Deployment: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { - cd `chart_dir` - local actual=$(helm template \ - -s templates/connect-inject-deployment.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command | any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/crd-gatewayclasses-external.bats b/charts/consul/test/unit/crd-gatewayclasses.bats similarity index 80% rename from charts/consul/test/unit/crd-gatewayclasses-external.bats rename to charts/consul/test/unit/crd-gatewayclasses.bats index a1a845a249..8400590606 100644 --- a/charts/consul/test/unit/crd-gatewayclasses-external.bats +++ b/charts/consul/test/unit/crd-gatewayclasses.bats @@ -5,7 +5,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-gatewayclasses-external.yaml \ + -s templates/crd-gatewayclasses.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gatewayclasses-external.yaml \ + -s templates/crd-gatewayclasses.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "gatewayclasses/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gatewayclasses-external.yaml \ + -s templates/crd-gatewayclasses.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-gatewaypolicies.bats b/charts/consul/test/unit/crd-gatewaypolicies.bats deleted file mode 100644 index 2a40a8182e..0000000000 --- a/charts/consul/test/unit/crd-gatewaypolicies.bats +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "gatewaypolicies/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-gatewaypolicies.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "gatewaypolicies/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-gatewaypolicies.yaml \ - --set 'connectInject.enabled=false' \ - . -} diff --git a/charts/consul/test/unit/crd-gateways-external.bats b/charts/consul/test/unit/crd-gateways.bats similarity index 82% rename from charts/consul/test/unit/crd-gateways-external.bats rename to charts/consul/test/unit/crd-gateways.bats index 30b6d71630..8a7f0284e2 100644 --- a/charts/consul/test/unit/crd-gateways-external.bats +++ b/charts/consul/test/unit/crd-gateways.bats @@ -5,7 +5,7 @@ load _helpers @test "gateways/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-gateways-external.yaml \ + -s templates/crd-gateways.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "gateways/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gateways-external.yaml \ + -s templates/crd-gateways.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "gateways/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-gateways-external.yaml \ + -s templates/crd-gateways.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-grpcroutes-external.bats b/charts/consul/test/unit/crd-grpcroutes.bats similarity index 81% rename from charts/consul/test/unit/crd-grpcroutes-external.bats rename to charts/consul/test/unit/crd-grpcroutes.bats index 625648e326..d5e3e298d7 100644 --- a/charts/consul/test/unit/crd-grpcroutes-external.bats +++ b/charts/consul/test/unit/crd-grpcroutes.bats @@ -5,7 +5,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-grpcroutes-external.yaml \ + -s templates/crd-grpcroutes.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-grpcroutes-external.yaml \ + -s templates/crd-grpcroutes.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "grpcroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-grpcroutes-external.yaml \ + -s templates/crd-grpcroutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-httproutes-external.bats b/charts/consul/test/unit/crd-httproutes.bats similarity index 81% rename from charts/consul/test/unit/crd-httproutes-external.bats rename to charts/consul/test/unit/crd-httproutes.bats index e35bebc3e4..99c58e0492 100644 --- a/charts/consul/test/unit/crd-httproutes-external.bats +++ b/charts/consul/test/unit/crd-httproutes.bats @@ -5,7 +5,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-httproutes-external.yaml \ + -s templates/crd-httproutes.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-httproutes-external.yaml \ + -s templates/crd-httproutes.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "httproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-httproutes-external.yaml \ + -s templates/crd-httproutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-routeauthfilters.bats b/charts/consul/test/unit/crd-routeauthfilters.bats deleted file mode 100644 index d4af62dd5c..0000000000 --- a/charts/consul/test/unit/crd-routeauthfilters.bats +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "routeauth-filters/CustomResourceDefinition: enabled by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/crd-routeauthfilters.yaml \ - . | tee /dev/stderr | - yq 'length > 0' | tee /dev/stderr) - [ "$actual" = "true" ] -} - -@test "routeauth-filter/CustomResourceDefinition: disabled with connectInject.enabled=false" { - cd `chart_dir` - assert_empty helm template \ - -s templates/crd-routeauthfilters.yaml \ - --set 'connectInject.enabled=false' \ - . -} diff --git a/charts/consul/test/unit/crd-tcproutes-external.bats b/charts/consul/test/unit/crd-tcproutes.bats similarity index 84% rename from charts/consul/test/unit/crd-tcproutes-external.bats rename to charts/consul/test/unit/crd-tcproutes.bats index c91eb15e6b..9cfdd182e7 100644 --- a/charts/consul/test/unit/crd-tcproutes-external.bats +++ b/charts/consul/test/unit/crd-tcproutes.bats @@ -5,7 +5,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tcproutes-external.yaml \ + -s templates/crd-tcproutes.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ + -s templates/crd-tcproutes.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ + -s templates/crd-tcproutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } @@ -30,7 +30,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false and connectInject.apiGateway.manageNonStandardCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tcproutes-external.yaml \ + -s templates/crd-tcproutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ --set 'connectInject.apiGateway.manageNonStandardCRDs=false' \ . @@ -39,7 +39,7 @@ load _helpers @test "tcproutes/CustomResourceDefinition: enabled with connectInject.apiGateway.manageNonStandardCRDs=true" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tcproutes-external.yaml \ + -s templates/crd-tcproutes.yaml \ --set 'connectInject.apiGateway.manageNonStandardCRDs=true' \ . | tee /dev/stderr | yq -s 'length > 0' | tee /dev/stderr) diff --git a/charts/consul/test/unit/crd-tlsroutes-external.bats b/charts/consul/test/unit/crd-tlsroutes.bats similarity index 82% rename from charts/consul/test/unit/crd-tlsroutes-external.bats rename to charts/consul/test/unit/crd-tlsroutes.bats index 88b37521f2..7e1d5c471f 100644 --- a/charts/consul/test/unit/crd-tlsroutes-external.bats +++ b/charts/consul/test/unit/crd-tlsroutes.bats @@ -5,7 +5,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-tlsroutes-external.yaml \ + -s templates/crd-tlsroutes.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tlsroutes-external.yaml \ + -s templates/crd-tlsroutes.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "tlsroutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-tlsroutes-external.yaml \ + -s templates/crd-tlsroutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/crd-udproutes-external.bats b/charts/consul/test/unit/crd-udproutes.bats similarity index 82% rename from charts/consul/test/unit/crd-udproutes-external.bats rename to charts/consul/test/unit/crd-udproutes.bats index 6693e67b2d..407592a770 100644 --- a/charts/consul/test/unit/crd-udproutes-external.bats +++ b/charts/consul/test/unit/crd-udproutes.bats @@ -5,7 +5,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: enabled by default" { cd `chart_dir` local actual=$(helm template \ - -s templates/crd-udproutes-external.yaml \ + -s templates/crd-udproutes.yaml \ . | tee /dev/stderr | yq 'length > 0' | tee /dev/stderr) [ "$actual" = "true" ] @@ -14,7 +14,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: disabled with connectInject.enabled=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-udproutes-external.yaml \ + -s templates/crd-udproutes.yaml \ --set 'connectInject.enabled=false' \ . } @@ -22,7 +22,7 @@ load _helpers @test "udproutes/CustomResourceDefinition: disabled with connectInject.apiGateway.manageExternalCRDs=false" { cd `chart_dir` assert_empty helm template \ - -s templates/crd-udproutes-external.yaml \ + -s templates/crd-udproutes.yaml \ --set 'connectInject.apiGateway.manageExternalCRDs=false' \ . } diff --git a/charts/consul/test/unit/gateway-cleanup-job.bats b/charts/consul/test/unit/gateway-cleanup-job.bats index 26c3d08e97..711974b9c2 100644 --- a/charts/consul/test/unit/gateway-cleanup-job.bats +++ b/charts/consul/test/unit/gateway-cleanup-job.bats @@ -30,10 +30,6 @@ target=templates/gateway-cleanup-job.yaml local actual=$(helm template \ -s $target \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/gateway-resources-job.bats b/charts/consul/test/unit/gateway-resources-job.bats index e38397231b..7d9334bae4 100644 --- a/charts/consul/test/unit/gateway-resources-job.bats +++ b/charts/consul/test/unit/gateway-resources-job.bats @@ -149,10 +149,6 @@ target=templates/gateway-resources-job.yaml local actual=$(helm template \ -s $target \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/helpers.bats b/charts/consul/test/unit/helpers.bats index f8523a7220..efcbc116b5 100644 --- a/charts/consul/test/unit/helpers.bats +++ b/charts/consul/test/unit/helpers.bats @@ -327,154 +327,3 @@ load _helpers actual=$(echo $object | jq '.volumeMounts[] | select(.name == "consul-ca-cert")') [ "${actual}" = "" ] } - -#-------------------------------------------------------------------- -# consul.validateResourceAPIs -# These tests use test-runner.yaml to test the -# consul.validateResourceAPIs helper since we need an existing template - -@test "connectInject/Deployment: fails if resource-apis is set and peering is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.tls.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.peering.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.peering.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and admin partitions are enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.enableConsulNamespaces=true' \ - --set 'global.adminPartitions.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.adminPartitions.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and federation is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.tls.enabled=true' \ - --set 'meshGateway.enabled=true' \ - --set 'global.federation.enabled=true' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.federation.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and cloud is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.resourceId.secretName=hello' \ - --set 'global.cloud.resourceId.secretKey=hello' \ - --set 'global.cloud.clientId.secretName=hello' \ - --set 'global.cloud.clientId.secretKey=hello' \ - --set 'global.cloud.clientSecret.secretName=hello' \ - --set 'global.cloud.clientSecret.secretKey=hello' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, global.cloud.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and client is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'client.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, client.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and ui is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ui.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and syncCatalog is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'syncCatalog.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, syncCatalog.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and meshGateway is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'meshGateway.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, meshGateway.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and ingressGateways is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'ingressGateways.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, ingressGateways.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and terminatingGateways is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'terminatingGateways.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, terminatingGateways.enabled is currently unsupported." ]] -} - -@test "connectInject/Deployment: fails if resource-apis is set and apiGateway is enabled" { - cd `chart_dir` - run helm template \ - -s templates/tests/test-runner.yaml \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'apiGateway.enabled=true' . - [ "$status" -eq 1 ] - [[ "$output" =~ "When the value global.experiments.resourceAPIs is set, apiGateway.enabled is currently unsupported." ]] -} diff --git a/charts/consul/test/unit/ingress-gateways-deployment.bats b/charts/consul/test/unit/ingress-gateways-deployment.bats index 96f6d7ee3c..be617ac538 100644 --- a/charts/consul/test/unit/ingress-gateways-deployment.bats +++ b/charts/consul/test/unit/ingress-gateways-deployment.bats @@ -799,7 +799,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] } @test "ingressGateways/Deployment: extra annotations can be set through defaults" { @@ -814,7 +814,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -836,7 +836,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -859,7 +859,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "9" ] + [ "${actual}" = "8" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1146,17 +1146,7 @@ key2: value2' \ --set 'global.tls.caCert.secretName=foo' \ --set 'global.secretsBackend.vault.consulCARole=carole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role") | - del(."consul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/gateway-wan-port") | - del(."vconsul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/gateway-consul-service-name") | - del(."consul.hashicorp.com/gateway-kind")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-wan-port") | del(."vconsul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/mesh-gateway-deployment.bats b/charts/consul/test/unit/mesh-gateway-deployment.bats index afde4976a7..8e08463c43 100755 --- a/charts/consul/test/unit/mesh-gateway-deployment.bats +++ b/charts/consul/test/unit/mesh-gateway-deployment.bats @@ -44,7 +44,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "8" ] + [ "${actual}" = "7" ] } @test "meshGateway/Deployment: extra annotations can be set" { @@ -57,7 +57,7 @@ load _helpers key2: value2' \ . | tee /dev/stderr | yq -r '.spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "10" ] + [ "${actual}" = "9" ] } #-------------------------------------------------------------------- @@ -1415,18 +1415,7 @@ key2: value2' \ --set 'global.tls.caCert.secretName=foo' \ --set 'global.secretsBackend.vault.consulCARole=carole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role") | - del(."consul.hashicorp.com/gateway-kind") | - del(."consul.hashicorp.com/gateway-wan-address-source") | - del(."consul.hashicorp.com/mesh-gateway-container-port") | - del(."consul.hashicorp.com/gateway-wan-address-static") | - del(."consul.hashicorp.com/gateway-wan-port") | - del(."consul.hashicorp.com/gateway-consul-service-name")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-kind") | del(."consul.hashicorp.com/gateway-wan-address-source") | del(."consul.hashicorp.com/mesh-gateway-container-port") | del(."consul.hashicorp.com/gateway-wan-address-static") | del(."consul.hashicorp.com/gateway-wan-port") | del(."consul.hashicorp.com/gateway-consul-service-name")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/partition-init-job.bats b/charts/consul/test/unit/partition-init-job.bats index 131d13f8a1..12912416f0 100644 --- a/charts/consul/test/unit/partition-init-job.bats +++ b/charts/consul/test/unit/partition-init-job.bats @@ -635,15 +635,7 @@ reservedNameTest() { --set 'global.secretsBackend.vault.consulCARole=carole' \ --set 'global.secretsBackend.vault.manageSystemACLsRole=aclrole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/agent-pre-populate-only") | - del(."vault.hashicorp.com/role") | - del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | - del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/agent-pre-populate-only") | del(."vault.hashicorp.com/role") | del(."vault.hashicorp.com/agent-inject-secret-serverca.crt") | del(."vault.hashicorp.com/agent-inject-template-serverca.crt")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1025,4 +1017,4 @@ reservedNameTest() { local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) [ "${actualTemplateFoo}" = "bar" ] [ "${actualTemplateBaz}" = "qux" ] -} \ No newline at end of file +} diff --git a/charts/consul/test/unit/server-acl-init-cleanup-job.bats b/charts/consul/test/unit/server-acl-init-cleanup-job.bats index f6a67a1893..8743ea4a8d 100644 --- a/charts/consul/test/unit/server-acl-init-cleanup-job.bats +++ b/charts/consul/test/unit/server-acl-init-cleanup-job.bats @@ -236,11 +236,7 @@ load _helpers -s templates/server-acl-init-cleanup-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/server-acl-init-job.bats b/charts/consul/test/unit/server-acl-init-job.bats index 2205192ad2..a9873a8e61 100644 --- a/charts/consul/test/unit/server-acl-init-job.bats +++ b/charts/consul/test/unit/server-acl-init-job.bats @@ -1081,7 +1081,6 @@ load _helpers local expected=$(echo '{ "consul.hashicorp.com/connect-inject": "false", - "consul.hashicorp.com/mesh-inject": "false", "vault.hashicorp.com/agent-inject": "true", "vault.hashicorp.com/agent-pre-populate": "true", "vault.hashicorp.com/agent-pre-populate-only": "false", @@ -2357,11 +2356,7 @@ load _helpers -s templates/server-acl-init-job.yaml \ --set 'global.acls.manageSystemACLs=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -2411,36 +2406,3 @@ load _helpers yq -r '.spec.template.metadata.annotations["argocd.argoproj.io/hook-delete-policy"]' | tee /dev/stderr) [ "${actual}" = null ] } - -#-------------------------------------------------------------------- -# resource-apis - -@test "serverACLInit/Job: resource-apis is not set by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo $object | - yq 'any(contains("-enable-resource-apis"))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "serverACLInit/Job: -enable-resource-apis=true is set when global.experiments contains [\"resource-apis\"] " { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-acl-init-job.yaml \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'connectInject.enabled=true' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command' | tee /dev/stderr) - - local actual=$(echo $object | - yq 'any(contains("-enable-resource-apis=true"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} diff --git a/charts/consul/test/unit/server-config-configmap.bats b/charts/consul/test/unit/server-config-configmap.bats index 53d842fc9c..e074c3b1ae 100755 --- a/charts/consul/test/unit/server-config-configmap.bats +++ b/charts/consul/test/unit/server-config-configmap.bats @@ -1258,27 +1258,27 @@ load _helpers --set 'server.auditLogs.sinks[2].format=json' \ --set 'server.auditLogs.sinks[2].delivery_guarantee=best-effort' \ --set 'server.auditLogs.sinks[2].rotate_max_files=20' \ - --set 'server.auditLogs.sinks[2].rotate_duration=18h' \ --set 'server.auditLogs.sinks[2].rotate_bytes=12445' \ + --set 'server.auditLogs.sinks[2].rotate_duration=18h' \ --set 'server.auditLogs.sinks[2].path=/tmp/audit-3.json' \ . | tee /dev/stderr | yq -r '.data["audit-logging.json"]' | tee /dev/stderr) local actual=$(echo $object | jq -r .audit.sink.MySink1.path | tee /dev/stderr) [ "${actual}" = "/tmp/audit.json" ] - + local actual=$(echo $object | jq -r .audit.sink.MySink3.path | tee /dev/stderr) [ "${actual}" = "/tmp/audit-3.json" ] + local actual=$(echo $object | jq -r .audit.sink.MySink1.rotate_max_files | tee /dev/stderr) + [ ${actual} = 15 ] + local actual=$(echo $object | jq -r .audit.sink.MySink2.path | tee /dev/stderr) [ "${actual}" = "/tmp/audit-2.json" ] local actual=$(echo $object | jq -r .audit.sink.MySink1.name | tee /dev/stderr) [ "${actual}" = "null" ] - local actual=$(echo $object | jq -r .audit.sink.MySink1.rotate_max_files | tee /dev/stderr) - [ ${actual} = 15 ] - local actual=$(echo $object | jq -r .audit.sink.MySink3.delivery_guarantee | tee /dev/stderr) [ "${actual}" = "best-effort" ] @@ -1317,4 +1317,4 @@ load _helpers yq -r '.data["server.json"]' | jq -r .log_level | tee /dev/stderr) [ "${configmap}" = "DEBUG" ] -} \ No newline at end of file +} diff --git a/charts/consul/test/unit/server-statefulset.bats b/charts/consul/test/unit/server-statefulset.bats index f4ab239aa8..13fecfd7ff 100755 --- a/charts/consul/test/unit/server-statefulset.bats +++ b/charts/consul/test/unit/server-statefulset.bats @@ -627,11 +627,7 @@ load _helpers local actual=$(helm template \ -s templates/server-statefulset.yaml \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1983,13 +1979,7 @@ load _helpers --set 'global.secretsBackend.vault.consulClientRole=test' \ --set 'global.secretsBackend.vault.consulServerRole=foo' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -3008,31 +2998,3 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ yq -r '.spec.template.spec.containers[1].command[2] | contains("-interval=10h34m5s")' | tee /dev/stderr) [ "${actual}" = "true" ] } - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "server/StatefulSet: experiments=[\"resource-apis\"] is not set in command when global.experiments is empty" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - . | tee /dev/stderr) - - # Test the flag is set. - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) - [ "${actual}" = "false" ] -} - -@test "server/StatefulSet: experiments=[\"resource-apis\"] is set in command when global.experiments contains \"resource-apis\"" { - cd `chart_dir` - local object=$(helm template \ - -s templates/server-statefulset.yaml \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - . | tee /dev/stderr) - - local actual=$(echo "$object" | - yq '.spec.template.spec.containers[] | select(.name == "consul") | .command | any(contains("-hcl=\"experiments=[\\\"resource-apis\\\"]\""))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} \ No newline at end of file diff --git a/charts/consul/test/unit/sync-catalog-deployment.bats b/charts/consul/test/unit/sync-catalog-deployment.bats index 0c9579df20..d8321eefdf 100755 --- a/charts/consul/test/unit/sync-catalog-deployment.bats +++ b/charts/consul/test/unit/sync-catalog-deployment.bats @@ -984,10 +984,7 @@ load _helpers -s templates/sync-catalog-deployment.yaml \ --set 'syncCatalog.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject")' | tee /dev/stderr) [ "${actual}" = "{}" ] } @@ -1236,12 +1233,7 @@ load _helpers --set 'global.tls.caCert.secretName=foo' \ --set 'global.secretsBackend.vault.consulCARole=carole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/telemetry-collector-deployment.bats b/charts/consul/test/unit/telemetry-collector-deployment.bats index 432200541b..ad50341061 100755 --- a/charts/consul/test/unit/telemetry-collector-deployment.bats +++ b/charts/consul/test/unit/telemetry-collector-deployment.bats @@ -1198,18 +1198,4 @@ MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ local actual=$(echo "$cmd" | yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "telemetryCollector/Deployment: disabled when V2 is enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - . } \ No newline at end of file diff --git a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats b/charts/consul/test/unit/telemetry-collector-v2-deployment.bats deleted file mode 100755 index f1d5de2597..0000000000 --- a/charts/consul/test/unit/telemetry-collector-v2-deployment.bats +++ /dev/null @@ -1,1350 +0,0 @@ -#!/usr/bin/env bats - -load _helpers - -@test "telemetryCollector/Deployment(V2): disabled by default" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - . -} - -@test "telemetryCollector/Deployment(V2): fails if no image is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=null' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "telemetryCollector.image must be set to enable consul-telemetry-collector" ]] -} - -@test "telemetryCollector/Deployment(V2): disable with telemetry-collector.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment(V2): disable with global.enabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'global.enabled=false' \ - . -} - -@test "telemetryCollector/Deployment(V2): container image overrides" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].image' | tee /dev/stderr) - [ "${actual}" = "\"bar\"" ] -} - -#-------------------------------------------------------------------- -# nodeSelector - -@test "telemetryCollector/Deployment(V2): nodeSelector is not set by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment(V2): specified nodeSelector" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.nodeSelector=testing' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.nodeSelector' | tee /dev/stderr) - [ "${actual}" = "testing" ] -} - -#-------------------------------------------------------------------- -# consul.name - -@test "telemetryCollector/Deployment(V2): name is constant regardless of consul name" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'consul.name=foobar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].name' | tee /dev/stderr) - [ "${actual}" = "consul-telemetry-collector" ] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volume when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment(V2): Adds tls-ca-cert volumeMounts when global.tls.enabled is true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" != "" ] -} - -@test "telemetryCollector/Deployment(V2): can overwrite CA secret with the provided one" { - cd `chart_dir` - local ca_cert_volume=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.caCert.secretName=foo-ca-cert' \ - --set 'global.tls.caCert.secretKey=key' \ - --set 'global.tls.caKey.secretName=foo-ca-key' \ - --set 'global.tls.caKey.secretKey=key' \ - . | tee /dev/stderr | - yq '.spec.template.spec.volumes[] | select(.name=="consul-ca-cert")' | tee /dev/stderr) - - # check that the provided ca cert secret is attached as a volume - local actual - actual=$(echo $ca_cert_volume | jq -r '.secret.secretName' | tee /dev/stderr) - [ "${actual}" = "foo-ca-cert" ] - - # check that the volume uses the provided secret key - actual=$(echo $ca_cert_volume | jq -r '.secret.items[0].key' | tee /dev/stderr) - [ "${actual}" = "key" ] -} - -#-------------------------------------------------------------------- -# global.tls.enableAutoEncrypt - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volumeMount is added when TLS with auto-encrypt is enabled without clients" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[1].volumeMounts[] | select(.name == "consul-ca-cert") | length > 0' | tee - /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume is not added if externalServers.enabled=true and externalServers.useSystemRoots=true" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=foo.com' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.volumes[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} - -#-------------------------------------------------------------------- -# resources - -@test "telemetryCollector/Deployment(V2): resources has default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "1000m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "512Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "1000m" ] -} - -@test "telemetryCollector/Deployment(V2): resources can be overridden" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.resources.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].resources.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# init container resources - -@test "telemetryCollector/Deployment(V2): init container has default resources" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - [ $(echo "${actual}" | yq -r '.requests.memory') = "25Mi" ] - [ $(echo "${actual}" | yq -r '.requests.cpu') = "50m" ] - [ $(echo "${actual}" | yq -r '.limits.memory') = "150Mi" ] - [ $(echo "${actual}" | yq -r '.limits.cpu') = "50m" ] -} - -@test "telemetryCollector/Deployment(V2): init container resources can be set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'telemetryCollector.initContainer.resources.requests.memory=memory' \ - --set 'telemetryCollector.initContainer.resources.requests.cpu=cpu' \ - --set 'telemetryCollector.initContainer.resources.limits.memory=memory2' \ - --set 'telemetryCollector.initContainer.resources.limits.cpu=cpu2' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].resources' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.requests.memory' | tee /dev/stderr) - [ "${actual}" = "memory" ] - - local actual=$(echo $object | yq -r '.requests.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu" ] - - local actual=$(echo $object | yq -r '.limits.memory' | tee /dev/stderr) - [ "${actual}" = "memory2" ] - - local actual=$(echo $object | yq -r '.limits.cpu' | tee /dev/stderr) - [ "${actual}" = "cpu2" ] -} - -#-------------------------------------------------------------------- -# priorityClassName - -@test "telemetryCollector/Deployment(V2): no priorityClassName by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "null" ] -} - -@test "telemetryCollector/Deployment(V2): can set a priorityClassName" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.priorityClassName=name' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.priorityClassName' | tee /dev/stderr) - - [ "${actual}" = "name" ] -} - -#-------------------------------------------------------------------- -# replicas - -@test "telemetryCollector/Deployment(V2): replicas defaults to 1" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "1" ] -} - -@test "telemetryCollector/Deployment(V2): replicas can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'telemetryCollector.replicas=3' \ - . | tee /dev/stderr | - yq '.spec.replicas' | tee /dev/stderr) - - [ "${actual}" = "3" ] -} - -#-------------------------------------------------------------------- -# Vault - -@test "telemetryCollector/Deployment(V2): vault CA is not configured by default" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - . | tee /dev/stderr | - yq -r '.spec.template' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') - [ "${actual}" = "false" ] - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') - [ "${actual}" = "false" ] -} - -@test "telemetryCollector/Deployment(V2): vault CA is not configured when secretName is set but secretKey is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.ca.secretName=ca' \ - . | tee /dev/stderr | - yq -r '.spec.template' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') - [ "${actual}" = "false" ] - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') - [ "${actual}" = "false" ] -} - -@test "telemetryCollector/Deployment(V2): vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.vaultNamespace=vns' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.tls.enableAutoEncrypt=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" - [ "${actual}" = "vns" ] -} - -@test "telemetryCollector/Deployment(V2): correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set without vaultNamespace annotation" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.vaultNamespace=vns' \ - --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/agent-extra-secret: bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.tls.enableAutoEncrypt=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" - [ "${actual}" = "vns" ] -} - -@test "telemetryCollector/Deployment(V2): correct vault namespace annotations is set when global.secretsBackend.vault.vaultNamespace is set and agentAnnotations are also set with vaultNamespace annotation" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.vaultNamespace=vns' \ - --set 'global.secretsBackend.vault.agentAnnotations=vault.hashicorp.com/namespace: bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.tls.enableAutoEncrypt=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/namespace"]' | tee /dev/stderr)" - [ "${actual}" = "bar" ] -} - -@test "telemetryCollector/Deployment(V2): vault CA is not configured when secretKey is set but secretName is not" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ - . | tee /dev/stderr | - yq -r '.spec.template' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/agent-extra-secret")') - [ "${actual}" = "false" ] - local actual=$(echo $object | yq -r '.metadata.annotations | has("vault.hashicorp.com/ca-cert")') - [ "${actual}" = "false" ] -} - -@test "telemetryCollector/Deployment(V2): vault CA is configured when both secretName and secretKey are set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=test' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.ca.secretName=ca' \ - --set 'global.secretsBackend.vault.ca.secretKey=tls.crt' \ - . | tee /dev/stderr | - yq -r '.spec.template' | tee /dev/stderr) - - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/agent-extra-secret"') - [ "${actual}" = "ca" ] - local actual=$(echo $object | yq -r '.metadata.annotations."vault.hashicorp.com/ca-cert"') - [ "${actual}" = "/vault/custom/tls.crt" ] -} - -@test "telemetryCollector/Deployment(V2): vault tls annotations are set when tls is enabled" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=foo' \ - --set 'global.secretsBackend.vault.consulServerRole=bar' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'server.serverCert.secretName=pki_int/issue/test' \ - --set 'global.tls.caCert.secretName=pki_int/cert/ca' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata' | tee /dev/stderr) - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-template-serverca.crt"]' | tee /dev/stderr)" - local expected=$'{{- with secret \"pki_int/cert/ca\" -}}\n{{- .Data.certificate -}}\n{{- end -}}' - [ "${actual}" = "${expected}" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject-secret-serverca.crt"]' | tee /dev/stderr)" - [ "${actual}" = "pki_int/cert/ca" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-init-first"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/agent-inject"]' | tee /dev/stderr)" - [ "${actual}" = "true" ] - - local actual="$(echo $cmd | - yq -r '.annotations["vault.hashicorp.com/role"]' | tee /dev/stderr)" - [ "${actual}" = "test" ] -} - -@test "telemetryCollector/Deployment(V2): vault agent annotations can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=foo' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.tls.caCert.secretName=foo' \ - --set 'global.secretsBackend.vault.enabled=true' \ - --set 'global.secretsBackend.vault.consulClientRole=test' \ - --set 'global.secretsBackend.vault.consulServerRole=foo' \ - --set 'global.secretsBackend.vault.consulCARole=test' \ - --set 'global.secretsBackend.vault.agentAnnotations=foo: bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations.foo' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# telemetryCollector.cloud - -@test "telemetryCollector/Deployment(V2): success with all cloud bits set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId is set and global.cloud.resourceId is not set or global.cloud.clientSecret.secretName is not set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientSecret.secretName=client-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=client-resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=client-resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.clientSecret.secretName is not set but global.cloud.clientId.secretName and global.cloud.resourceId.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.enabled is true and global.cloud.resourceId.secretName is not set but global.cloud.clientId.secretName and global.cloud.clientSecret.secretName is set" { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When global.cloud.enabled is true, global.cloud.resourceId.secretName, global.cloud.clientId.secretName, and global.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.resourceId.secretName is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - [[ "$output" =~ "When either global.cloud.resourceId.secretName or global.cloud.resourceId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretName is set but global.cloud.authURL.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.authURL.secretKey is set but global.cloud.authURL.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.authUrl.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.authUrl.secretName or global.cloud.authUrl.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretName is set but global.cloud.apiHost.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretName=auth-url-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.apiHost.secretKey is set but global.cloud.apiHost.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.apiHost.secretKey=auth-url-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.apiHost.secretName or global.cloud.apiHost.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretName is set but global.cloud.scadaAddress.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretName=scada-address-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when global.cloud.scadaAddress.secretKey is set but global.cloud.scadaAddress.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'global.tls.enableAutoEncrypt=true' \ - --set 'global.datacenter=dc-foo' \ - --set 'global.domain=bar' \ - --set 'global.cloud.enabled=true' \ - --set 'global.cloud.clientId.secretName=client-id-name' \ - --set 'global.cloud.clientId.secretKey=client-id-key' \ - --set 'global.cloud.clientSecret.secretName=client-secret-id-name' \ - --set 'global.cloud.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - --set 'global.cloud.resourceId.secretKey=resource-id-key' \ - --set 'global.cloud.scadaAddress.secretKey=scada-address-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either global.cloud.scadaAddress.secretName or global.cloud.scadaAddress.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetryCollector.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-id-key' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretKey is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'global.resourceId.secretName=resource-id-name' \ - --set 'global.resourceId.secretKey=resource-id-key' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetryCollector.cloud.clientId.secretName is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.clientSecret.secretName=client-secret-id-name' \ - --set 'telemetryCollector.clientSecret.secretKey=client-secret-key-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When telemetryCollector.cloud.clientId.secretName is set, global.cloud.resourceId.secretName, telemetryCollector.cloud.clientSecret.secretName must also be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId.secretName is set but telemetry.cloud.clientId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientId.secretName or telemetryCollector.cloud.clientId.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientSecret.secretName is set but telemetry.cloud.clientSecret.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When either telemetryCollector.cloud.clientSecret.secretName or telemetryCollector.cloud.clientSecret.secretKey is defined, both must be set." ]] -} - -@test "telemetryCollector/Deployment(V2): fails when telemetryCollector.cloud.clientId and telemetryCollector.cloud.clientSecret is set but global.cloud.resourceId.secretKey is not set." { - cd `chart_dir` - run helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.cloud.clientId.secretName=client-id-name' \ - --set 'telemetryCollector.cloud.clientId.secretKey=client-id-key' \ - --set 'telemetryCollector.cloud.clientSecret.secretName=client-secret-name' \ - --set 'telemetryCollector.cloud.clientSecret.secretKey=client-secret-key' \ - --set 'global.cloud.resourceId.secretName=resource-id-name' \ - . - [ "$status" -eq 1 ] - - [[ "$output" =~ "When telemetryCollector has clientId and clientSecret .global.cloud.resourceId.secretKey must be set" ]] -} - -#-------------------------------------------------------------------- -# global.tls.enabled - -@test "telemetryCollector/Deployment(V2): sets -tls-disabled args when when not using TLS." { - cd `chart_dir` - - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=false' \ - . | yq -r .spec.template.spec.containers[1].args) - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-disabled"))') - [ "${actual}" = 'true' ] - -} - -@test "telemetryCollector/Deployment(V2): -ca-certs set correctly when using TLS." { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# External Server - -@test "telemetryCollector/Deployment(V2): sets external server args when global.tls.enabled and externalServers.enabled" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.tls.enabled=true' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.httpsPort=8501' \ - --set 'externalServers.tlsServerName=foo.tls.server' \ - --set 'externalServers.useSystemRoots=true' \ - --set 'server.enabled=false' \ - --set 'client.enabled=false' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo $flags | yq -r '. | any(contains("-ca-certs=/consul/tls/ca/tls.crt"))' | tee /dev/stderr) - [ "${actual}" = 'false' ] - - local actual=$(echo $flags | yq -r '. | any(contains("-tls-server-name=foo.tls.server"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] - - local actual=$(echo $flags | jq -r '. | any(contains("-addresses=external-consul.host"))' | tee /dev/stderr) - [ "${actual}" = 'true' ] -} - -#-------------------------------------------------------------------- -# Admin Partitions -# TODO: re-enable this test when V2 supports admin partitions. - -# @test "telemetryCollector/Deployment: partition flags are set when using admin partitions" { -# cd `chart_dir` -# local flags=$(helm template \ -# -s templates/telemetry-collector-deployment.yaml \ -# --set 'ui.enabled=false' \ -# --set 'global.experiments[0]=resource-apis' \ -# --set 'telemetryCollector.enabled=true' \ -# --set 'telemetryCollector.image=bar' \ -# --set 'global.enableConsulNamespaces=true' \ -# --set 'global.adminPartitions.enabled=true' \ -# --set 'global.adminPartitions.name=hashi' \ -# --set 'global.acls.manageSystemACLs=true' \ -# . | tee /dev/stderr | -# yq '.spec.template.spec.containers[1].args' | tee /dev/stderr) -# -# local actual=$(echo $flags | jq -r '. | any(contains("-login-partition=hashi"))' | tee /dev/stderr) -# [ "${actual}" = 'true' ] -# -# local actual=$(echo $flags | jq -r '. | any(contains("-service-partition=hashi"))' | tee /dev/stderr) -# [ "${actual}" = "true" ] -# } - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} - -@test "telemetryCollector/Deployment(V2): config volume mount is set when config exists" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].volumeMounts[] | select(.name == "config") | .name' | tee /dev/stderr) - [ "${actual}" = "config" ] -} - -@test "telemetryCollector/Deployment(V2): config flag is set when config exists" { - cd `chart_dir` - local flags=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'telemetryCollector.customExporterConfig="foo"' \ - . | tee /dev/stderr | - yq '.spec.template.spec.containers[0].command') - - local actual=$(echo $flags | yq -r '. | any(contains("-config-file-path /consul/config/config.json"))') - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): consul-ca-cert volume mount is not set on acl-init when using externalServers and useSystemRoots" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.acls.manageSystemACLs=true' \ - --set 'global.tls.enabled=true' \ - --set 'server.enabled=false' \ - --set 'externalServers.hosts[0]=external-consul.host' \ - --set 'externalServers.enabled=true' \ - --set 'externalServers.useSystemRoots=true' \ - . | tee /dev/stderr | - yq '.spec.template.spec.initContainers[1].volumeMounts[] | select(.name == "consul-ca-cert")' | tee /dev/stderr) - [ "${actual}" = "" ] -} -#-------------------------------------------------------------------- -# trustedCAs - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set command is modified correctly" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): trustedCAs: if multiple Trusted cas were set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - --set 'global.trustedCAs[1]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0]' | tee /dev/stderr) - - - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-0.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] - local actual=$(echo $object | jq '.command[2] | contains("cat < /trusted-cas/custom-ca-1.pem")' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set /trusted-cas volumeMount is added" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec' | tee /dev/stderr) - local actual=$(echo $object | jq -r '.volumes[] | select(.name == "trusted-cas") | .name' | tee /dev/stderr) - [ "${actual}" = "trusted-cas" ] -} - - -@test "telemetryCollector/Deployment(V2): trustedCAs: if trustedCAs is set SSL_CERT_DIR env var is set" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'global.trustedCAs[0]=-----BEGIN CERTIFICATE----- -MIICFjCCAZsCCQCdwLtdjbzlYzAKBggqhkjOPQQDAjB0MQswCQYDVQQGEwJDQTEL' \ - . | tee /dev/stderr | yq -r '.spec.template.spec.containers[0].env[] | select(.name == "SSL_CERT_DIR")' | tee /dev/stderr) - - local actual=$(echo $object | jq -r '.name' | tee /dev/stderr) - [ "${actual}" = "SSL_CERT_DIR" ] - local actual=$(echo $object | jq -r '.value' | tee /dev/stderr) - [ "${actual}" = "/etc/ssl/certs:/trusted-cas" ] -} - -#-------------------------------------------------------------------- -# extraLabels - -@test "telemetryCollector/Deployment(V2): no extra labels defined by default" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.metadata.labels | del(."app") | del(."chart") | del(."release") | del(."component") | del(."consul.hashicorp.com/connect-inject-managed-by")' \ - | tee /dev/stderr) - [ "${actual}" = "{}" ] -} - -@test "telemetryCollector/Deployment(V2): extra global labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.extraLabels.foo=bar' \ - . | tee /dev/stderr) - local actualBar=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) - [ "${actualBar}" = "bar" ] - local actualTemplateBar=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) - [ "${actualTemplateBar}" = "bar" ] -} - -@test "telemetryCollector/Deployment(V2): multiple global extra labels can be set" { - cd `chart_dir` - local actual=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - --set 'global.extraLabels.foo=bar' \ - --set 'global.extraLabels.baz=qux' \ - . | tee /dev/stderr) - local actualFoo=$(echo "${actual}" | yq -r '.metadata.labels.foo' | tee /dev/stderr) - local actualBaz=$(echo "${actual}" | yq -r '.metadata.labels.baz' | tee /dev/stderr) - [ "${actualFoo}" = "bar" ] - [ "${actualBaz}" = "qux" ] - local actualTemplateFoo=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.foo' | tee /dev/stderr) - local actualTemplateBaz=$(echo "${actual}" | yq -r '.spec.template.metadata.labels.baz' | tee /dev/stderr) - [ "${actualTemplateFoo}" = "bar" ] - [ "${actualTemplateBaz}" = "qux" ] -} - -#-------------------------------------------------------------------- -# extraEnvironmentVariables - -@test "telemetryCollector/Deployment(V2): extra environment variables" { - cd `chart_dir` - local object=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.extraEnvironmentVars.HCP_AUTH_TLS=insecure' \ - --set 'telemetryCollector.extraEnvironmentVars.foo=bar' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[0].env' | tee /dev/stderr) - - local actual=$(echo $object | - yq -r 'map(select(.name == "HCP_AUTH_TLS")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "insecure" ] - - local actual=$(echo $object | - yq -r 'map(select(.name == "foo")) | .[0].value' | tee /dev/stderr) - [ "${actual}" = "bar" ] -} - -#-------------------------------------------------------------------- -# logLevel - -@test "telemetryCollector/Deployment(V2): use global.logLevel by default" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=warn' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.initContainers[0].command' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=warn"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): use global.logLevel by default for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=info"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -@test "telemetryCollector/Deployment(V2): override global.logLevel when telemetryCollector.logLevel is set for dataplane container" { - cd `chart_dir` - local cmd=$(helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'ui.enabled=false' \ - --set 'global.experiments[0]=resource-apis' \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.logLevel=debug' \ - . | tee /dev/stderr | - yq -r '.spec.template.spec.containers[1].args' | tee /dev/stderr) - - local actual=$(echo "$cmd" | - yq 'any(contains("-log-level=debug"))' | tee /dev/stderr) - [ "${actual}" = "true" ] -} - -#-------------------------------------------------------------------- -# global.experiments=["resource-apis"] - -@test "telemetryCollector/Deployment(V2): disabled when V2 is disabled" { - cd `chart_dir` - assert_empty helm template \ - -s templates/telemetry-collector-v2-deployment.yaml \ - --set 'telemetryCollector.enabled=true' \ - --set 'telemetryCollector.image=bar' \ - . -} diff --git a/charts/consul/test/unit/terminating-gateways-deployment.bats b/charts/consul/test/unit/terminating-gateways-deployment.bats index 0c4ab7dcca..5e10c5b1fd 100644 --- a/charts/consul/test/unit/terminating-gateways-deployment.bats +++ b/charts/consul/test/unit/terminating-gateways-deployment.bats @@ -871,7 +871,7 @@ load _helpers --set 'connectInject.enabled=true' \ . | tee /dev/stderr | yq -s -r '.[0].spec.template.metadata.annotations | length' | tee /dev/stderr) - [ "${actual}" = "4" ] + [ "${actual}" = "3" ] } @test "terminatingGateways/Deployment: extra annotations can be set through defaults" { @@ -886,7 +886,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -908,7 +908,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "6" ] + [ "${actual}" = "5" ] local actual=$(echo $object | yq -r '.key1' | tee /dev/stderr) [ "${actual}" = "value1" ] @@ -931,7 +931,7 @@ key2: value2' \ yq -s -r '.[0].spec.template.metadata.annotations' | tee /dev/stderr) local actual=$(echo $object | yq '. | length' | tee /dev/stderr) - [ "${actual}" = "7" ] + [ "${actual}" = "6" ] local actual=$(echo $object | yq -r '.defaultkey' | tee /dev/stderr) [ "${actual}" = "defaultvalue" ] @@ -1214,13 +1214,7 @@ key2: value2' \ --set 'global.tls.caCert.secretName=foo' \ --set 'global.secretsBackend.vault.consulCARole=carole' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."vault.hashicorp.com/agent-inject") | - del(."vault.hashicorp.com/role") | - del(."consul.hashicorp.com/gateway-consul-service-name") | - del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."vault.hashicorp.com/agent-inject") | del(."vault.hashicorp.com/role") | del(."consul.hashicorp.com/gateway-consul-service-name") | del(."consul.hashicorp.com/gateway-kind")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-cleanup-job.bats b/charts/consul/test/unit/tls-init-cleanup-job.bats index 2e72033396..735d991780 100644 --- a/charts/consul/test/unit/tls-init-cleanup-job.bats +++ b/charts/consul/test/unit/tls-init-cleanup-job.bats @@ -145,11 +145,7 @@ load _helpers -s templates/tls-init-cleanup-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/test/unit/tls-init-job.bats b/charts/consul/test/unit/tls-init-job.bats index 1c5b7ae7a1..f71edc43d5 100644 --- a/charts/consul/test/unit/tls-init-job.bats +++ b/charts/consul/test/unit/tls-init-job.bats @@ -262,11 +262,7 @@ load _helpers -s templates/tls-init-job.yaml \ --set 'global.tls.enabled=true' \ . | tee /dev/stderr | - yq -r '.spec.template.metadata.annotations | - del(."consul.hashicorp.com/connect-inject") | - del(."consul.hashicorp.com/mesh-inject") | - del(."consul.hashicorp.com/config-checksum")' | - tee /dev/stderr) + yq -r '.spec.template.metadata.annotations | del(."consul.hashicorp.com/connect-inject") | del(."consul.hashicorp.com/config-checksum")' | tee /dev/stderr) [ "${actual}" = "{}" ] } diff --git a/charts/consul/todo.txt b/charts/consul/todo.txt new file mode 100644 index 0000000000..c79bef389b --- /dev/null +++ b/charts/consul/todo.txt @@ -0,0 +1,3 @@ + +- [x] Remove gatewayclass gatewayclassconfig bats +- [ ] Add test for each of the CRDs diff --git a/charts/consul/values.yaml b/charts/consul/values.yaml index c9abd21713..c1622f99fd 100644 --- a/charts/consul/values.yaml +++ b/charts/consul/values.yaml @@ -66,7 +66,7 @@ global: # image: "hashicorp/consul-enterprise:1.10.0-ent" # ``` # @default: hashicorp/consul: - image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.18-dev + image: docker.mirror.hashicorp.services/hashicorppreview/consul:1.16-dev # Array of objects containing image pull secret names that will be applied to each service account. # This can be used to reference image pull secrets if using a custom consul or consul-k8s-control-plane Docker image. @@ -86,7 +86,7 @@ global: # image that is used for functionality such as catalog sync. # This can be overridden per component. # @default: hashicorp/consul-k8s-control-plane: - imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.4-dev + imageK8S: docker.mirror.hashicorp.services/hashicorppreview/consul-k8s-control-plane:1.2.4-dev # The name of the datacenter that the agents should # register as. This can't be changed once the Consul cluster is up and running @@ -293,7 +293,7 @@ global: # The key within the Kubernetes secret or Vault secret key that holds the gossip # encryption key. secretKey: "" - # Override global log verbosity level for gossip-encryption-autogenerate-job pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `gossip-encryption-autogenerate-job` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -391,7 +391,7 @@ global: secretKey: null # This value defines additional annotations for - # tls init jobs. This should be formatted as a multi-line string. + # tls init jobs. Format this value as a multi-line string. # # ```yaml # annotations: | @@ -517,7 +517,7 @@ global: nodeSelector: null # This value defines additional annotations for - # acl init jobs. This should be formatted as a multi-line string. + # acl init jobs. Format this value as a multi-line string. # # ```yaml # annotations: | @@ -601,7 +601,7 @@ global: # @type: string k8sAuthMethodHost: null - # Override global log verbosity level for the create-federation-secret-job pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for the `create-federation-secret-job` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -639,7 +639,7 @@ global: # The name (and tag) of the consul-dataplane Docker image used for the # connect-injected sidecar proxies and mesh, terminating, and ingress gateways. # @default: hashicorp/consul-dataplane: - imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.4-dev + imageConsulDataplane: docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.2-dev # Configuration for running this Helm chart on the Red Hat OpenShift platform. # This Helm chart currently supports OpenShift v4.x+. @@ -745,25 +745,7 @@ global: # ] # ``` # @type: array - trustedCAs: [ ] - - # Consul feature flags that will be enabled across components. - # Supported feature flags: - # * `resource-apis`: - # _**Danger**_! This feature is under active development. It is not - # recommended for production use. Setting this flag during an - # upgrade could risk breaking your Consul cluster. - # If this flag is set, Consul components will use the - # V2 resources APIs for all operations. - # - # Example: - # - # ```yaml - # experiments: [ "resource-apis" ] - # ``` - # @type: array - experiments: [ ] - + trustedCAs: [] # Server, when enabled, configures a server cluster to run. This should # be disabled if you plan on connecting to a Consul cluster external to @@ -1256,6 +1238,43 @@ server: # @type: string caCert: null + # Settings for potentially limiting timeouts, rate limiting on clients as well + # as servers, and other settings to limit exposure too many requests, requests + # waiting for too long, and other runtime considerations. + limits: + # This object specifies configurations that limit the rate of RPC and gRPC + # requests on the Consul server. Limiting the rate of gRPC and RPC requests + # also limits HTTP requests to the Consul server. + # https://developer.hashicorp.com/consul/docs/agent/config/config-files#request_limits + requestLimits: + # Setting for disabling or enabling rate limiting. If not disabled, it + # enforces the action that will occur when RequestLimitsReadRate + # or RequestLimitsWriteRate is exceeded. The default value of "disabled" will + # prevent any rate limiting from occuring. A value of "enforce" will block + # the request from processings by returning an error. A value of + # "permissive" will not block the request and will allow the request to + # continue processing. + # @type: string + mode: "disabled" + + # Setting that controls how frequently RPC, gRPC, and HTTP + # queries are allowed to happen. In any large enough time interval, rate + # limiter limits the rate to RequestLimitsReadRate tokens per second. + # + # See https://en.wikipedia.org/wiki/Token_bucket for more about token + # buckets. + # @type: integer + readRate: -1 + + # Setting that controls how frequently RPC, gRPC, and HTTP + # writes are allowed to happen. In any large enough time interval, rate + # limiter limits the rate to RequestLimitsWriteRate tokens per second. + # + # See https://en.wikipedia.org/wiki/Token_bucket for more about token + # buckets. + # @type: integer + writeRate: -1 + # [Enterprise Only] Added in Consul 1.8, the audit object allow users to enable auditing # and configure a sink and filters for their audit logs. Please refer to # [audit logs](https://developer.hashicorp.com/consul/docs/enterprise/audit-logging) documentation @@ -1265,7 +1284,7 @@ server: # global.acls.manageSystemACLs must be enabled to use this feature. enabled: false - # A single entry of the sink object provides configuration for the destination to which Consul + # A single entry of the sink object provides configuration for the destination to which Consul # will log auditing events. # # Example: @@ -1280,7 +1299,7 @@ server: # rotate_duration: 24h # rotate_max_files: 15 # rotate_bytes: 25165824 - # + # # ``` # # The sink object supports the following keys: @@ -1310,43 +1329,6 @@ server: # @type: array sinks: [] - # Settings for potentially limiting timeouts, rate limiting on clients as well - # as servers, and other settings to limit exposure too many requests, requests - # waiting for too long, and other runtime considerations. - limits: - # This object specifies configurations that limit the rate of RPC and gRPC - # requests on the Consul server. Limiting the rate of gRPC and RPC requests - # also limits HTTP requests to the Consul server. - # https://developer.hashicorp.com/consul/docs/agent/config/config-files#request_limits - requestLimits: - # Setting for disabling or enabling rate limiting. If not disabled, it - # enforces the action that will occur when RequestLimitsReadRate - # or RequestLimitsWriteRate is exceeded. The default value of "disabled" will - # prevent any rate limiting from occuring. A value of "enforce" will block - # the request from processings by returning an error. A value of - # "permissive" will not block the request and will allow the request to - # continue processing. - # @type: string - mode: "disabled" - - # Setting that controls how frequently RPC, gRPC, and HTTP - # queries are allowed to happen. In any large enough time interval, rate - # limiter limits the rate to RequestLimitsReadRate tokens per second. - # - # See https://en.wikipedia.org/wiki/Token_bucket for more about token - # buckets. - # @type: integer - readRate: -1 - - # Setting that controls how frequently RPC, gRPC, and HTTP - # writes are allowed to happen. In any large enough time interval, rate - # limiter limits the rate to RequestLimitsWriteRate tokens per second. - # - # See https://en.wikipedia.org/wiki/Token_bucket for more about token - # buckets. - # @type: integer - writeRate: -1 - # Configuration for Consul servers when the servers are running outside of Kubernetes. # When running external servers, configuring these values is recommended # if setting `global.tls.enableAutoEncrypt` to true @@ -2252,7 +2234,7 @@ connectInject: # @type: string openshiftSCCName: "restricted-v2" - # This value defines the amount we will add to privileged container ports on gateways that use this class. + # This value defines the amount Consul will add to privileged container ports on gateways that use this class. # This is useful if you don't want to give your containers extra permissions to run privileged ports. # Example: The gateway listener is defined on port 80, but the underlying value of the port on the container # will be the 80 + the number defined below. @@ -2672,16 +2654,16 @@ connectInject: # - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path` # @type: map lifecycle: - # @type: boolean - defaultEnabled: true - # @type: boolean - defaultEnableShutdownDrainListeners: true - # @type: integer - defaultShutdownGracePeriodSeconds: 30 - # @type: integer - defaultGracefulPort: 20600 - # @type: string - defaultGracefulShutdownPath: "/graceful_shutdown" + # @type: boolean + defaultEnabled: true + # @type: boolean + defaultEnableShutdownDrainListeners: true + # @type: integer + defaultShutdownGracePeriodSeconds: 30 + # @type: integer + defaultGracefulPort: 20600 + # @type: string + defaultGracefulShutdownPath: "/graceful_shutdown" # The resource settings for the Connect injected init container. If null, the resources # won't be set for the initContainer. The defaults are optimized for developer instances of @@ -2712,7 +2694,7 @@ meshGateway: # Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. enabled: false - # Override global log verbosity level for mesh-gateway-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `mesh-gateway-deployment` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -2928,7 +2910,7 @@ ingressGateways: # Enable ingress gateway deployment. Requires `connectInject.enabled=true`. enabled: false - # Override global log verbosity level for ingress-gateways-deployment pods. One of "trace", "debug", "info", "warn", or "error". + # Override global log verbosity level for `ingress-gateways-deployment` pods. One of "trace", "debug", "info", "warn", or "error". # @type: string logLevel: "" @@ -3528,4 +3510,4 @@ telemetryCollector: # feature, in case kubernetes cluster is behind egress http proxies. Additionally, # it could be used to configure custom consul parameters. # @type: map - extraEnvironmentVars: { } + extraEnvironmentVars: {} diff --git a/cli/cmd/envoy-stats/envoy_stats.go b/cli/cmd/envoy-stats/envoy_stats.go index 9846e4f703..dd0c884c3d 100644 --- a/cli/cmd/envoy-stats/envoy_stats.go +++ b/cli/cmd/envoy-stats/envoy_stats.go @@ -182,10 +182,10 @@ func (c *Command) setupKubeClient(settings *helmCLI.EnvSettings) error { // Help returns a description of the command and how it is used. func (c *Command) Help() string { c.once.Do(c.init) - return c.Synopsis() + "\n\nUsage: consul-k8s envoy-stats [flags]\n\n" + c.help + return c.Synopsis() + "\n\nUsage: consul-k8s status [flags]\n\n" + c.help } // Synopsis returns a one-line command summary. func (c *Command) Synopsis() string { - return "Displays Envoy Stats for the pod in a particular namespace" + return "Check the status of a Consul installation on Kubernetes." } diff --git a/cli/cmd/envoy-stats/envoy_stats_test.go b/cli/cmd/envoy-stats/envoy_stats_test.go index 1774dd9c65..3346829b5b 100644 --- a/cli/cmd/envoy-stats/envoy_stats_test.go +++ b/cli/cmd/envoy-stats/envoy_stats_test.go @@ -99,6 +99,39 @@ func TestEnvoyStats(t *testing.T) { }, }, }, + "All kinds of Pods": { + namespace: "default", + pods: []v1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + Labels: map[string]string{ + "consul.hashicorp.com/connect-inject-status": "injected", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-gateway", + Namespace: "default", + Labels: map[string]string{ + "component": "mesh-gateway", + "chart": "consul-helm", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "api-gateway", + Namespace: "default", + Labels: map[string]string{ + "api-gateway.consul.hashicorp.com/managed": "true", + }, + }, + }, + }, + }, "Pods in consul namespaces": { namespace: "consul", pods: []v1.Pod{ @@ -130,13 +163,10 @@ func TestEnvoyStats(t *testing.T) { c := setupCommand(new(bytes.Buffer)) c.kubernetes = fake.NewSimpleClientset(&v1.PodList{Items: tc.pods}) c.flagNamespace = tc.namespace - for _, pod := range tc.pods { - c.flagPod = pod.Name - mpf := &MockPortForwarder{} - resp, err := c.getEnvoyStats(mpf) - require.NoError(t, err) - require.Equal(t, resp, "Envoy Stats") - } + mpf := &MockPortForwarder{} + resp, err := c.getEnvoyStats(mpf) + require.NoError(t, err) + require.Equal(t, resp, "Envoy Stats") }) } srv.Shutdown(context.Background()) diff --git a/cli/go.mod b/cli/go.mod index ea3c122e2e..caf3af423c 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -8,7 +8,7 @@ require ( github.com/fatih/color v1.14.1 github.com/google/go-cmp v0.5.9 github.com/hashicorp/consul-k8s/charts v0.0.0-00010101000000-000000000000 - github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 + github.com/hashicorp/consul/troubleshoot v0.3.1 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/hcp-sdk-go v0.62.1-0.20230913154003-cf69c0370c54 github.com/kr/text v0.2.0 @@ -100,8 +100,8 @@ require ( github.com/gorilla/mux v1.8.0 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect - github.com/hashicorp/consul/api v1.22.0-rc1 // indirect - github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 // indirect + github.com/hashicorp/consul/api v1.24.0 // indirect + github.com/hashicorp/consul/envoyextensions v0.4.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect diff --git a/cli/go.sum b/cli/go.sum index 23eba93b3a..f392d224e3 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -436,14 +436,14 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.22.0-rc1 h1:ePmGqndeMgaI38KUbSA/CqTzeEAIogXyWnfNJzglo70= -github.com/hashicorp/consul/api v1.22.0-rc1/go.mod h1:wtduXtbAqSGtBdi3tyA5SSAYGAG51rBejV9SEUBciMY= -github.com/hashicorp/consul/envoyextensions v0.3.0-rc1 h1:weclrwjvLeX+vxPOyo4b4dCDxSpnDl60Z9K16nnCVnI= -github.com/hashicorp/consul/envoyextensions v0.3.0-rc1/go.mod h1:ckxoPHMiWXAe6dhyxmKsX1XqO4KTV64KWIyTu44z8UI= +github.com/hashicorp/consul/api v1.24.0 h1:u2XyStA2j0jnCiVUU7Qyrt8idjRn4ORhK6DlvZ3bWhA= +github.com/hashicorp/consul/api v1.24.0/go.mod h1:NZJGRFYruc/80wYowkPFCp1LbGmJC9L8izrwfyVx/Wg= +github.com/hashicorp/consul/envoyextensions v0.4.1 h1:7s3IXE+qmwjPbZPva+8BjHLrpkFrFkNE+z/6X/O6PQc= +github.com/hashicorp/consul/envoyextensions v0.4.1/go.mod h1:PkLAV99qviACPT7v8Pn7d/vacZixC8/4fuZpR7Rf7vA= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.14.0-rc1 h1:PuETOfN0uxl28i0Pq6rK7TBCrIl7psMbL0YTSje4KvM= -github.com/hashicorp/consul/troubleshoot v0.3.0-rc1 h1:Z6ZUEKILsf85wA/zXK3XMop6IGtjui4ZZ0bAu+JIAz4= -github.com/hashicorp/consul/troubleshoot v0.3.0-rc1/go.mod h1:2WfcYZ8M4vpLtTv9M5Dp3egqSPZ16l5XsqMpO9QUYxc= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/troubleshoot v0.3.1 h1:M3sAzWRT3xXseRoa/Z8q8Kf+Ws0J1xN8nLDj0dC2UIw= +github.com/hashicorp/consul/troubleshoot v0.3.1/go.mod h1:QjmTNmf2Umo/mqTjW+EhDAHEG2l15B81CkcxhF4GALU= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/cli/version/version.go b/cli/version/version.go index da2c79a1b4..01bc4acfbb 100644 --- a/cli/version/version.go +++ b/cli/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.4.0" + Version = "1.2.4" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/control-plane/PROJECT b/control-plane/PROJECT index 7146070824..5338cff047 100644 --- a/control-plane/PROJECT +++ b/control-plane/PROJECT @@ -108,47 +108,4 @@ resources: kind: ControlPlaneRequestLimit path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: RouteRetryFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: hashicorp.com - group: consul - kind: RouteTimeoutFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - domain: hashicorp.com - group: consul - kind: RouteAuthFilter - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - domain: hashicorp.com - group: consul - kind: GatewayPolicy - path: github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1beta1 - namespaced: true - controller: true - domain: consul.hashicorp.com - group: auth - kind: TrafficPermissions - path: github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1 - version: v2beta1 version: "3" diff --git a/control-plane/api-gateway/binding/binder.go b/control-plane/api-gateway/binding/binder.go index c6557985b5..7798a6b49c 100644 --- a/control-plane/api-gateway/binding/binder.go +++ b/control-plane/api-gateway/binding/binder.go @@ -6,15 +6,14 @@ package binding import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) // BinderConfig configures a binder instance with all of the information @@ -53,8 +52,6 @@ type BinderConfig struct { Pods []corev1.Pod // Service is the deployed service associated with the Gateway deployment. Service *corev1.Service - // JWTProviders is the list of all JWTProviders in the cluster - JWTProviders []v1alpha1.JWTProvider // ConsulGateway is the config entry we've created in Consul. ConsulGateway *api.APIGatewayConfigEntry @@ -64,9 +61,6 @@ type BinderConfig struct { // Resources is a map containing all service targets to verify // against the routing backends. Resources *common.ResourceMap - - // Policies is a list containing all GatewayPolicies that are part of the Gateway Deployment - Policies []v1alpha1.GatewayPolicy } // Binder is used for generating a Snapshot of all operations that should occur both @@ -122,10 +116,7 @@ func (b *Binder) Snapshot() *Snapshot { var gatewayValidation gatewayValidationResult var listenerValidation listenerValidationResults - var policyValidation gatewayPolicyValidationResults - var authFilterValidation authFilterValidationResults - authFilters := b.config.Resources.GetExternalAuthFilters() if !isGatewayDeleted { var updated bool @@ -142,8 +133,6 @@ func (b *Binder) Snapshot() *Snapshot { // calculate the status for the gateway gatewayValidation = validateGateway(b.config.Gateway, registrationPods, b.config.ConsulGateway) listenerValidation = validateListeners(b.config.Gateway, b.config.Gateway.Spec.Listeners, b.config.Resources, b.config.GatewayClassConfig) - policyValidation = validateGatewayPolicies(b.config.Gateway, b.config.Policies, b.config.Resources) - authFilterValidation = validateAuthFilters(authFilters, b.config.Resources) } // used for tracking how many routes have successfully bound to which listeners @@ -246,36 +235,6 @@ func (b *Binder) Snapshot() *Snapshot { b.config.Gateway.Status = status snapshot.Kubernetes.StatusUpdates.Add(&b.config.Gateway) } - - for idx, policy := range b.config.Policies { - policy := policy - - var policyStatus v1alpha1.GatewayPolicyStatus - - policyStatus.Conditions = policyValidation.Conditions(policy.Generation, idx) - // only mark the policy as needing a status update if there's a diff with its old status - if !common.GatewayPolicyStatusesEqual(policyStatus, policy.Status) { - b.config.Policies[idx].Status = policyStatus - snapshot.Kubernetes.StatusUpdates.Add(&b.config.Policies[idx]) - } - } - - for idx, authFilter := range authFilters { - if authFilter == nil { - continue - } - authFilter := authFilter - - var filterStatus v1alpha1.RouteAuthFilterStatus - - filterStatus.Conditions = authFilterValidation.Conditions(authFilter.Generation, idx) - - // only mark the filter as needing a status update if there's a diff with its old status - if !common.RouteAuthFilterStatusesEqual(filterStatus, authFilter.Status) { - authFilter.Status = filterStatus - snapshot.Kubernetes.StatusUpdates.Add(authFilter) - } - } } else { // if the gateway has been deleted, unset whatever we've set on it snapshot.Consul.Deletions = append(snapshot.Consul.Deletions, b.nonNormalizedConsulKey) @@ -287,14 +246,8 @@ func (b *Binder) Snapshot() *Snapshot { Namespace: service.Namespace, }) } - if common.RemoveFinalizer(&b.config.Gateway) { snapshot.Kubernetes.Updates.Add(&b.config.Gateway) - for _, policy := range b.config.Policies { - policy := policy - policy.Status = v1alpha1.GatewayPolicyStatus{} - snapshot.Kubernetes.StatusUpdates.Add(&policy) - } } } diff --git a/control-plane/api-gateway/binding/binder_test.go b/control-plane/api-gateway/binding/binder_test.go index b4a274c8fa..7366d1a164 100644 --- a/control-plane/api-gateway/binding/binder_test.go +++ b/control-plane/api-gateway/binding/binder_test.go @@ -23,10 +23,9 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" ) func init() { @@ -62,9 +61,6 @@ type resourceMapResources struct { tcpRoutes []gwv1alpha2.TCPRoute meshServices []v1alpha1.MeshService services []types.NamespacedName - jwtProviders []*v1alpha1.JWTProvider - gatewayPolicies []*v1alpha1.GatewayPolicy - externalAuthFilters []*v1alpha1.RouteAuthFilter consulInlineCertificates []api.InlineCertificateConfigEntry consulHTTPRoutes []api.HTTPRouteConfigEntry consulTCPRoutes []api.TCPRouteConfigEntry @@ -97,17 +93,6 @@ func newTestResourceMap(t *testing.T, resources resourceMapResources) *common.Re for _, r := range resources.consulTCPRoutes { resourceMap.ReferenceCountConsulTCPRoute(r) } - for _, r := range resources.gatewayPolicies { - resourceMap.AddGatewayPolicy(r) - } - for _, r := range resources.jwtProviders { - resourceMap.AddJWTProvider(r) - } - - for _, r := range resources.externalAuthFilters { - resourceMap.AddExternalFilter(r) - } - return resourceMap } @@ -262,7 +247,7 @@ func TestBinder_Lifecycle(t *testing.T) { Type: "ResolvedRefs", Status: metav1.ConditionTrue, Reason: "ResolvedRefs", - Message: "resolved references", + Message: "resolved certificate references", }, }, }}, @@ -775,794 +760,6 @@ func TestBinder_Lifecycle(t *testing.T) { {Kind: api.APIGateway, Name: "gateway-deleted"}, }, }, - "gateway deletion policies": { - config: controlledBinder(BinderConfig{ - Gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{ - { - Name: gwv1beta1.SectionName("l1"), - }, - { - Name: gwv1beta1.SectionName("l2"), - }, - }, - }, - }, - Policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "p1", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - Status: v1alpha1.GatewayPolicyStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: 5, - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: 5, - Message: "resolved references", - }, - }, - }, - }, - { - ObjectMeta: metav1.ObjectMeta{ - Name: "p2", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), - }, - }, - Status: v1alpha1.GatewayPolicyStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: 5, - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: 5, - Message: "resolved references", - }, - }, - }, - }, - }, - }), - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{ - gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }), - }, - }, - expectedStatusUpdates: []client.Object{ - &v1alpha1.GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p1", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - &v1alpha1.GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "p2", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: "Gateway", - Name: "gateway-deleted", - SectionName: common.PointerTo(gwv1beta1.SectionName("l2")), - }, - }, - }, - }, - expectedUpdates: []client.Object{ - addClassConfig(gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-deleted", - DeletionTimestamp: deletionTimestamp, - Finalizers: []string{}, - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: testGatewayClassName, - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }), - }, - expectedConsulUpdates: []api.ConfigEntry{}, - expectedConsulDeletions: []api.ResourceReference{ - {Kind: api.APIGateway, Name: "gateway-deleted"}, - }, - }, - "gateway http route references missing external ref": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - httpRoutes: []gwv1beta1.HTTPRoute{}, - jwtProviders: []*v1alpha1.JWTProvider{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - externalAuthFilters: []*v1alpha1.RouteAuthFilter{}, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "FilterNotFound", - Message: "ref not found", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, - "gateway http route route auth filter references missing jwt provider": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - httpRoutes: []gwv1beta1.HTTPRoute{}, - jwtProviders: []*v1alpha1.JWTProvider{}, - externalAuthFilters: []*v1alpha1.RouteAuthFilter{ - { - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "route-auth", - Namespace: "default", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: v1alpha1.RouteAuthFilterKind, - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "RouteAuthFilter", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "JWTProviderNotFound", - Message: "filter invalid: default/route-auth", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{Kind: "RouteAuthFilter"}, - ObjectMeta: metav1.ObjectMeta{Name: "route-auth", Namespace: "default"}, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Status: v1alpha1.RouteAuthFilterStatus{ - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "False", - Reason: "ReferencesNotValid", - Message: "route filter is not accepted due to errors with references", - }, - { - Type: "ResolvedRefs", - Status: "False", - Reason: "MissingJWTProviderReference", - Message: "route filter references one or more jwt providers that do not exist: missingProviderNames: okta", - }, - }, - }, - }, - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, - "gateway http route route references invalid external ref type": { - resources: resourceMapResources{ - gateways: []gwv1beta1.Gateway{gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{{ - Name: "l1", - Protocol: "HTTP", - }}, - })}, - }, - config: controlledBinder(BinderConfig{ - ConsulGateway: &api.APIGatewayConfigEntry{ - Name: "gateway", - Kind: "api-gateway", - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Protocol: "HTTP", - }, - }, - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - }, - Gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }), - HTTPRoutes: []gwv1beta1.HTTPRoute{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "OhNoThisIsInvalid", - Name: "route-auth", - }, - }}, - }, - }, - }, - }, - testHTTPRoute("http-route-2", []string{"gateway"}, nil), - }, - }), - expectedStatusUpdates: []client.Object{ - &gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "h1", - Finalizers: []string{common.GatewayFinalizer}, - Namespace: "default", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - Name: "gateway", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Filters: []gwv1beta1.HTTPRouteFilter{{ - Type: "ExtensionRef", - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: "OhNoThisIsInvalid", - Name: "route-auth", - }, - }}, - }, - }, - }, - Status: gwv1beta1.HTTPRouteStatus{ - RouteStatus: gwv1beta1.RouteStatus{ - Parents: []gwv1beta1.RouteParentStatus{ - { - ParentRef: gwv1beta1.ParentReference{ - Group: (*gwv1beta1.Group)(&common.BetaGroup), - Kind: common.PointerTo(gwv1beta1.Kind("Gateway")), - Name: "gateway", - Namespace: common.PointerTo(gwv1beta1.Namespace("default")), - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - ControllerName: testControllerName, - Conditions: []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - Message: "resolved backend references", - }, - { - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "UnsupportedValue", - Message: "invalid externalref filter kind", - }, - }, - }, - }, - }, - }, - }, - common.PointerTo(testHTTPRoute("http-route-2", []string{"gateway"}, nil)), - addClassConfig(gatewayWithFinalizerStatus(gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - Protocol: gwv1beta1.HTTPProtocolType, - }, - }, - }, gwv1beta1.GatewayStatus{ - Addresses: []gwv1beta1.GatewayAddress{}, - Conditions: []metav1.Condition{{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - Message: "gateway accepted", - }, { - Type: "Programmed", - Status: metav1.ConditionFalse, - Reason: "Pending", - Message: "gateway pods are still being scheduled", - }}, - Listeners: []gwv1beta1.ListenerStatus{ - { - Name: "l1", - SupportedKinds: []gwv1beta1.RouteGroupKind{{Group: (*gwv1beta1.Group)(&common.BetaGroup), Kind: "HTTPRoute"}}, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "listener accepted", - }, - { - Type: "Programmed", - Status: "True", - Reason: "Programmed", - Message: "listener programmed", - }, - { - Type: "Conflicted", - Status: "False", - Reason: "NoConflicts", - Message: "listener has no conflicts", - }, - { - Type: "ResolvedRefs", - Status: "True", - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - }, - })), - }, - expectedUpdates: []client.Object{}, - expectedConsulDeletions: []api.ResourceReference{}, - expectedConsulUpdates: []api.ConfigEntry{ - &api.APIGatewayConfigEntry{ - Kind: "api-gateway", - Name: "gateway", - Meta: map[string]string{"k8s-name": "gateway", "k8s-namespace": "default"}, - Listeners: []api.APIGatewayListener{{Name: "l1", Protocol: "http"}}, - }, - }, - }, } { t.Run(name, func(t *testing.T) { tt.resources.gateways = append(tt.resources.gateways, tt.config.Gateway) diff --git a/control-plane/api-gateway/binding/result.go b/control-plane/api-gateway/binding/result.go index 38219a2c79..e6c0760e7a 100644 --- a/control-plane/api-gateway/binding/result.go +++ b/control-plane/api-gateway/binding/result.go @@ -25,6 +25,7 @@ var errRefNotPermitted = errors.New("reference not permitted due to lack of Refe var ( // Each of the below are specified in the Gateway spec under RouteConditionReason + // the general usage is that each error is specified as errRoute* where * corresponds // to the RouteConditionReason given in the spec. If a reason is overloaded and can // be used with two different types of things (i.e. something is not found or it's not supported) // then we distinguish those two usages with errRoute*_Usage. @@ -34,9 +35,6 @@ var ( errRouteInvalidKind = errors.New("invalid backend kind") errRouteBackendNotFound = errors.New("backend not found") errRouteNoMatchingParent = errors.New("no matching parent") - errInvalidExternalRefType = errors.New("invalid externalref filter kind") - errExternalRefNotFound = errors.New("ref not found") - errFilterInvalid = errors.New("filter invalid") ) // routeValidationResult holds the result of validating a route globally, in other @@ -178,22 +176,16 @@ func (b bindResults) Condition() metav1.Condition { // if we only have a single binding error, we can get more specific if len(b) == 1 { for _, result := range b { - switch { - case errors.Is(result.err, errRouteNoMatchingListenerHostname): + switch result.err { + case errRouteNoMatchingListenerHostname: // if we have a hostname mismatch error, then use the more specific reason reason = "NoMatchingListenerHostname" - case errors.Is(result.err, errRefNotPermitted): + case errRefNotPermitted: // or if we have a ref not permitted, then use that reason = "RefNotPermitted" - case errors.Is(result.err, errRouteNoMatchingParent): + case errRouteNoMatchingParent: // or if the route declares a parent that we can't find reason = "NoMatchingParent" - case errors.Is(result.err, errExternalRefNotFound): - reason = "FilterNotFound" - case errors.Is(result.err, errFilterInvalid): - reason = "JWTProviderNotFound" - case errors.Is(result.err, errInvalidExternalRefType): - reason = "UnsupportedValue" } } } @@ -243,7 +235,6 @@ var ( errListenerInvalidCertificateRef_InvalidData = errors.New("certificate is invalid or does not contain a supported server name") errListenerInvalidCertificateRef_NonFIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA Keys must be at least 2048-bit") errListenerInvalidCertificateRef_FIPSRSAKeyLen = errors.New("certificate has an invalid length: RSA keys must be either 2048-bit, 3072-bit, or 4096-bit in FIPS mode") - errListenerJWTProviderNotFound = errors.New("policy referencing this listener references unknown JWT provider") errListenerInvalidRouteKinds = errors.New("allowed route kind is invalid") errListenerProgrammed_Invalid = errors.New("listener cannot be programmed because it is invalid") @@ -273,7 +264,7 @@ type listenerValidationResult struct { // status type: Conflicted conflictedErr error // status type: ResolvedRefs - refErrs []error + refErr error // status type: ResolvedRefs (but with internal validation) routeKindErr error } @@ -285,7 +276,7 @@ func (l listenerValidationResult) programmedCondition(generation int64) metav1.C now := timeFunc() switch { - case l.acceptedErr != nil, l.conflictedErr != nil, len(l.refErrs) != 0, l.routeKindErr != nil: + case l.acceptedErr != nil, l.conflictedErr != nil, l.refErr != nil, l.routeKindErr != nil: return metav1.Condition{ Type: "Programmed", Status: metav1.ConditionFalse, @@ -386,78 +377,59 @@ func (l listenerValidationResult) conflictedCondition(generation int64) metav1.C } // acceptedCondition constructs the condition for the ResolvedRefs status type. -func (l listenerValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { +func (l listenerValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { now := timeFunc() - conditions := make([]metav1.Condition, 0) - if l.routeKindErr != nil { - return []metav1.Condition{{ + return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionFalse, Reason: "InvalidRouteKinds", ObservedGeneration: generation, Message: l.routeKindErr.Error(), LastTransitionTime: now, - }} + } } - for _, refErr := range l.refErrs { - switch refErr { - case errListenerInvalidCertificateRef_NotFound, - errListenerInvalidCertificateRef_NotSupported, - errListenerInvalidCertificateRef_InvalidData, - errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, - errListenerInvalidCertificateRef_FIPSRSAKeyLen: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidCertificateRef", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) - case errListenerJWTProviderNotFound: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "InvalidJWTProviderRef", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) - case errRefNotPermitted: - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "RefNotPermitted", - ObservedGeneration: generation, - Message: refErr.Error(), - LastTransitionTime: now, - }) + switch l.refErr { + case errListenerInvalidCertificateRef_NotFound, errListenerInvalidCertificateRef_NotSupported, errListenerInvalidCertificateRef_InvalidData, errListenerInvalidCertificateRef_NonFIPSRSAKeyLen, errListenerInvalidCertificateRef_FIPSRSAKeyLen: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "InvalidCertificateRef", + ObservedGeneration: generation, + Message: l.refErr.Error(), + LastTransitionTime: now, } - } - if len(conditions) == 0 { - conditions = append(conditions, metav1.Condition{ + case errRefNotPermitted: + return metav1.Condition{ + Type: "ResolvedRefs", + Status: metav1.ConditionFalse, + Reason: "RefNotPermitted", + ObservedGeneration: generation, + Message: l.refErr.Error(), + LastTransitionTime: now, + } + default: + return metav1.Condition{ Type: "ResolvedRefs", Status: metav1.ConditionTrue, Reason: "ResolvedRefs", ObservedGeneration: generation, - Message: "resolved references", + Message: "resolved certificate references", LastTransitionTime: now, - }) + } } - return conditions } // Conditions constructs the entire set of conditions for a given gateway listener. func (l listenerValidationResult) Conditions(generation int64) []metav1.Condition { - conditions := []metav1.Condition{ + return []metav1.Condition{ l.acceptedCondition(generation), l.programmedCondition(generation), l.conflictedCondition(generation), + l.resolvedRefsCondition(generation), } - return append(conditions, l.resolvedRefsConditions(generation)...) } // listenerValidationResults holds all of the results for a gateway's listeners @@ -585,157 +557,3 @@ func (l gatewayValidationResult) Conditions(generation int64, listenersInvalid b l.programmedCondition(generation), } } - -type gatewayPolicyValidationResult struct { - acceptedErr error - resolvedRefsErrs []error -} - -type gatewayPolicyValidationResults []gatewayPolicyValidationResult - -var ( - errPolicyListenerReferenceDoesNotExist = errors.New("gateway policy references a listener that does not exist") - errPolicyJWTProvidersReferenceDoesNotExist = errors.New("gateway policy references one or more jwt providers that do not exist") - errNotAcceptedDueToInvalidRefs = errors.New("policy is not accepted due to errors with references") -) - -func (g gatewayPolicyValidationResults) Conditions(generation int64, idx int) []metav1.Condition { - result := g[idx] - return result.Conditions(generation) -} - -func (g gatewayPolicyValidationResult) Conditions(generation int64) []metav1.Condition { - return append([]metav1.Condition{g.acceptedCondition(generation)}, g.resolvedRefsConditions(generation)...) -} - -func (g gatewayPolicyValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.acceptedErr != nil { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "ReferencesNotValid", - ObservedGeneration: generation, - Message: g.acceptedErr.Error(), - LastTransitionTime: now, - } - } - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "gateway policy accepted", - LastTransitionTime: now, - } -} - -func (g gatewayPolicyValidationResult) resolvedRefsConditions(generation int64) []metav1.Condition { - now := timeFunc() - if len(g.resolvedRefsErrs) == 0 { - return []metav1.Condition{ - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: generation, - Message: "resolved references", - LastTransitionTime: now, - }, - } - } - - conditions := make([]metav1.Condition, 0, len(g.resolvedRefsErrs)) - for _, err := range g.resolvedRefsErrs { - switch { - case errors.Is(err, errPolicyListenerReferenceDoesNotExist): - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingListenerReference", - ObservedGeneration: generation, - Message: err.Error(), - LastTransitionTime: now, - }) - case errors.Is(err, errPolicyJWTProvidersReferenceDoesNotExist): - conditions = append(conditions, metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingJWTProviderReference", - ObservedGeneration: generation, - Message: err.Error(), - LastTransitionTime: now, - }) - } - } - return conditions -} - -type authFilterValidationResults []authFilterValidationResult - -type authFilterValidationResult struct { - acceptedErr error - resolvedRefErr error -} - -var ( - errRouteFilterJWTProvidersReferenceDoesNotExist = errors.New("route filter references one or more jwt providers that do not exist") - errRouteFilterNotAcceptedDueToInvalidRefs = errors.New("route filter is not accepted due to errors with references") -) - -func (g authFilterValidationResults) Conditions(generation int64, idx int) []metav1.Condition { - result := g[idx] - return result.Conditions(generation) -} - -func (g authFilterValidationResult) Conditions(generation int64) []metav1.Condition { - return []metav1.Condition{ - g.acceptedCondition(generation), - g.resolvedRefsCondition(generation), - } -} - -func (g authFilterValidationResult) acceptedCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.acceptedErr != nil { - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionFalse, - Reason: "ReferencesNotValid", - ObservedGeneration: generation, - Message: g.acceptedErr.Error(), - LastTransitionTime: now, - } - } - return metav1.Condition{ - Type: "Accepted", - Status: metav1.ConditionTrue, - Reason: "Accepted", - ObservedGeneration: generation, - Message: "route auth filter accepted", - LastTransitionTime: now, - } -} - -func (g authFilterValidationResult) resolvedRefsCondition(generation int64) metav1.Condition { - now := timeFunc() - if g.resolvedRefErr == nil { - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - Reason: "ResolvedRefs", - ObservedGeneration: generation, - Message: "resolved references", - LastTransitionTime: now, - } - } - - return metav1.Condition{ - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - Reason: "MissingJWTProviderReference", - ObservedGeneration: generation, - Message: g.resolvedRefErr.Error(), - LastTransitionTime: now, - } -} diff --git a/control-plane/api-gateway/binding/result_test.go b/control-plane/api-gateway/binding/result_test.go index 327e1733ae..6989bb09ab 100644 --- a/control-plane/api-gateway/binding/result_test.go +++ b/control-plane/api-gateway/binding/result_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -52,21 +51,6 @@ func TestBindResults_Condition(t *testing.T) { Results: bindResults{{section: "", err: errRouteNoMatchingParent}}, Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "NoMatchingParent", Message: "no matching parent"}, }, - { - Name: "external filter ref not found", - Results: bindResults{{section: "", err: errExternalRefNotFound}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "FilterNotFound", Message: "ref not found"}, - }, - { - Name: "jwt provider referenced by external filter is not found", - Results: bindResults{{section: "", err: errFilterInvalid}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "JWTProviderNotFound", Message: "filter invalid"}, - }, - { - Name: "route references invalid filter type", - Results: bindResults{{section: "", err: errInvalidExternalRefType}}, - Expected: metav1.Condition{Type: "Accepted", Status: "False", Reason: "UnsupportedValue", Message: "invalid externalref filter kind"}, - }, { Name: "unhandled error type", Results: bindResults{{section: "abc", err: errors.New("you don't know me")}}, @@ -84,180 +68,3 @@ func TestBindResults_Condition(t *testing.T) { }) } } - -func TestGatewayPolicyValidationResult_Conditions(t *testing.T) { - t.Parallel() - var generation int64 = 5 - for name, tc := range map[string]struct { - results gatewayPolicyValidationResult - expected []metav1.Condition - }{ - "policy valid": { - results: gatewayPolicyValidationResult{}, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "Accepted", - Message: "gateway policy accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - "errors with JWT references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{errorForMissingJWTProviders(map[string]struct{}{"okta": {}})}, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), - }, - }, - }, - "errors with listener references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{errorForMissingListener("gw", "l1")}, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingListenerReference", - Message: errorForMissingListener("gw", "l1").Error(), - }, - }, - }, - "errors with listener and jwt references": { - results: gatewayPolicyValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{ - errorForMissingJWTProviders(map[string]struct{}{"okta": {}}), - errorForMissingListener("gw", "l1"), - }, - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: errorForMissingJWTProviders(map[string]struct{}{"okta": {}}).Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingListenerReference", - Message: errorForMissingListener("gw", "l1").Error(), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) - }) - } -} - -func TestAuthFilterValidationResult_Conditions(t *testing.T) { - t.Parallel() - var generation int64 = 5 - for name, tc := range map[string]struct { - results authFilterValidationResult - expected []metav1.Condition - }{ - "policy valid": { - results: authFilterValidationResult{}, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "Accepted", - Message: "route auth filter accepted", - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionTrue, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ResolvedRefs", - Message: "resolved references", - }, - }, - }, - "errors with JWT references": { - results: authFilterValidationResult{ - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta"), - }, - expected: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "ReferencesNotValid", - Message: errNotAcceptedDueToInvalidRefs.Error(), - }, - { - Type: "ResolvedRefs", - Status: metav1.ConditionFalse, - ObservedGeneration: generation, - LastTransitionTime: timeFunc(), - Reason: "MissingJWTProviderReference", - Message: fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta").Error(), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, tc.results.Conditions(generation)) - }) - } -} diff --git a/control-plane/api-gateway/binding/route_binding.go b/control-plane/api-gateway/binding/route_binding.go index a2a1c49754..8b2e66e761 100644 --- a/control-plane/api-gateway/binding/route_binding.go +++ b/control-plane/api-gateway/binding/route_binding.go @@ -4,9 +4,6 @@ package binding import ( - "fmt" - "strings" - mapset "github.com/deckarep/golang-set" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -14,9 +11,8 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul/api" ) // bindRoute contains the main logic for binding a route to a given gateway. @@ -167,42 +163,6 @@ func (r *Binder) bindRoute(route client.Object, boundCount map[gwv1beta1.Section parent: ref, results: result, }) - - httproute, ok := route.(*gwv1beta1.HTTPRoute) - if ok { - if !externalRefsOnRouteAllExist(httproute, r.config.Resources) { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: errExternalRefNotFound, - }, - }, - }) - } - - if invalidFilterNames := authFilterReferencesMissingJWTProvider(httproute, r.config.Resources); len(invalidFilterNames) > 0 { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: fmt.Errorf("%w: %s", errFilterInvalid, strings.Join(invalidFilterNames, ",")), - }, - }, - }) - } - - if !externalRefsKindAllowedOnRoute(httproute) { - results = append(results, parentBindResult{ - parent: ref, - results: []bindResult{ - { - err: errInvalidExternalRefType, - }, - }, - }) - } - } } updated := false @@ -334,7 +294,6 @@ func (r *Binder) mutateRouteWithBindingResults(snapshot *Snapshot, object client for parent := range parents.Iter() { new.Parents = append(new.Parents, parent.(api.ResourceReference)) } - return new }) case *gwv1alpha2.TCPRoute: diff --git a/control-plane/api-gateway/binding/validation.go b/control-plane/api-gateway/binding/validation.go index ea9208f150..6029c10b24 100644 --- a/control-plane/api-gateway/binding/validation.go +++ b/control-plane/api-gateway/binding/validation.go @@ -4,11 +4,8 @@ package binding import ( - "fmt" "strings" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" klabels "k8s.io/apimachinery/pkg/labels" @@ -18,11 +15,10 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/hashicorp/consul/api" ) var ( @@ -165,70 +161,6 @@ func validateGateway(gateway gwv1beta1.Gateway, pods []corev1.Pod, consulGateway return result } -func validateGatewayPolicies(gateway gwv1beta1.Gateway, policies []v1alpha1.GatewayPolicy, resources *common.ResourceMap) gatewayPolicyValidationResults { - results := make(gatewayPolicyValidationResults, 0, len(policies)) - - for _, policy := range policies { - result := gatewayPolicyValidationResult{ - resolvedRefsErrs: []error{}, - } - - exists := listenerExistsForPolicy(gateway, policy) - if !exists { - result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingListener(policy.Spec.TargetRef.Name, string(*policy.Spec.TargetRef.SectionName))) - } - - missingJWTProviders := make(map[string]struct{}) - if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { - for _, policyJWTProvider := range policy.Spec.Override.JWT.Providers { - _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) - if !jwtExists { - missingJWTProviders[policyJWTProvider.Name] = struct{}{} - } - } - } - - if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { - for _, policyJWTProvider := range policy.Spec.Default.JWT.Providers { - _, jwtExists := resources.GetJWTProviderForGatewayJWTProvider(policyJWTProvider) - if !jwtExists { - missingJWTProviders[policyJWTProvider.Name] = struct{}{} - } - } - } - - if len(missingJWTProviders) > 0 { - result.resolvedRefsErrs = append(result.resolvedRefsErrs, errorForMissingJWTProviders(missingJWTProviders)) - } - - if len(result.resolvedRefsErrs) > 0 { - result.acceptedErr = errNotAcceptedDueToInvalidRefs - } - results = append(results, result) - - } - return results -} - -func listenerExistsForPolicy(gateway gwv1beta1.Gateway, policy v1alpha1.GatewayPolicy) bool { - return gateway.Name == policy.Spec.TargetRef.Name && - slices.ContainsFunc(gateway.Spec.Listeners, func(l gwv1beta1.Listener) bool { return l.Name == *policy.Spec.TargetRef.SectionName }) -} - -func errorForMissingListener(name, listenerName string) error { - return fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, name, listenerName) -} - -func errorForMissingJWTProviders(names map[string]struct{}) error { - namesList := make([]string, 0, len(names)) - for name := range names { - namesList = append(namesList, name) - } - slices.Sort(namesList) - mergedNames := strings.Join(namesList, ",") - return fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, mergedNames) -} - // mergedListener associates a listener with its indexed position // in the gateway spec, it's used to re-associate a status with // a listener after we merge compatible listeners together and then @@ -294,32 +226,6 @@ func validateTLS(gateway gwv1beta1.Gateway, tls *gwv1beta1.GatewayTLSConfig, res return nil, refsErr } -func validateJWT(gateway gwv1beta1.Gateway, listener gwv1beta1.Listener, resources *common.ResourceMap) error { - policy, _ := resources.GetPolicyForGatewayListener(gateway, listener) - if policy == nil { - return nil - } - - if policy.Spec.Override != nil && policy.Spec.Override.JWT != nil { - for _, provider := range policy.Spec.Override.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - return errListenerJWTProviderNotFound - } - } - } - - if policy.Spec.Default != nil && policy.Spec.Default.JWT != nil { - for _, provider := range policy.Spec.Default.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - return errListenerJWTProviderNotFound - } - } - } - return nil -} - func validateCertificateRefs(gateway gwv1beta1.Gateway, refs []gwv1beta1.SecretObjectReference, resources *common.ResourceMap) error { for _, cert := range refs { // Verify that the reference has a group and kind that we support @@ -430,19 +336,9 @@ func validateListeners(gateway gwv1beta1.Gateway, listeners []gwv1beta1.Listener var result listenerValidationResult err, refErr := validateTLS(gateway, listener.TLS, resources) - if refErr != nil { - result.refErrs = append(result.refErrs, refErr) - } - - jwtErr := validateJWT(gateway, listener, resources) - if jwtErr != nil { - result.refErrs = append(result.refErrs, jwtErr) - } - + result.refErr = refErr if err != nil { result.acceptedErr = err - } else if jwtErr != nil { - result.acceptedErr = jwtErr } else { _, supported := supportedKindsForProtocol[listener.Protocol] if !supported { @@ -535,105 +431,6 @@ func routeAllowedForListenerHostname(hostname *gwv1beta1.Hostname, hostnames []g return false } -// externalRefsOnRouteAllExist checks to make sure that all external filters referenced by the route exist in the resource map. -func externalRefsOnRouteAllExist(route *gwv1beta1.HTTPRoute, resources *common.ResourceMap) bool { - for _, rule := range route.Spec.Rules { - for _, filter := range rule.Filters { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - continue - } - - if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { - return false - } - - } - - for _, backendRef := range rule.BackendRefs { - for _, filter := range backendRef.Filters { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - continue - } - - if !resources.ExternalFilterExists(*filter.ExtensionRef, route.Namespace) { - return false - } - } - } - } - - return true -} - -func checkIfReferencesMissingJWTProvider(filter gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap, namespace string, invalidFilters map[string]struct{}) { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - return - } - externalFilter, ok := resources.GetExternalFilter(*filter.ExtensionRef, namespace) - if !ok { - return - } - authFilter, ok := externalFilter.(*v1alpha1.RouteAuthFilter) - if !ok { - return - } - - for _, provider := range authFilter.Spec.JWT.Providers { - _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider) - if !ok { - invalidFilters[fmt.Sprintf("%s/%s", namespace, authFilter.Name)] = struct{}{} - return - } - } -} - -func authFilterReferencesMissingJWTProvider(httproute *gwv1beta1.HTTPRoute, resources *common.ResourceMap) []string { - invalidFilters := make(map[string]struct{}) - for _, rule := range httproute.Spec.Rules { - for _, filter := range rule.Filters { - checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) - } - - for _, backendRef := range rule.BackendRefs { - for _, filter := range backendRef.Filters { - checkIfReferencesMissingJWTProvider(filter, resources, httproute.Namespace, invalidFilters) - } - } - } - - return maps.Keys(invalidFilters) -} - -// externalRefsKindAllowedOnRoute makes sure that all externalRefs reference a kind supported by gatewaycontroller. -func externalRefsKindAllowedOnRoute(route *gwv1beta1.HTTPRoute) bool { - for _, rule := range route.Spec.Rules { - if !filtersAllAllowedType(rule.Filters) { - return false - } - - // same thing but for backendref - for _, backendRef := range rule.BackendRefs { - if !filtersAllAllowedType(backendRef.Filters) { - return false - } - } - } - return true -} - -func filtersAllAllowedType(filters []gwv1beta1.HTTPRouteFilter) bool { - for _, filter := range filters { - if filter.ExtensionRef == nil { - continue - } - - if !common.FilterIsExternalFilter(filter) { - return false - } - } - return true -} - // hostnameMatch checks that an individual hostname matches another hostname for // compatibility. func hostnamesMatch(a gwv1alpha2.Hostname, b gwv1beta1.Hostname) bool { @@ -684,35 +481,6 @@ func routeKindIsAllowedForListenerExplicit(allowedRoutes *gwv1alpha2.AllowedRout return routeKindIsAllowedForListener(allowedRoutes.Kinds, gk) } -func validateAuthFilters(authFilters []*v1alpha1.RouteAuthFilter, resources *common.ResourceMap) authFilterValidationResults { - results := make(authFilterValidationResults, 0, len(authFilters)) - - for _, filter := range authFilters { - if filter == nil { - continue - } - var result authFilterValidationResult - missingJWTProviders := make([]string, 0) - for _, provider := range filter.Spec.JWT.Providers { - if _, ok := resources.GetJWTProviderForGatewayJWTProvider(provider); !ok { - missingJWTProviders = append(missingJWTProviders, provider.Name) - } - } - - if len(missingJWTProviders) > 0 { - mergedNames := strings.Join(missingJWTProviders, ",") - result.resolvedRefErr = fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, mergedNames) - } - - if result.resolvedRefErr != nil { - result.acceptedErr = errRouteFilterNotAcceptedDueToInvalidRefs - } - - results = append(results, result) - } - return results -} - // toNamespaceSet constructs a list of labels used to match a Namespace. func toNamespaceSet(name string, labels map[string]string) klabels.Labels { // If namespace label is not set, implicitly insert it to support older Kubernetes versions diff --git a/control-plane/api-gateway/binding/validation_test.go b/control-plane/api-gateway/binding/validation_test.go index c1c9e250ed..1f2b143387 100644 --- a/control-plane/api-gateway/binding/validation_test.go +++ b/control-plane/api-gateway/binding/validation_test.go @@ -4,10 +4,11 @@ package binding import ( - "fmt" "testing" logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,9 +16,6 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func TestValidateRefs(t *testing.T) { @@ -577,47 +575,35 @@ func TestValidateListeners(t *testing.T) { expectedAcceptedErr error listenerIndexToTest int mapPrivilegedContainerPorts int32 - gateway gwv1beta1.Gateway - resources resourceMapResources }{ "valid protocol HTTP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.HTTPProtocolType}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "valid protocol HTTPS": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.HTTPSProtocolType}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "valid protocol TCP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.TCPProtocolType}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: nil, }, "invalid protocol UDP": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.UDPProtocolType}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: errListenerUnsupportedProtocol, }, "invalid port": { listeners: []gwv1beta1.Listener{ {Protocol: gwv1beta1.TCPProtocolType, Port: 20000}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: errListenerPortUnavailable, }, "conflicted port": { @@ -625,8 +611,6 @@ func TestValidateListeners(t *testing.T) { {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{}, expectedAcceptedErr: errListenerPortUnavailable, listenerIndexToTest: 1, }, @@ -635,180 +619,10 @@ func TestValidateListeners(t *testing.T) { {Protocol: gwv1beta1.TCPProtocolType, Port: 80}, {Protocol: gwv1beta1.TCPProtocolType, Port: 2080}, }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), expectedAcceptedErr: errListenerMappedToPrivilegedPortMapping, - resources: resourceMapResources{}, listenerIndexToTest: 1, mapPrivilegedContainerPorts: 2000, }, - "valid JWT provider in override of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: nil, - }, - "valid JWT provider in default of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Override: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: nil, - }, - "invalid JWT provider in override of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: errListenerJWTProviderNotFound, - }, - "invalid JWT provider in default of policy": { - listeners: []gwv1beta1.Listener{ - {Name: "l1", Protocol: gwv1beta1.HTTPProtocolType}, - }, - gateway: gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), - resources: resourceMapResources{ - jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - }, - }, - gatewayPolicies: []*v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: common.KindGateway, - Name: "gateway", - Namespace: "default", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Override: &v1alpha1.GatewayPolicyConfig{}, - }, - }, - }, - }, - expectedAcceptedErr: errListenerJWTProviderNotFound, - }, } { t.Run(name, func(t *testing.T) { gwcc := &v1alpha1.GatewayClassConfig{ @@ -817,7 +631,7 @@ func TestValidateListeners(t *testing.T) { }, } - require.Equal(t, tt.expectedAcceptedErr, validateListeners(tt.gateway, tt.listeners, newTestResourceMap(t, tt.resources), gwcc)[tt.listenerIndexToTest].acceptedErr) + require.Equal(t, tt.expectedAcceptedErr, validateListeners(gatewayWithFinalizer(gwv1beta1.GatewaySpec{}), tt.listeners, nil, gwcc)[tt.listenerIndexToTest].acceptedErr) }) } } @@ -1046,528 +860,3 @@ func TestRouteKindIsAllowedForListener(t *testing.T) { }) } } - -func TestValidateGatewayPolicies(t *testing.T) { - for name, tc := range map[string]struct { - gateway gwv1beta1.Gateway - policies []v1alpha1.GatewayPolicy - resources *common.ResourceMap - expected gatewayPolicyValidationResults - }{ - "happy path, everything exists": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "local", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "local", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: nil, - resolvedRefsErrs: []error{}, - }, - }, - }, - "a policy references a gateway that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "auth0", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist")}, - }, - }, - }, - "a policy references a JWT provider in the override section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references a JWT provider in the default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references the same JWT provider in the both override and default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "okta")}, - }, - }, - }, - "a policy references different JWT providers in the both override and default section that does not exist": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("l1")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta")}, - }, - }, - }, - "everything is wrong: listener does not exist and override and default both reference different missing jwt providers": { - gateway: gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - policies: []v1alpha1.GatewayPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Name: "gw", - SectionName: common.PointerTo(gwv1beta1.SectionName("does not exist")), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "local", - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth0", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "auth0", - }, - }, - }}), - expected: gatewayPolicyValidationResults{ - { - acceptedErr: errNotAcceptedDueToInvalidRefs, - resolvedRefsErrs: []error{ - fmt.Errorf("%w: gatewayName - %q, listenerName - %q", errPolicyListenerReferenceDoesNotExist, "gw", "does not exist"), - fmt.Errorf("%w: missingProviderNames: %s", errPolicyJWTProvidersReferenceDoesNotExist, "local,okta"), - }, - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.EqualValues(t, tc.expected, validateGatewayPolicies(tc.gateway, tc.policies, tc.resources)) - }) - } -} - -func TestValidateAuthFilters(t *testing.T) { - for name, tc := range map[string]struct { - authFilters []*v1alpha1.RouteAuthFilter - resources *common.ResourceMap - expected authFilterValidationResults - }{ - "auth filter valid": { - authFilters: []*v1alpha1.RouteAuthFilter{ - { - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "okta", - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: authFilterValidationResults{authFilterValidationResult{}}, - }, - "auth filter references missing JWT Provider": { - authFilters: []*v1alpha1.RouteAuthFilter{ - { - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "auth0", - }, - }, - }, - }, - }, - }, - resources: newTestResourceMap(t, resourceMapResources{jwtProviders: []*v1alpha1.JWTProvider{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "okta", - }, - Spec: v1alpha1.JWTProviderSpec{ - Issuer: "okta", - }, - }, - }}), - expected: authFilterValidationResults{ - authFilterValidationResult{ - acceptedErr: errRouteFilterNotAcceptedDueToInvalidRefs, - resolvedRefErr: fmt.Errorf("%w: missingProviderNames: %s", errRouteFilterJWTProvidersReferenceDoesNotExist, "auth0"), - }, - }, - }, - } { - t.Run(name, func(t *testing.T) { - require.Equal(t, tc.expected, validateAuthFilters(tc.authFilters, tc.resources)) - }) - } -} diff --git a/control-plane/api-gateway/cache/consul.go b/control-plane/api-gateway/cache/consul.go index 0e08d3bdc8..f34538103d 100644 --- a/control-plane/api-gateway/cache/consul.go +++ b/control-plane/api-gateway/cache/consul.go @@ -55,7 +55,7 @@ const ( apiTimeout = 5 * time.Minute ) -var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate, api.JWTProvider} +var Kinds = []string{api.APIGateway, api.HTTPRoute, api.TCPRoute, api.InlineCertificate} type Config struct { ConsulClientConfig *consul.Config @@ -94,7 +94,6 @@ func New(config Config) *Cache { for _, kind := range Kinds { cache[kind] = common.NewReferenceMap() } - config.ConsulClientConfig.APITimeout = apiTimeout return &Cache{ @@ -225,18 +224,16 @@ func (c *Cache) updateAndNotify(ctx context.Context, once *sync.Once, kind strin for _, entry := range entries { meta := entry.GetMeta() - if kind != api.JWTProvider { - if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { - // Don't process things that don't belong to us. The main reason - // for this is so that we don't garbage collect config entries that - // are either user-created or that another controller running in a - // federated datacenter creates. While we still allow for competing controllers - // syncing/overriding each other due to conflicting Kubernetes objects in - // two federated clusters (which is what the rest of the controllers also allow - // for), we don't want to delete a config entry just because we don't have - // its corresponding Kubernetes object if we know it belongs to another datacenter. - continue - } + if meta[constants.MetaKeyKubeName] == "" || meta[constants.MetaKeyDatacenter] != c.datacenter { + // Don't process things that don't belong to us. The main reason + // for this is so that we don't garbage collect config entries that + // are either user-created or that another controller running in a + // federated datacenter creates. While we still allow for competing controllers + // syncing/overriding each other due to conflicting Kubernetes objects in + // two federated clusters (which is what the rest of the controllers also allow + // for), we don't want to delete a config entry just because we don't have + // its corresponding Kubernetes object if we know it belongs to another datacenter. + continue } cache.Set(common.EntryToReference(entry), entry) diff --git a/control-plane/api-gateway/cache/consul_test.go b/control-plane/api-gateway/cache/consul_test.go index d206c0a8a8..59570e532f 100644 --- a/control-plane/api-gateway/cache/consul_test.go +++ b/control-plane/api-gateway/cache/consul_test.go @@ -21,12 +21,11 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/event" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" ) func Test_resourceCache_diff(t *testing.T) { @@ -1323,7 +1322,7 @@ func TestCache_Write(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) @@ -1543,10 +1542,6 @@ func Test_Run(t *testing.T) { inlineCert := setupInlineCertificate() certs := []*api.InlineCertificateConfigEntry{inlineCert} - // setup jwt providers - jwtProvider := setupJWTProvider() - providers := []*api.JWTProviderConfigEntry{jwtProvider} - consulServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/config/http-route": @@ -1581,14 +1576,6 @@ func Test_Run(t *testing.T) { return } fmt.Fprintln(w, string(val)) - case "/v1/config/jwt-provider": - val, err := json.Marshal(providers) - if err != nil { - w.WriteHeader(500) - fmt.Fprintln(w, err) - return - } - fmt.Fprintln(w, string(val)) case "/v1/catalog/services": fmt.Fprintln(w, `{}`) case "/v1/peerings": @@ -1613,7 +1600,7 @@ func Test_Run(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) @@ -1627,7 +1614,7 @@ func Test_Run(t *testing.T) { } expectedCache := loadedReferenceMaps([]api.ConfigEntry{ - gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, jwtProvider, + gw, tcpRoute, httpRouteOne, httpRouteTwo, inlineCert, }) ctx, cancelFn := context.WithCancel(context.Background()) @@ -1687,16 +1674,6 @@ func Test_Run(t *testing.T) { } }) - jwtProviderNsn := types.NamespacedName{ - Name: jwtProvider.Name, - Namespace: jwtProvider.Namespace, - } - - jwtSubscriber := c.Subscribe(ctx, api.JWTProvider, func(cfe api.ConfigEntry) []types.NamespacedName { - return []types.NamespacedName{ - {Name: cfe.GetName(), Namespace: cfe.GetNamespace()}, - } - }) // mark this subscription as ended canceledSub.Cancel() @@ -1707,10 +1684,9 @@ func Test_Run(t *testing.T) { gwExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(gwNsn)} tcpExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(tcpRouteNsn)} certExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(certNsn)} - jwtProviderExpectedEvent := event.GenericEvent{Object: newConfigEntryObject(jwtProviderNsn)} - // 2 http routes + 1 gw + 1 tcp route + 1 cert + 1 jwtProvider = 6 - i := 6 + // 2 http routes + 1 gw + 1 tcp route + 1 cert = 5 + i := 5 for { if i == 0 { break @@ -1724,8 +1700,6 @@ func Test_Run(t *testing.T) { require.Equal(t, tcpExpectedEvent, actualTCPRouteEvent) case actualCertExpectedEvent := <-certSubscriber.Events(): require.Equal(t, certExpectedEvent, actualCertExpectedEvent) - case actualJWTExpectedEvent := <-jwtSubscriber.Events(): - require.Equal(t, jwtProviderExpectedEvent, actualJWTExpectedEvent) } i -= 1 } @@ -1981,13 +1955,6 @@ func setupInlineCertificate() *api.InlineCertificateConfigEntry { } } -func setupJWTProvider() *api.JWTProviderConfigEntry { - return &api.JWTProviderConfigEntry{ - Kind: api.JWTProvider, - Name: "okta", - } -} - func TestCache_Delete(t *testing.T) { t.Parallel() testCases := []struct { @@ -2034,7 +2001,7 @@ func TestCache_Delete(t *testing.T) { GRPCPort: port, APITimeout: 0, }, - ConsulServerConnMgr: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + ConsulServerConnMgr: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), NamespacesEnabled: false, Logger: logrtest.NewTestLogger(t), }) diff --git a/control-plane/api-gateway/common/diff.go b/control-plane/api-gateway/common/diff.go index df4f2cb4f0..b58bf23901 100644 --- a/control-plane/api-gateway/common/diff.go +++ b/control-plane/api-gateway/common/diff.go @@ -11,8 +11,6 @@ import ( "golang.org/x/exp/slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { @@ -21,14 +19,6 @@ func GatewayStatusesEqual(a, b gwv1beta1.GatewayStatus) bool { slices.EqualFunc(a.Listeners, b.Listeners, gatewayStatusesListenersEqual) } -func GatewayPolicyStatusesEqual(a, b v1alpha1.GatewayPolicyStatus) bool { - return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) -} - -func RouteAuthFilterStatusesEqual(a, b v1alpha1.RouteAuthFilterStatus) bool { - return slices.EqualFunc(a.Conditions, b.Conditions, conditionsEqual) -} - func gatewayStatusesAddressesEqual(a, b gwv1beta1.GatewayAddress) bool { return BothNilOrEqual(a.Type, b.Type) && a.Value == b.Value @@ -113,75 +103,7 @@ func (e entryComparator) apiGatewayListenersEqual(a, b api.APIGatewayListener) b a.Port == b.Port && // normalize the protocol name strings.EqualFold(a.Protocol, b.Protocol) && - e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) && - e.apiGatewayPoliciesEqual(a.Override, b.Override) && - e.apiGatewayPoliciesEqual(a.Default, b.Default) -} - -func (e entryComparator) apiGatewayPoliciesEqual(a, b *api.APIGatewayPolicy) bool { - // if both are nil then return true - if a == nil && b == nil { - return true - } - - // if only one is nil then return false - if a == nil || b == nil { - return false - } - - return e.equalJWTProviders(a.JWT, b.JWT) -} - -func (e entryComparator) equalJWTProviders(a, b *api.APIGatewayJWTRequirement) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - return slices.EqualFunc(a.Providers, b.Providers, providersEqual) -} - -func providersEqual(a, b *api.APIGatewayJWTProvider) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if a.Name != b.Name { - return false - } - - return slices.EqualFunc(a.VerifyClaims, b.VerifyClaims, equalClaims) -} - -func equalClaims(a, b *api.APIGatewayJWTClaimVerification) bool { - if a == nil && b == nil { - return true - } - - if a == nil || b == nil { - return false - } - - if a.Value != b.Value { - return false - } - - if len(a.Path) != len(b.Path) { - return false - } - - if !slices.Equal(a.Path, b.Path) { - return false - } - - return true + e.apiGatewayListenerTLSConfigurationsEqual(a.TLS, b.TLS) } func (e entryComparator) apiGatewayListenerTLSConfigurationsEqual(a, b api.APIGatewayTLSConfiguration) bool { @@ -226,12 +148,8 @@ func (e entryComparator) httpRoutesEqual(a, b api.HTTPRouteConfigEntry) bool { func (e entryComparator) httpRouteRulesEqual(a, b api.HTTPRouteRule) bool { return slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && - slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) && slices.EqualFunc(a.Matches, b.Matches, e.httpMatchesEqual) && - slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) && - bothNilOrEqualFunc(a.Filters.RetryFilter, b.Filters.RetryFilter, e.retryFiltersEqual) && - bothNilOrEqualFunc(a.Filters.TimeoutFilter, b.Filters.TimeoutFilter, e.timeoutFiltersEqual) && - bothNilOrEqualFunc(a.Filters.JWT, b.Filters.JWT, e.jwtFiltersEqual) + slices.EqualFunc(a.Services, b.Services, e.httpServicesEqual) } func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { @@ -240,8 +158,7 @@ func (e entryComparator) httpServicesEqual(a, b api.HTTPService) bool { orDefault(a.Namespace, e.namespaceA) == orDefault(b.Namespace, e.namespaceB) && orDefault(a.Partition, e.partitionA) == orDefault(b.Partition, e.partitionB) && slices.EqualFunc(a.Filters.Headers, b.Filters.Headers, e.httpHeaderFiltersEqual) && - bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) && - slices.EqualFunc(a.ResponseFilters.Headers, b.ResponseFilters.Headers, e.httpHeaderFiltersEqual) + bothNilOrEqualFunc(a.Filters.URLRewrite, b.Filters.URLRewrite, e.urlRewritesEqual) } func (e entryComparator) httpMatchesEqual(a, b api.HTTPMatch) bool { @@ -273,25 +190,6 @@ func (e entryComparator) urlRewritesEqual(a, b api.URLRewrite) bool { return a.Path == b.Path } -func (e entryComparator) retryFiltersEqual(a, b api.RetryFilter) bool { - return BothNilOrEqual(a.NumRetries, b.NumRetries) && BothNilOrEqual(a.RetryOnConnectFailure, b.RetryOnConnectFailure) && - slices.Equal(a.RetryOn, b.RetryOn) && slices.Equal(a.RetryOnStatusCodes, b.RetryOnStatusCodes) -} - -func (e entryComparator) timeoutFiltersEqual(a, b api.TimeoutFilter) bool { - return a.RequestTimeout == b.RequestTimeout && a.IdleTimeout == b.IdleTimeout -} - -// jwtFiltersEqual compares the contents of the list of providers on the JWT filters for a route, returning true if the -// filters have equal contents. -func (e entryComparator) jwtFiltersEqual(a, b api.JWTFilter) bool { - if len(a.Providers) != len(b.Providers) { - return false - } - - return slices.EqualFunc(a.Providers, b.Providers, providersEqual) -} - func tcpRoutesEqual(a, b *api.TCPRouteConfigEntry) bool { if a == nil || b == nil { return false diff --git a/control-plane/api-gateway/common/diff_test.go b/control-plane/api-gateway/common/diff_test.go deleted file mode 100644 index 04312c8162..0000000000 --- a/control-plane/api-gateway/common/diff_test.go +++ /dev/null @@ -1,2155 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/hashicorp/consul/api" - "github.com/stretchr/testify/require" -) - -func TestEntriesEqual(t *testing.T) { - testCases := map[string]struct { - a api.ConfigEntry - b api.ConfigEntry - expectedResult bool - }{ - "gateway equal": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: true, - }, - "gateway name different": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway-2", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway meta different": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey2": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l2", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different hostname": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host-different.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different port": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 123, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different protocol": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "https", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS max version": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "15", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS min version": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "0", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS cipher suites": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher", "another one"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different TLS certificate references": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert-2", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policies jwt provider name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "auth0", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policy jwt claims path": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"roles"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different override policy jwt claims value": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "user", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policies jwt provider name": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "auth0", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policy jwt claims path": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"roles"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - "gateway listeners different default policy jwt claims value": { - a: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "admin", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - b: &api.APIGatewayConfigEntry{ - Kind: api.APIGateway, - Name: "api-gateway", - Meta: map[string]string{ - "somekey": "somevalue", - }, - Listeners: []api.APIGatewayListener{ - { - Name: "l1", - Hostname: "host.com", - Port: 590, - Protocol: "http", - TLS: api.APIGatewayTLSConfiguration{ - Certificates: []api.ResourceReference{ - { - Kind: api.InlineCertificate, - Name: "cert", - SectionName: "section", - Partition: "partition", - Namespace: "ns", - }, - }, - MaxVersion: "5", - MinVersion: "2", - CipherSuites: []string{"cipher"}, - }, - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"role"}, - Value: "user", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "okta", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"aud"}, - Value: "consul.com", - }, - }, - }, - }, - }, - }, - }, - }, - Partition: "partition", - Namespace: "ns", - }, - expectedResult: false, - }, - } - - for name, tc := range testCases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - t.Parallel() - actual := EntriesEqual(tc.a, tc.b) - require.Equal(t, tc.expectedResult, actual) - }) - } -} diff --git a/control-plane/api-gateway/common/helpers.go b/control-plane/api-gateway/common/helpers.go index 7bc7eb61b6..b0eeb46510 100644 --- a/control-plane/api-gateway/common/helpers.go +++ b/control-plane/api-gateway/common/helpers.go @@ -4,7 +4,6 @@ package common import ( - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -28,24 +27,6 @@ func NilOrEqual[T ~string](v *T, check string) bool { return v == nil || string(*v) == check } -func FilterIsExternalFilter(filter gwv1beta1.HTTPRouteFilter) bool { - if filter.Type != gwv1beta1.HTTPRouteFilterExtensionRef { - return false - } - - if !DerefEqual(&filter.ExtensionRef.Group, v1alpha1.ConsulHashicorpGroup) { - return false - } - - switch filter.ExtensionRef.Kind { - case v1alpha1.RouteRetryFilterKind, v1alpha1.RouteTimeoutFilterKind, v1alpha1.RouteAuthFilterKind: - return true - } - - return false - -} - func IndexedNamespacedNameWithDefault[T ~string, U ~string, V ~string](t T, u *U, v V) types.NamespacedName { return types.NamespacedName{ Namespace: DerefStringOr(u, v), diff --git a/control-plane/api-gateway/common/resources.go b/control-plane/api-gateway/common/resources.go index 051c914ae7..d412c01eee 100644 --- a/control-plane/api-gateway/common/resources.go +++ b/control-plane/api-gateway/common/resources.go @@ -12,9 +12,8 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul/api" ) // ConsulUpdateOperation is an operation representing an @@ -116,13 +115,10 @@ type ResourceMap struct { tcpRouteGateways map[api.ResourceReference]*tcpRoute httpRouteGateways map[api.ResourceReference]*httpRoute gatewayResources map[api.ResourceReference]*resourceSet - externalFilters map[corev1.ObjectReference]client.Object - gatewayPolicies map[api.ResourceReference]*v1alpha1.GatewayPolicy // consul resources for a gateway consulTCPRoutes map[api.ResourceReference]*consulTCPRoute consulHTTPRoutes map[api.ResourceReference]*consulHTTPRoute - jwtProviders map[api.ResourceReference]*v1alpha1.JWTProvider // mutations consulMutations []*ConsulUpdateOperation @@ -143,8 +139,6 @@ func NewResourceMap(translator ResourceTranslator, validator ReferenceValidator, tcpRouteGateways: make(map[api.ResourceReference]*tcpRoute), httpRouteGateways: make(map[api.ResourceReference]*httpRoute), gatewayResources: make(map[api.ResourceReference]*resourceSet), - gatewayPolicies: make(map[api.ResourceReference]*v1alpha1.GatewayPolicy), - jwtProviders: make(map[api.ResourceReference]*v1alpha1.JWTProvider), } } @@ -370,110 +364,6 @@ func (s *ResourceMap) ReferenceCountHTTPRoute(route gwv1beta1.HTTPRoute) { s.httpRouteGateways[consulKey] = set } -func localObjectReferenceToObjectReference(filterRef gwv1beta1.LocalObjectReference, namespace string) corev1.ObjectReference { - return corev1.ObjectReference{ - Kind: string(filterRef.Kind), - Name: string(filterRef.Name), - Namespace: namespace, - } -} - -func objectToObjectReference(object client.Object) corev1.ObjectReference { - return corev1.ObjectReference{ - Kind: object.GetObjectKind().GroupVersionKind().Kind, - Name: object.GetName(), - Namespace: object.GetNamespace(), - } -} - -func (s *ResourceMap) AddExternalFilter(filter client.Object) { - if s.externalFilters == nil { - s.externalFilters = make(map[corev1.ObjectReference]client.Object) - } - - key := objectToObjectReference(filter) - s.externalFilters[key] = filter -} - -func (s *ResourceMap) GetExternalFilter(filterRef gwv1beta1.LocalObjectReference, namespace string) (client.Object, bool) { - key := localObjectReferenceToObjectReference(filterRef, namespace) - filter, ok := s.externalFilters[key] - return filter, ok -} - -func (s *ResourceMap) ExternalFilterExists(filterRef gwv1beta1.LocalObjectReference, namespace string) bool { - _, ok := s.GetExternalFilter(filterRef, namespace) - return ok -} - -func (s *ResourceMap) GetExternalAuthFilters() []*v1alpha1.RouteAuthFilter { - filters := make([]*v1alpha1.RouteAuthFilter, 0, len(s.externalFilters)) - for _, filter := range s.externalFilters { - if authFilter, ok := filter.(*v1alpha1.RouteAuthFilter); ok { - filters = append(filters, authFilter) - } - } - return filters -} - -func (s *ResourceMap) AddGatewayPolicy(gatewayPolicy *v1alpha1.GatewayPolicy) *v1alpha1.GatewayPolicy { - sectionName := "" - if gatewayPolicy.Spec.TargetRef.SectionName != nil { - sectionName = string(*gatewayPolicy.Spec.TargetRef.SectionName) - } - - gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace - if gwNamespace == "" { - gwNamespace = gatewayPolicy.Namespace - } - - key := api.ResourceReference{ - Kind: gatewayPolicy.Spec.TargetRef.Kind, - Name: gatewayPolicy.Spec.TargetRef.Name, - SectionName: sectionName, - Namespace: gwNamespace, - } - - if s.gatewayPolicies == nil { - s.gatewayPolicies = make(map[api.ResourceReference]*v1alpha1.GatewayPolicy) - } - - s.gatewayPolicies[key] = gatewayPolicy - - return s.gatewayPolicies[key] -} - -func (s *ResourceMap) AddJWTProvider(provider *v1alpha1.JWTProvider) { - key := api.ResourceReference{ - Kind: provider.Kind, - Name: provider.Name, - } - s.jwtProviders[key] = provider -} - -func (s *ResourceMap) GetJWTProviderForGatewayJWTProvider(provider *v1alpha1.GatewayJWTProvider) (*v1alpha1.JWTProvider, bool) { - key := api.ResourceReference{ - Name: provider.Name, - Kind: "JWTProvider", - } - - value, exists := s.jwtProviders[key] - return value, exists -} - -func (s *ResourceMap) GetPolicyForGatewayListener(gateway gwv1beta1.Gateway, gatewayListener gwv1beta1.Listener) (*v1alpha1.GatewayPolicy, bool) { - key := api.ResourceReference{ - Name: gateway.Name, - Kind: gateway.Kind, - SectionName: string(gatewayListener.Name), - Namespace: gateway.Namespace, - } - - value, exists := s.gatewayPolicies[key] - - return value, exists -} - func (s *ResourceMap) ReferenceCountTCPRoute(route gwv1alpha2.TCPRoute) { key := client.ObjectKeyFromObject(&route) consulKey := NormalizeMeta(s.toConsulReference(api.TCPRoute, key)) diff --git a/control-plane/api-gateway/common/resources_test.go b/control-plane/api-gateway/common/resources_test.go deleted file mode 100644 index 7f5619496f..0000000000 --- a/control-plane/api-gateway/common/resources_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" -) - -func TestResourceMap_JWTProvider(t *testing.T) { - resourceMap := NewResourceMap(ResourceTranslator{}, mockReferenceValidator{}, logrtest.New(t)) - require.Empty(t, resourceMap.jwtProviders) - provider := &v1alpha1.JWTProvider{ - TypeMeta: metav1.TypeMeta{ - Kind: "JWTProvider", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-jwt", - }, - Spec: v1alpha1.JWTProviderSpec{}, - } - - key := api.ResourceReference{ - Name: provider.Name, - Kind: "JWTProvider", - } - - resourceMap.AddJWTProvider(provider) - - require.Len(t, resourceMap.jwtProviders, 1) - require.NotNil(t, resourceMap.jwtProviders[key]) - require.Equal(t, resourceMap.jwtProviders[key], provider) -} - -type mockReferenceValidator struct{} - -func (m mockReferenceValidator) GatewayCanReferenceSecret(gateway gwv1beta1.Gateway, secretRef gwv1beta1.SecretObjectReference) bool { - return true -} - -func (m mockReferenceValidator) HTTPRouteCanReferenceBackend(httproute gwv1beta1.HTTPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} - -func (m mockReferenceValidator) TCPRouteCanReferenceBackend(tcpRoute gwv1alpha2.TCPRoute, backendRef gwv1beta1.BackendRef) bool { - return true -} diff --git a/control-plane/api-gateway/common/translation.go b/control-plane/api-gateway/common/translation.go index ed949b1ade..9303540e82 100644 --- a/control-plane/api-gateway/common/translation.go +++ b/control-plane/api-gateway/common/translation.go @@ -11,11 +11,10 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul/api" ) // ResourceTranslator handles translating K8s resources into Consul config entries. @@ -114,10 +113,6 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list } } - // Grab policy if it exists. - gatewayPolicyCrd, _ := resources.GetPolicyForGatewayListener(gateway, listener) - defaultPolicy, overridePolicy := t.translateGatewayPolicy(gatewayPolicyCrd) - portMapping := int32(0) if gwcc != nil { portMapping = gwcc.Spec.MapPrivilegedContainerPorts @@ -134,8 +129,6 @@ func (t ResourceTranslator) toAPIGatewayListener(gateway gwv1beta1.Gateway, list MaxVersion: maxVersion, MinVersion: minVersion, }, - Default: defaultPolicy, - Override: overridePolicy, }, true } @@ -148,98 +141,17 @@ func ToContainerPort(portNumber gwv1beta1.PortNumber, mapPrivilegedContainerPort return int(portNumber) + int(mapPrivilegedContainerPorts) } -func (t ResourceTranslator) translateRouteRetryFilter(routeRetryFilter *v1alpha1.RouteRetryFilter) *api.RetryFilter { - return &api.RetryFilter{ - NumRetries: routeRetryFilter.Spec.NumRetries, - RetryOn: routeRetryFilter.Spec.RetryOn, - RetryOnStatusCodes: routeRetryFilter.Spec.RetryOnStatusCodes, - RetryOnConnectFailure: routeRetryFilter.Spec.RetryOnConnectFailure, - } -} - -func (t ResourceTranslator) translateRouteTimeoutFilter(routeTimeoutFilter *v1alpha1.RouteTimeoutFilter) *api.TimeoutFilter { - return &api.TimeoutFilter{ - RequestTimeout: routeTimeoutFilter.Spec.RequestTimeout.Duration, - IdleTimeout: routeTimeoutFilter.Spec.IdleTimeout.Duration, - } -} - -func (t ResourceTranslator) translateRouteJWTFilter(routeJWTFilter *v1alpha1.RouteAuthFilter) *api.JWTFilter { - if routeJWTFilter.Spec.JWT == nil { - return nil - } - - return &api.JWTFilter{ - Providers: ConvertSliceFunc(routeJWTFilter.Spec.JWT.Providers, t.translateJWTProvider), - } -} - -func (t ResourceTranslator) translateGatewayPolicy(policy *v1alpha1.GatewayPolicy) (*api.APIGatewayPolicy, *api.APIGatewayPolicy) { - if policy == nil { - return nil, nil - } - - var defaultPolicy, overridePolicy *api.APIGatewayPolicy - - if policy.Spec.Default != nil { - defaultPolicy = &api.APIGatewayPolicy{ - JWT: t.translateJWTRequirement(policy.Spec.Default.JWT), - } - } - - if policy.Spec.Override != nil { - overridePolicy = &api.APIGatewayPolicy{ - JWT: t.translateJWTRequirement(policy.Spec.Override.JWT), - } - } - return defaultPolicy, overridePolicy -} - -func (t ResourceTranslator) translateJWTRequirement(crdRequirement *v1alpha1.GatewayJWTRequirement) *api.APIGatewayJWTRequirement { - apiRequirement := api.APIGatewayJWTRequirement{} - providers := ConvertSliceFunc(crdRequirement.Providers, t.translateJWTProvider) - apiRequirement.Providers = providers - return &apiRequirement -} - -func (t ResourceTranslator) translateJWTProvider(crdProvider *v1alpha1.GatewayJWTProvider) *api.APIGatewayJWTProvider { - if crdProvider == nil { - return nil - } - - apiProvider := api.APIGatewayJWTProvider{ - Name: crdProvider.Name, - } - claims := ConvertSliceFunc(crdProvider.VerifyClaims, t.translateVerifyClaims) - apiProvider.VerifyClaims = claims - - return &apiProvider -} - -func (t ResourceTranslator) translateVerifyClaims(crdClaims *v1alpha1.GatewayJWTClaimVerification) *api.APIGatewayJWTClaimVerification { - if crdClaims == nil { - return nil - } - verifyClaim := api.APIGatewayJWTClaimVerification{ - Path: crdClaims.Path, - Value: crdClaims.Value, - } - return &verifyClaim -} - func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *ResourceMap) *api.HTTPRouteConfigEntry { namespace := t.Namespace(route.Namespace) - // We don't translate parent refs. + // we don't translate parent refs hostnames := StringLikeSlice(route.Spec.Hostnames) - rules := ConvertSliceFuncIf( - route.Spec.Rules, - func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { - return t.translateHTTPRouteRule(route, rule, resources) - }) + rules := ConvertSliceFuncIf(route.Spec.Rules, func(rule gwv1beta1.HTTPRouteRule) (api.HTTPRouteRule, bool) { + return t.translateHTTPRouteRule(route, rule, resources) + }) - configEntry := api.HTTPRouteConfigEntry{ + return &api.HTTPRouteConfigEntry{ Kind: api.HTTPRoute, Name: route.Name, Namespace: namespace, @@ -251,29 +163,24 @@ func (t ResourceTranslator) ToHTTPRoute(route gwv1beta1.HTTPRoute, resources *Re Hostnames: hostnames, Rules: rules, } - - return &configEntry } func (t ResourceTranslator) translateHTTPRouteRule(route gwv1beta1.HTTPRoute, rule gwv1beta1.HTTPRouteRule, resources *ResourceMap) (api.HTTPRouteRule, bool) { - services := ConvertSliceFuncIf( - rule.BackendRefs, - func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { - return t.translateHTTPBackendRef(route, ref, resources) - }) + services := ConvertSliceFuncIf(rule.BackendRefs, func(ref gwv1beta1.HTTPBackendRef) (api.HTTPService, bool) { + return t.translateHTTPBackendRef(route, ref, resources) + }) if len(services) == 0 { return api.HTTPRouteRule{}, false } matches := ConvertSliceFunc(rule.Matches, t.translateHTTPMatch) - filters, responseFilters := t.translateHTTPFilters(rule.Filters, resources, route.Namespace) + filters := t.translateHTTPFilters(rule.Filters) return api.HTTPRouteRule{ - Filters: filters, - Matches: matches, - ResponseFilters: responseFilters, - Services: services, + Services: services, + Matches: matches, + Filters: filters, }, true } @@ -286,30 +193,29 @@ func (t ResourceTranslator) translateHTTPBackendRef(route gwv1beta1.HTTPRoute, r isServiceRef := NilOrEqual(ref.Group, "") && NilOrEqual(ref.Kind, "Service") if isServiceRef && resources.HasService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) + filters := t.translateHTTPFilters(ref.Filters) service := resources.Service(id) + return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - ResponseFilters: responseFilters, - Weight: DerefIntOr(ref.Weight, 1), + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + Weight: DerefIntOr(ref.Weight, 1), }, true } isMeshServiceRef := DerefEqual(ref.Group, v1alpha1.ConsulHashicorpGroup) && DerefEqual(ref.Kind, v1alpha1.MeshServiceKind) if isMeshServiceRef && resources.HasMeshService(id) && resources.HTTPRouteCanReferenceBackend(route, ref.BackendRef) { - filters, responseFilters := t.translateHTTPFilters(ref.Filters, resources, route.Namespace) + filters := t.translateHTTPFilters(ref.Filters) service := resources.MeshService(id) return api.HTTPService{ - Name: service.Name, - Namespace: service.Namespace, - Partition: t.ConsulPartition, - Filters: filters, - ResponseFilters: responseFilters, - Weight: DerefIntOr(ref.Weight, 1), + Name: service.Name, + Namespace: service.Namespace, + Partition: t.ConsulPartition, + Filters: filters, + Weight: DerefIntOr(ref.Weight, 1), }, true } @@ -367,62 +273,24 @@ func (t ResourceTranslator) translateHTTPQueryMatch(match gwv1beta1.HTTPQueryPar } } -func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter, resourceMap *ResourceMap, namespace string) (api.HTTPFilters, api.HTTPResponseFilters) { - var ( - urlRewrite *api.URLRewrite - retryFilter *api.RetryFilter - timeoutFilter *api.TimeoutFilter - requestHeaderFilters = []api.HTTPHeaderFilter{} - responseHeaderFilters = []api.HTTPHeaderFilter{} - jwtFilter *api.JWTFilter - ) - - // Convert Gateway API filters to portions of the Consul request and response filters. - // Multiple filters applying the same or conflicting operations are allowed but may - // result in unexpected behavior. +func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFilter) api.HTTPFilters { + var urlRewrite *api.URLRewrite + consulFilter := api.HTTPHeaderFilter{ + Add: make(map[string]string), + Set: make(map[string]string), + } + for _, filter := range filters { if filter.RequestHeaderModifier != nil { - newFilter := api.HTTPHeaderFilter{} - - newFilter.Remove = append(newFilter.Remove, filter.RequestHeaderModifier.Remove...) - - if len(filter.RequestHeaderModifier.Add) > 0 { - newFilter.Add = map[string]string{} - for _, toAdd := range filter.RequestHeaderModifier.Add { - newFilter.Add[string(toAdd.Name)] = toAdd.Value - } - } + consulFilter.Remove = append(consulFilter.Remove, filter.RequestHeaderModifier.Remove...) - if len(filter.RequestHeaderModifier.Set) > 0 { - newFilter.Set = map[string]string{} - for _, toSet := range filter.RequestHeaderModifier.Set { - newFilter.Set[string(toSet.Name)] = toSet.Value - } + for _, toAdd := range filter.RequestHeaderModifier.Add { + consulFilter.Add[string(toAdd.Name)] = toAdd.Value } - requestHeaderFilters = append(requestHeaderFilters, newFilter) - } - - if filter.ResponseHeaderModifier != nil { - newFilter := api.HTTPHeaderFilter{} - - newFilter.Remove = append(newFilter.Remove, filter.ResponseHeaderModifier.Remove...) - - if len(filter.ResponseHeaderModifier.Add) > 0 { - newFilter.Add = map[string]string{} - for _, toAdd := range filter.ResponseHeaderModifier.Add { - newFilter.Add[string(toAdd.Name)] = toAdd.Value - } + for _, toSet := range filter.RequestHeaderModifier.Set { + consulFilter.Set[string(toSet.Name)] = toSet.Value } - - if len(filter.ResponseHeaderModifier.Set) > 0 { - newFilter.Set = map[string]string{} - for _, toSet := range filter.ResponseHeaderModifier.Set { - newFilter.Set[string(toSet.Name)] = toSet.Value - } - } - - responseHeaderFilters = append(responseHeaderFilters, newFilter) } // we drop any path rewrites that are not prefix matches as we don't support those @@ -431,39 +299,11 @@ func (t ResourceTranslator) translateHTTPFilters(filters []gwv1beta1.HTTPRouteFi filter.URLRewrite.Path.Type == gwv1beta1.PrefixMatchHTTPPathModifier { urlRewrite = &api.URLRewrite{Path: DerefStringOr(filter.URLRewrite.Path.ReplacePrefixMatch, "")} } - - if filter.ExtensionRef != nil { - // get crd from resources map - crdFilter, exists := resourceMap.GetExternalFilter(*filter.ExtensionRef, namespace) - if !exists { - // this should never be the case because we only translate a route if it's actually valid, and if we're missing filters during the validation step, then we won't get here - continue - } - - switch filter.ExtensionRef.Kind { - case v1alpha1.RouteRetryFilterKind: - retryFilter = t.translateRouteRetryFilter(crdFilter.(*v1alpha1.RouteRetryFilter)) - case v1alpha1.RouteTimeoutFilterKind: - timeoutFilter = t.translateRouteTimeoutFilter(crdFilter.(*v1alpha1.RouteTimeoutFilter)) - case v1alpha1.RouteAuthFilterKind: - jwtFilter = t.translateRouteJWTFilter(crdFilter.(*v1alpha1.RouteAuthFilter)) - } - } } - - requestFilter := api.HTTPFilters{ - Headers: requestHeaderFilters, - URLRewrite: urlRewrite, - RetryFilter: retryFilter, - TimeoutFilter: timeoutFilter, - JWT: jwtFilter, - } - - responseFilter := api.HTTPResponseFilters{ - Headers: responseHeaderFilters, + return api.HTTPFilters{ + Headers: []api.HTTPHeaderFilter{consulFilter}, + URLRewrite: urlRewrite, } - - return requestFilter, responseFilter } func (t ResourceTranslator) ToTCPRoute(route gwv1alpha2.TCPRoute, resources *ResourceMap) *api.TCPRouteConfigEntry { diff --git a/control-plane/api-gateway/common/translation_test.go b/control-plane/api-gateway/common/translation_test.go index 8f02f6eca1..e8caad76ed 100644 --- a/control-plane/api-gateway/common/translation_test.go +++ b/control-plane/api-gateway/common/translation_test.go @@ -15,9 +15,6 @@ import ( "testing" "time" - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,10 +26,9 @@ import ( logrtest "github.com/go-logr/logr/testing" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul/api" ) type fakeReferenceValidator struct{} @@ -358,12 +354,10 @@ func TestTranslator_ToAPIGateway(t *testing.T) { func TestTranslator_ToHTTPRoute(t *testing.T) { t.Parallel() type args struct { - k8sHTTPRoute gwv1beta1.HTTPRoute - services []types.NamespacedName - meshServices []v1alpha1.MeshService - externalFilters []client.Object + k8sHTTPRoute gwv1beta1.HTTPRoute + services []types.NamespacedName + meshServices []v1alpha1.MeshService } - tests := map[string]struct { args args want api.HTTPRouteConfigEntry @@ -513,7 +507,6 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, - ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -539,8 +532,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Namespace: "other", + Name: "service one", + Weight: 45, Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -558,8 +551,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - ResponseFilters: api.HTTPResponseFilters{Headers: []api.HTTPHeaderFilter{}}, - Weight: 45, + Namespace: "other", }, }, }, @@ -719,9 +711,6 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, }, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -747,8 +736,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Namespace: "some ns", + Name: "service one", + Weight: 45, Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -766,10 +755,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, + Namespace: "some ns", }, }, }, @@ -937,9 +923,6 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -965,8 +948,8 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, Services: []api.HTTPService{ { - Name: "service one", - Namespace: "some ns", + Name: "service one", + Weight: 45, Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -984,10 +967,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, + Namespace: "some ns", }, }, }, @@ -1169,9 +1149,6 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, URLRewrite: &api.URLRewrite{Path: "v1"}, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, Matches: []api.HTTPMatch{ { Headers: []api.HTTPHeaderMatch{ @@ -1196,18 +1173,10 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { }, }, Services: []api.HTTPService{ + {Name: "some-override", Namespace: "svc-ns", Weight: 1, Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{{Add: make(map[string]string), Set: make(map[string]string)}}}}, { - Name: "some-override", - Namespace: "svc-ns", - Weight: 1, - Filters: api.HTTPFilters{Headers: []api.HTTPHeaderFilter{}}, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - }, - { - Name: "service one", - Namespace: "some ns", + Name: "service one", + Weight: 45, Filters: api.HTTPFilters{ Headers: []api.HTTPHeaderFilter{ { @@ -1225,275 +1194,7 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { Path: "path", }, }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Weight: 45, - }, - }, - }, - }, - Hostnames: []string{ - "host-name.example.com", - "consul.io", - }, - Meta: map[string]string{ - constants.MetaKeyKubeNS: "k8s-ns", - constants.MetaKeyKubeName: "k8s-http-route", - }, - Namespace: "k8s-ns", - }, - }, - "test with external filters": { - args: args{ - k8sHTTPRoute: gwv1beta1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "k8s-http-route", - Namespace: "k8s-ns", - Annotations: map[string]string{}, - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Namespace: PointerTo(gwv1beta1.Namespace("k8s-gw-ns")), - Name: gwv1beta1.ObjectName("api-gw"), - Kind: PointerTo(gwv1beta1.Kind("Gateway")), - SectionName: PointerTo(gwv1beta1.SectionName("listener-1")), - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{ - "host-name.example.com", - "consul.io", - }, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: PointerTo(gwv1beta1.PathMatchPathPrefix), - Value: PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: PointerTo(gwv1beta1.HeaderMatchExact), - Name: "my header match", - Value: "the value", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "term", - }, - }, - Method: PointerTo(gwv1beta1.HTTPMethodGet), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test", - Kind: v1alpha1.RouteRetryFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test-timeout-filter", - Kind: v1alpha1.RouteTimeoutFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test-jwt-filter", - Kind: v1alpha1.RouteAuthFilterKind, - Group: gwv1beta1.Group(v1alpha1.GroupVersion.Group), - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "service one", - Namespace: PointerTo(gwv1beta1.Namespace("other")), - }, - Weight: PointerTo(int32(45)), - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Name: "test", - Kind: v1alpha1.RouteRetryFilterKind, - Group: "consul.hashicorp.com/v1alpha1", - }, - }, - }, - }, - }, - }, - }, - }, - }, - services: []types.NamespacedName{ - {Name: "service one", Namespace: "other"}, - }, - externalFilters: []client.Object{ - &v1alpha1.RouteRetryFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteRetryFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteRetryFilterSpec{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), - }, - }, - - &v1alpha1.RouteRetryFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteRetryFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "other-namespace-even-though-same-name", - }, - Spec: v1alpha1.RouteRetryFilterSpec{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"don't"}, - RetryOnStatusCodes: []uint32{404}, - RetryOnConnectFailure: pointer.Bool(true), - }, - }, - - &v1alpha1.RouteTimeoutFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteTimeoutFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-timeout-filter", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteTimeoutFilterSpec{ - RequestTimeout: metav1.Duration{Duration: 10}, - IdleTimeout: metav1.Duration{Duration: 30}, - }, - }, - - &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - APIVersion: "consul.hashicorp.com/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-jwt-filter", - Namespace: "k8s-ns", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "test-jwt-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"/okta"}, - Value: "okta", - }, - }, - }, - }, - }, - }, - }, - }, - }, - want: api.HTTPRouteConfigEntry{ - Kind: api.HTTPRoute, - Name: "k8s-http-route", - Rules: []api.HTTPRouteRule{ - { - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, - URLRewrite: nil, - RetryFilter: &api.RetryFilter{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), - }, - TimeoutFilter: &api.TimeoutFilter{ - RequestTimeout: time.Duration(10 * time.Nanosecond), - IdleTimeout: time.Duration(30 * time.Nanosecond), - }, - JWT: &api.JWTFilter{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "test-jwt-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"/okta"}, - Value: "okta", - }, - }, - }, - }, - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Matches: []api.HTTPMatch{ - { - Headers: []api.HTTPHeaderMatch{ - { - Match: api.HTTPHeaderMatchExact, - Name: "my header match", - Value: "the value", - }, - }, - Method: api.HTTPMatchMethodGet, - Path: api.HTTPPathMatch{ - Match: api.HTTPPathMatchPrefix, - Value: "/v1", - }, - Query: []api.HTTPQueryMatch{ - { - Match: api.HTTPQueryMatchExact, - Name: "search", - Value: "term", - }, - }, - }, - }, - Services: []api.HTTPService{ - { - Name: "service one", - Weight: 45, - Filters: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, - RetryFilter: &api.RetryFilter{ - NumRetries: pointer.Uint32(3), - RetryOn: []string{"cancelled"}, - RetryOnStatusCodes: []uint32{500, 502}, - RetryOnConnectFailure: pointer.Bool(false), - }, - }, - ResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, - Namespace: "other", + Namespace: "some ns", }, }, }, @@ -1525,10 +1226,6 @@ func TestTranslator_ToHTTPRoute(t *testing.T) { resources.AddMeshService(service) } - for _, filterToAdd := range tc.args.externalFilters { - resources.AddExternalFilter(filterToAdd) - } - got := tr.ToHTTPRoute(tc.args.k8sHTTPRoute, resources) if diff := cmp.Diff(&tc.want, got); diff != "" { t.Errorf("Translator.ToHTTPRoute() mismatch (-want +got):\n%s", diff) @@ -1711,11 +1408,10 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { filters []gwv1beta1.HTTPRouteFilter } tests := []struct { - name string - fields fields - args args - want api.HTTPFilters - wantResponseFilters api.HTTPResponseFilters + name string + fields fields + args args + want api.HTTPFilters }{ { name: "no httproutemodifier set", @@ -1728,12 +1424,14 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { }, }, want: api.HTTPFilters{ - Headers: []api.HTTPHeaderFilter{}, + Headers: []api.HTTPHeaderFilter{ + { + Add: map[string]string{}, + Set: map[string]string{}, + }, + }, URLRewrite: nil, }, - wantResponseFilters: api.HTTPResponseFilters{ - Headers: []api.HTTPHeaderFilter{}, - }, }, } for _, tt := range tests { @@ -1746,155 +1444,7 @@ func TestResourceTranslator_translateHTTPFilters(t1 *testing.T) { ConsulPartition: tt.fields.ConsulPartition, Datacenter: tt.fields.Datacenter, } - requestHeaders, responseHeaders := t.translateHTTPFilters(tt.args.filters, nil, "") - assert.Equalf(t1, tt.want, requestHeaders, "translateHTTPFilters(%v)", tt.args.filters) - assert.Equalf(t1, tt.wantResponseFilters, responseHeaders, "translateHTTPFilters(%v)", tt.args.filters) - }) - } -} - -func newSectionNamePtr(s string) *gwv1beta1.SectionName { - sectionName := gwv1beta1.SectionName(s) - return §ionName -} - -func TestResourceTranslator_toAPIGatewayListener(t *testing.T) { - type args struct { - gateway gwv1beta1.Gateway - listener gwv1beta1.Listener - gwcc *v1alpha1.GatewayClassConfig - } - tests := []struct { - name string - args args - policies []v1alpha1.GatewayPolicy - want api.APIGatewayListener - want1 bool - }{ - { - name: "listener with jwt auth", - policies: []v1alpha1.GatewayPolicy{ - { - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Kind: KindGateway, - Name: "test", - Namespace: "test", - SectionName: newSectionNamePtr("test-listener"), - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "override-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: "default-provider", - VerifyClaims: []*v1alpha1.GatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }}, - }, - }, - }, - args: args{ - gateway: gwv1beta1.Gateway{ - TypeMeta: metav1.TypeMeta{ - Kind: KindGateway, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "test", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "test-listener", - Port: 80, - Protocol: "HTTP", - }, - }, - }, - }, - listener: gwv1beta1.Listener{ - Name: "test-listener", - Port: 80, - Protocol: "HTTP", - }, - gwcc: &v1alpha1.GatewayClassConfig{ - Spec: v1alpha1.GatewayClassConfigSpec{}, - }, - }, - want: api.APIGatewayListener{ - Name: "test-listener", - Port: 80, - Protocol: "http", - Override: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "override-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - Default: &api.APIGatewayPolicy{ - JWT: &api.APIGatewayJWTRequirement{ - Providers: []*api.APIGatewayJWTProvider{ - { - Name: "default-provider", - VerifyClaims: []*api.APIGatewayJWTClaimVerification{ - { - Path: []string{"path"}, - Value: "value", - }, - }, - }, - }, - }, - }, - }, - want1: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t1 *testing.T) { - translator := ResourceTranslator{ - EnableConsulNamespaces: true, - ConsulDestNamespace: "", - EnableK8sMirroring: true, - MirroringPrefix: "", - } - - resources := NewResourceMap(translator, fakeReferenceValidator{}, logrtest.NewTestLogger(t)) - for _, p := range tt.policies { - resources.AddGatewayPolicy(&p) - } - got, got1 := translator.toAPIGatewayListener(tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) - assert.Equalf(t, tt.want, got, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) - assert.Equalf(t, tt.want1, got1, "toAPIGatewayListener(%v, %v, %v, %v)", tt.args.gateway, tt.args.listener, resources, tt.args.gwcc) + assert.Equalf(t1, tt.want, t.translateHTTPFilters(tt.args.filters), "translateHTTPFilters(%v)", tt.args.filters) }) } } diff --git a/control-plane/api-gateway/controllers/gateway_controller.go b/control-plane/api-gateway/controllers/gateway_controller.go index 8447e69f64..66347adea4 100644 --- a/control-plane/api-gateway/controllers/gateway_controller.go +++ b/control-plane/api-gateway/controllers/gateway_controller.go @@ -5,13 +5,11 @@ package controllers import ( "context" - "fmt" "reflect" "strconv" "strings" mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" "github.com/go-logr/logr" @@ -31,14 +29,13 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/binding" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/gatekeeper" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" ) // GatewayControllerConfig holds the values necessary for configuring the GatewayController. @@ -169,19 +166,6 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - // get all gatewaypolicies referencing this gateway - policies, err := r.getRelatedGatewayPolicies(ctx, req.NamespacedName, resources) - if err != nil { - log.Error(err, "unable to list gateway policies") - return ctrl.Result{}, err - } - - _, err = r.getJWTProviders(ctx, resources) - if err != nil { - log.Error(err, "unable to list JWT providers") - return ctrl.Result{}, err - } - // fetch the rest of the consul objects from cache consulServices := r.getConsulServices(consulKey) consulGateway := r.getConsulGateway(consulKey) @@ -203,7 +187,6 @@ func (r *GatewayController) Reconcile(ctx context.Context, req ctrl.Request) (ct Resources: resources, ConsulGateway: consulGateway, ConsulGatewayServices: consulServices, - Policies: policies, }) updates := binder.Snapshot() @@ -473,29 +456,7 @@ func SetupGatewayControllerWithManager(ctx context.Context, mgr ctrl.Manager, co // Subscribe to changes from Consul for InlineCertificates &source.Channel{Source: c.Subscribe(ctx, api.InlineCertificate, r.transformConsulInlineCertificate(ctx)).Events()}, &handler.EnqueueRequestForObject{}, - ). - Watches( - &source.Channel{Source: c.Subscribe(ctx, api.JWTProvider, r.transformConsulJWTProvider(ctx)).Events()}, - &handler.EnqueueRequestForObject{}, - ). - Watches( - source.NewKindWithCache((&v1alpha1.GatewayPolicy{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformGatewayPolicy(ctx)), - ). - Watches( - source.NewKindWithCache((&v1alpha1.RouteRetryFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteRetryFilter(ctx)), - ). - Watches( - source.NewKindWithCache((&v1alpha1.RouteTimeoutFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteTimeoutFilter(ctx)), - ). - Watches( - // Subscribe to changes in RouteAuthFilter custom resources referenced by HTTPRoutes. - source.NewKindWithCache((&v1alpha1.RouteAuthFilter{}), mgr.GetCache()), - handler.EnqueueRequestsFromMapFunc(r.transformRouteAuthFilter(ctx)), - ). - Complete(r) + ).Complete(r) } // transformGatewayClass will check the list of GatewayClass objects for a matching @@ -611,46 +572,6 @@ func (r *GatewayController) transformConsulHTTPRoute(ctx context.Context) func(e } } -// transformGatewayPolicy will return a list of all gateways that need to be reconcilled. -func (r *GatewayController) transformGatewayPolicy(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - gatewayPolicy := o.(*v1alpha1.GatewayPolicy) - gwNamespace := gatewayPolicy.Spec.TargetRef.Namespace - if gwNamespace == "" { - gwNamespace = gatewayPolicy.Namespace - } - gatewayRef := types.NamespacedName{ - Namespace: gwNamespace, - Name: gatewayPolicy.Spec.TargetRef.Name, - } - return []reconcile.Request{ - { - NamespacedName: gatewayRef, - }, - } - } -} - -// transformRouteRetryFilter will return a list of routes that need to be reconciled. -func (r *GatewayController) transformRouteRetryFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteRetryFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - -// transformTimeoutRetryFilter will return a list of routes that need to be reconciled. -func (r *GatewayController) transformRouteTimeoutFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteTimeoutFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - -func (r *GatewayController) transformRouteAuthFilter(ctx context.Context) func(object client.Object) []reconcile.Request { - return func(o client.Object) []reconcile.Request { - return r.gatewaysForRoutesReferencing(ctx, "", HTTPRoute_RouteAuthFilterIndex, client.ObjectKeyFromObject(o).String()) - } -} - func (r *GatewayController) transformConsulTCPRoute(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { return func(entry api.ConfigEntry) []types.NamespacedName { parents := mapset.NewSet() @@ -694,42 +615,6 @@ func (r *GatewayController) transformConsulInlineCertificate(ctx context.Context } } -func (r *GatewayController) transformConsulJWTProvider(ctx context.Context) func(entry api.ConfigEntry) []types.NamespacedName { - return func(entry api.ConfigEntry) []types.NamespacedName { - var gateways []types.NamespacedName - - jwtEntry := entry.(*api.JWTProviderConfigEntry) - r.Log.Info("gatewaycontroller", "gateway items", r.cache.List(api.APIGateway)) - for _, gwEntry := range r.cache.List(api.APIGateway) { - gateway := gwEntry.(*api.APIGatewayConfigEntry) - LISTENER_LOOP: - for _, listener := range gateway.Listeners { - - r.Log.Info("override names", "listener", fmt.Sprintf("%#v", listener)) - if listener.Override != nil && listener.Override.JWT != nil { - for _, provider := range listener.Override.JWT.Providers { - r.Log.Info("override names", "provider", provider.Name, "entry", jwtEntry.Name) - if provider.Name == jwtEntry.Name { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - continue LISTENER_LOOP - } - } - } - - if listener.Default != nil && listener.Default.JWT != nil { - for _, provider := range listener.Default.JWT.Providers { - if provider.Name == jwtEntry.Name { - gateways = append(gateways, common.EntryToNamespacedName(gateway)) - continue LISTENER_LOOP - } - } - } - } - } - return gateways - } -} - func gatewayReferencesCertificate(certificateKey api.ResourceReference, gateway *api.APIGatewayConfigEntry) bool { for _, listener := range gateway.Listeners { for _, cert := range listener.TLS.Certificates { @@ -775,17 +660,15 @@ func (r *GatewayController) transformEndpoints(ctx context.Context) func(o clien func (r *GatewayController) gatewaysForRoutesReferencing(ctx context.Context, tcpIndex, httpIndex, key string) []reconcile.Request { requestSet := make(map[types.NamespacedName]struct{}) - if tcpIndex != "" { - tcpRouteList := &gwv1alpha2.TCPRouteList{} - if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), - }); err != nil { - r.Log.Error(err, "unable to list TCPRoutes") - } - for _, route := range tcpRouteList.Items { - for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { - requestSet[ref] = struct{}{} - } + tcpRouteList := &gwv1alpha2.TCPRouteList{} + if err := r.Client.List(ctx, tcpRouteList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(tcpIndex, key), + }); err != nil { + r.Log.Error(err, "unable to list TCPRoutes") + } + for _, route := range tcpRouteList.Items { + for _, ref := range common.ParentRefs(common.BetaGroup, common.KindGateway, route.Namespace, route.Spec.ParentRefs) { + requestSet[ref] = struct{}{} } } @@ -894,109 +777,6 @@ func (c *GatewayController) getRelatedHTTPRoutes(ctx context.Context, gateway ty for _, route := range list.Items { resources.ReferenceCountHTTPRoute(route) - - _, err := c.getExternalFiltersForHTTPRoute(ctx, route, resources) - if err != nil { - c.Log.Error(err, "unable to list HTTPRoute ExternalFilters") - return nil, err - } - } - - return list.Items, nil -} - -func (c *GatewayController) getExternalFiltersForHTTPRoute(ctx context.Context, route gwv1beta1.HTTPRoute, resources *common.ResourceMap) ([]interface{}, error) { - var externalFilters []interface{} - for _, rule := range route.Spec.Rules { - ruleFilters, err := c.filterFiltersForExternalRefs(ctx, route, rule.Filters, resources) - if err != nil { - return nil, err - } - externalFilters = append(externalFilters, ruleFilters...) - - for _, backendRef := range rule.BackendRefs { - backendRefFilter, err := c.filterFiltersForExternalRefs(ctx, route, backendRef.Filters, resources) - if err != nil { - return nil, err - } - - externalFilters = append(externalFilters, backendRefFilter...) - } - } - - return externalFilters, nil -} - -func (c *GatewayController) filterFiltersForExternalRefs(ctx context.Context, route gwv1beta1.HTTPRoute, filters []gwv1beta1.HTTPRouteFilter, resources *common.ResourceMap) ([]interface{}, error) { - var externalFilters []interface{} - - for _, filter := range filters { - var externalFilter client.Object - - // check to see if we need to grab this filter - if filter.ExtensionRef == nil { - continue - } - switch kind := filter.ExtensionRef.Kind; kind { - case v1alpha1.RouteRetryFilterKind: - externalFilter = &v1alpha1.RouteRetryFilter{} - case v1alpha1.RouteTimeoutFilterKind: - externalFilter = &v1alpha1.RouteTimeoutFilter{} - case v1alpha1.RouteAuthFilterKind: - externalFilter = &v1alpha1.RouteAuthFilter{} - default: - continue - } - - // get object from API - err := c.Client.Get(ctx, client.ObjectKey{ - Name: string(filter.ExtensionRef.Name), - Namespace: route.Namespace, - }, externalFilter) - if err != nil { - if k8serrors.IsNotFound(err) { - c.Log.Info(fmt.Sprintf("externalref %s:%s not found: %v", filter.ExtensionRef.Kind, filter.ExtensionRef.Name, err)) - // ignore, the validation call should mark this route as error - continue - } else { - return nil, err - } - } - - // add external ref (or error) to resource map for this route - resources.AddExternalFilter(externalFilter) - externalFilters = append(externalFilters, externalFilter) - } - return externalFilters, nil -} - -func (c *GatewayController) getRelatedGatewayPolicies(ctx context.Context, gateway types.NamespacedName, resources *common.ResourceMap) ([]v1alpha1.GatewayPolicy, error) { - var list v1alpha1.GatewayPolicyList - - if err := c.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gateway.String()), - }); err != nil { - return nil, err - } - - // add all policies to the resourcemap - for _, policy := range list.Items { - resources.AddGatewayPolicy(&policy) - } - - return list.Items, nil -} - -func (c *GatewayController) getJWTProviders(ctx context.Context, resources *common.ResourceMap) ([]v1alpha1.JWTProvider, error) { - var list v1alpha1.JWTProviderList - - if err := c.Client.List(ctx, &list, &client.ListOptions{}); err != nil { - return nil, err - } - - // add all policies to the resourcemap - for _, provider := range list.Items { - resources.AddJWTProvider(&provider) } return list.Items, nil @@ -1192,7 +972,6 @@ func (c *GatewayController) fetchServicesForEndpoints(ctx context.Context, resou } resources.AddService(key, serviceName(pod, endpoints)) - } } } diff --git a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go index ee8d8240f6..8083cbd81b 100644 --- a/control-plane/api-gateway/controllers/gateway_controller_integration_test.go +++ b/control-plane/api-gateway/controllers/gateway_controller_integration_test.go @@ -32,12 +32,11 @@ import ( gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/cache" "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul/api" ) func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { @@ -48,13 +47,11 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { require.NoError(t, v1alpha1.AddToScheme(s)) testCases := map[string]struct { - namespace string - certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret - gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway - httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute - tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute - externalFilterFn func(*testing.T, context.Context, client.WithWatch, string) *v1alpha1.RouteAuthFilter - policyFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway, string) + namespace string + certFn func(*testing.T, context.Context, client.WithWatch, string) *corev1.Secret + gwFn func(*testing.T, context.Context, client.WithWatch, string) *gwv1beta1.Gateway + httpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute + tcpRouteFn func(*testing.T, context.Context, client.WithWatch, *gwv1beta1.Gateway) *v1alpha2.TCPRoute }{ "all fields set": { namespace: "consul", @@ -62,10 +59,6 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: createAllFieldsSetAPIGW, httpRouteFn: createAllFieldsSetHTTPRoute, tcpRouteFn: createAllFieldsSetTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, }, "minimal fields set": { namespace: "", @@ -73,10 +66,6 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: minimalFieldsSetAPIGW, httpRouteFn: minimalFieldsSetHTTPRoute, tcpRouteFn: minimalFieldsSetTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, }, "funky casing to test normalization doesnt cause infinite reconciliation": { namespace: "", @@ -84,30 +73,6 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { gwFn: createFunkyCasingFieldsAPIGW, httpRouteFn: createFunkyCasingFieldsHTTPRoute, tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "http route with JWT auth": { - namespace: "", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createJWTAuthHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: createRouteAuthFilter, - policyFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ *gwv1beta1.Gateway, _ string) {}, - }, - "policy attached to gateway": { - namespace: "", - certFn: createCert, - gwFn: createAllFieldsSetAPIGW, - httpRouteFn: createAllFieldsSetHTTPRoute, - tcpRouteFn: createFunkyCasingFieldsTCPRoute, - externalFilterFn: func(_ *testing.T, _ context.Context, _ client.WithWatch, _ string) *v1alpha1.RouteAuthFilter { - return nil - }, - policyFn: createGWPolicy, }, } @@ -174,11 +139,8 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { }) require.NoError(t, err) - jwtProvider := createJWTProvider(t, ctx, k8sClient) - authFilterObj := tc.externalFilterFn(t, ctx, k8sClient, jwtProvider.Name) - httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj, authFilterObj) + httpRouteObj := tc.httpRouteFn(t, ctx, k8sClient, k8sGWObj) tcpRouteObj := tc.tcpRouteFn(t, ctx, k8sClient, k8sGWObj) - tc.policyFn(t, ctx, k8sClient, k8sGWObj, jwtProvider.Name) // reconcile again so that we get the route bound to the gateway _, err = gwCtrl.Reconcile(ctx, reconcile.Request{ @@ -213,17 +175,17 @@ func TestControllerDoesNotInfinitelyReconcile(t *testing.T) { case <-gwSub.Events(): if !gwDone { gwDone = true - w.Done() + wg.Done() } case <-httpRouteSub.Events(): if !httpRouteDone { httpRouteDone = true - w.Done() + wg.Done() } case <-tcpRouteSub.Events(): if !tcpRouteDone { tcpRouteDone = true - w.Done() + wg.Done() } case <-inlineCertSub.Events(): } @@ -461,7 +423,7 @@ func createAllFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client return gw } -func createJWTAuthHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, authFilter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { +func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -621,14 +583,6 @@ func createJWTAuthHTTPRoute(t *testing.T, ctx context.Context, k8sClient client. }, }, }, - { - Type: gwv1beta1.HTTPRouteFilterExtensionRef, - ExtensionRef: &gwv1beta1.LocalObjectReference{ - Group: gwv1beta1.Group(v1alpha1.ConsulHashicorpGroup), - Kind: v1alpha1.RouteAuthFilterKind, - Name: gwv1beta1.ObjectName(authFilter.Name), - }, - }, }, BackendRefs: []gwv1beta1.HTTPBackendRef{ { @@ -845,7 +799,7 @@ func minimalFieldsSetAPIGW(t *testing.T, ctx context.Context, k8sClient client.W return gw } -func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { +func minimalFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -1145,7 +1099,7 @@ func createFunkyCasingFieldsAPIGW(t *testing.T, ctx context.Context, k8sClient c return gw } -func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, _ *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { +func createFunkyCasingFieldsHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway) *gwv1beta1.HTTPRoute { svcDefault := &v1alpha1.ServiceDefaults{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceDefaults", @@ -1367,271 +1321,3 @@ func createFunkyCasingFieldsTCPRoute(t *testing.T, ctx context.Context, k8sClien return route } - -func createAllFieldsSetHTTPRoute(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, filter *v1alpha1.RouteAuthFilter) *gwv1beta1.HTTPRoute { - svcDefault := &v1alpha1.ServiceDefaults{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceDefaults", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - Spec: v1alpha1.ServiceDefaultsSpec{ - Protocol: "http", - }, - } - - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "high", - Protocol: "TCP", - Port: 8080, - }, - }, - Selector: map[string]string{"app": "Service"}, - }, - } - - serviceAccount := &corev1.ServiceAccount{ - TypeMeta: metav1.TypeMeta{ - Kind: "ServiceAccount", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - }, - } - - deployment := &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "Service", - Labels: map[string]string{"app": "Service"}, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: common.PointerTo(int32(1)), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{"app": "Service"}, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: corev1.PodSpec{}, - }, - }, - } - - err := k8sClient.Create(ctx, svcDefault) - require.NoError(t, err) - - err = k8sClient.Create(ctx, svc) - require.NoError(t, err) - - err = k8sClient.Create(ctx, serviceAccount) - require.NoError(t, err) - - err = k8sClient.Create(ctx, deployment) - require.NoError(t, err) - - route := &gwv1beta1.HTTPRoute{ - TypeMeta: metav1.TypeMeta{ - Kind: "HTTPRoute", - APIVersion: "gateway.networking.k8s.io/v1beta1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "http-route", - }, - Spec: gwv1beta1.HTTPRouteSpec{ - CommonRouteSpec: gwv1beta1.CommonRouteSpec{ - ParentRefs: []gwv1beta1.ParentReference{ - { - Kind: (*gwv1beta1.Kind)(&gw.Kind), - Namespace: (*gwv1beta1.Namespace)(&gw.Namespace), - Name: gwv1beta1.ObjectName(gw.Name), - SectionName: &gw.Spec.Listeners[0].Name, - Port: &gw.Spec.Listeners[0].Port, - }, - }, - }, - Hostnames: []gwv1beta1.Hostname{"route.consul.io"}, - Rules: []gwv1beta1.HTTPRouteRule{ - { - Matches: []gwv1beta1.HTTPRouteMatch{ - { - Path: &gwv1beta1.HTTPPathMatch{ - Type: common.PointerTo(gwv1beta1.PathMatchType("PathPrefix")), - Value: common.PointerTo("/v1"), - }, - Headers: []gwv1beta1.HTTPHeaderMatch{ - { - Type: common.PointerTo(gwv1beta1.HeaderMatchExact), - Name: "version", - Value: "version", - }, - }, - QueryParams: []gwv1beta1.HTTPQueryParamMatch{ - { - Type: common.PointerTo(gwv1beta1.QueryParamMatchExact), - Name: "search", - Value: "q", - }, - }, - Method: common.PointerTo(gwv1beta1.HTTPMethod("GET")), - }, - }, - Filters: []gwv1beta1.HTTPRouteFilter{ - { - Type: gwv1beta1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gwv1beta1.HTTPHeaderFilter{ - Set: []gwv1beta1.HTTPHeader{ - { - Name: "foo", - Value: "bax", - }, - }, - Add: []gwv1beta1.HTTPHeader{ - { - Name: "arc", - Value: "reactor", - }, - }, - Remove: []string{"remove"}, - }, - }, - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.FullPathHTTPPathModifier, - ReplaceFullPath: common.PointerTo("/foobar"), - }, - }, - }, - - { - Type: gwv1beta1.HTTPRouteFilterURLRewrite, - URLRewrite: &gwv1beta1.HTTPURLRewriteFilter{ - Hostname: common.PointerTo(gwv1beta1.PreciseHostname("host.com")), - Path: &gwv1beta1.HTTPPathModifier{ - Type: gwv1beta1.PrefixMatchHTTPPathModifier, - ReplacePrefixMatch: common.PointerTo("/foo"), - }, - }, - }, - }, - BackendRefs: []gwv1beta1.HTTPBackendRef{ - { - BackendRef: gwv1beta1.BackendRef{ - BackendObjectReference: gwv1beta1.BackendObjectReference{ - Name: "Service", - Port: common.PointerTo(gwv1beta1.PortNumber(8080)), - }, - Weight: common.PointerTo(int32(50)), - }, - }, - }, - }, - }, - }, - } - - err = k8sClient.Create(ctx, route) - require.NoError(t, err) - - return route -} - -func createRouteAuthFilter(t *testing.T, ctx context.Context, k8sClient client.WithWatch, providerName string) *v1alpha1.RouteAuthFilter { - filter := &v1alpha1.RouteAuthFilter{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.RouteAuthFilterKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "auth-filter", - }, - Spec: v1alpha1.RouteAuthFilterSpec{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - } - err := k8sClient.Create(ctx, filter) - require.NoError(t, err) - - return filter -} - -func createJWTProvider(t *testing.T, ctx context.Context, k8sClient client.WithWatch) *v1alpha1.JWTProvider { - provider := &v1alpha1.JWTProvider{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.JWTProviderKubeKind, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "provider", - }, - Spec: v1alpha1.JWTProviderSpec{ - JSONWebKeySet: &v1alpha1.JSONWebKeySet{}, - Issuer: "local", - }, - } - - err := k8sClient.Create(ctx, provider) - require.NoError(t, err) - - return provider -} - -func createGWPolicy(t *testing.T, ctx context.Context, k8sClient client.WithWatch, gw *gwv1beta1.Gateway, providerName string) { - policy := &v1alpha1.GatewayPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "GatewayPolicy", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-policy", - }, - Spec: v1alpha1.GatewayPolicySpec{ - TargetRef: v1alpha1.PolicyTargetReference{ - Group: gw.GroupVersionKind().Group, - Kind: gw.GroupVersionKind().Kind, - Name: gw.Name, - Namespace: gw.Namespace, - SectionName: &gw.Spec.Listeners[0].Name, - }, - Override: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - Default: &v1alpha1.GatewayPolicyConfig{ - JWT: &v1alpha1.GatewayJWTRequirement{ - Providers: []*v1alpha1.GatewayJWTProvider{ - { - Name: providerName, - }, - }, - }, - }, - }, - } - - err := k8sClient.Create(ctx, policy) - require.NoError(t, err) -} diff --git a/control-plane/api-gateway/controllers/index.go b/control-plane/api-gateway/controllers/index.go index 46c1f98459..7fd13de1de 100644 --- a/control-plane/api-gateway/controllers/index.go +++ b/control-plane/api-gateway/controllers/index.go @@ -6,8 +6,6 @@ package controllers import ( "context" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -21,23 +19,15 @@ const ( // Naming convention: TARGET_REFERENCE. GatewayClass_GatewayClassConfigIndex = "__gatewayclass_referencing_gatewayclassconfig" GatewayClass_ControllerNameIndex = "__gatewayclass_controller_name" - - Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" - - HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" - HTTPRoute_ServiceIndex = "__httproute_referencing_service" - HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" - HTTPRoute_RouteRetryFilterIndex = "__httproute_referencing_retryfilter" - HTTPRoute_RouteTimeoutFilterIndex = "__httproute_referencing_timeoutfilter" - HTTPRoute_RouteAuthFilterIndex = "__httproute_referencing_routeauthfilter" - - TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" - TCPRoute_ServiceIndex = "__tcproute_referencing_service" - TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" - - MeshService_PeerIndex = "__meshservice_referencing_peer" - Secret_GatewayIndex = "__secret_referencing_gateway" - Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" + Gateway_GatewayClassIndex = "__gateway_referencing_gatewayclass" + HTTPRoute_GatewayIndex = "__httproute_referencing_gateway" + HTTPRoute_ServiceIndex = "__httproute_referencing_service" + HTTPRoute_MeshServiceIndex = "__httproute_referencing_mesh_service" + TCPRoute_GatewayIndex = "__tcproute_referencing_gateway" + TCPRoute_ServiceIndex = "__tcproute_referencing_service" + TCPRoute_MeshServiceIndex = "__tcproute_referencing_mesh_service" + MeshService_PeerIndex = "__meshservice_referencing_peer" + Secret_GatewayIndex = "__secret_referencing_gateway" ) // RegisterFieldIndexes registers all of the field indexes for the API gateway controllers. @@ -114,26 +104,6 @@ var indexes = []index{ target: &v1alpha1.MeshService{}, indexerFunc: peersForMeshService, }, - { - name: HTTPRoute_RouteRetryFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: HTTPRoute_RouteTimeoutFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: HTTPRoute_RouteAuthFilterIndex, - target: &gwv1beta1.HTTPRoute{}, - indexerFunc: filtersForHTTPRoute, - }, - { - name: Gatewaypolicy_GatewayIndex, - target: &v1alpha1.GatewayPolicy{}, - indexerFunc: gatewayForGatewayPolicy, - }, } // gatewayClassConfigForGatewayClass creates an index of every GatewayClassConfig referenced by a GatewayClass. @@ -301,66 +271,3 @@ func gatewaysForRoute(namespace string, refs []gwv1beta1.ParentReference, status } return references } - -func filtersForHTTPRoute(o client.Object) []string { - route := o.(*gwv1beta1.HTTPRoute) - filters := []string{} - var nilString *string - - for _, rule := range route.Spec.Rules { - FILTERS_LOOP: - for _, filter := range rule.Filters { - if common.FilterIsExternalFilter(filter) { - // TODO this seems like its type agnostic, so this might just work without having to make - // multiple index functions per custom filter type? - - // index external filters - filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() - for _, member := range filters { - if member == filter { - continue FILTERS_LOOP - } - } - filters = append(filters, filter) - } - } - - // same thing but over the backend refs - BACKEND_LOOP: - for _, ref := range rule.BackendRefs { - for _, filter := range ref.Filters { - if common.FilterIsExternalFilter(filter) { - filter := common.IndexedNamespacedNameWithDefault(string(filter.ExtensionRef.Name), nilString, route.Namespace).String() - for _, member := range filters { - if member == filter { - continue BACKEND_LOOP - } - } - filters = append(filters, filter) - } - } - } - } - return filters -} - -func gatewayForGatewayPolicy(o client.Object) []string { - gatewayPolicy := o.(*v1alpha1.GatewayPolicy) - - targetGateway := gatewayPolicy.Spec.TargetRef - if targetGateway.Group == gwv1beta1.GroupVersion.String() && targetGateway.Kind == common.KindGateway { - policyNamespace := gatewayPolicy.Namespace - if policyNamespace == "" { - policyNamespace = "default" - } - targetNS := targetGateway.Namespace - if targetNS == "" { - targetNS = policyNamespace - } - - namespacedName := types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS} - return []string{namespacedName.String()} - } - - return []string{} -} diff --git a/control-plane/api-gateway/gatekeeper/dataplane.go b/control-plane/api-gateway/gatekeeper/dataplane.go index 090464e9c8..2cfe8d5271 100644 --- a/control-plane/api-gateway/gatekeeper/dataplane.go +++ b/control-plane/api-gateway/gatekeeper/dataplane.go @@ -159,7 +159,7 @@ func getDataplaneArgs(namespace string, config common.HelmConfig, bearerTokenFil args = append(args, "-tls-server-name="+config.ConsulTLSServerName) } if config.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) + args = append(args, "-ca-certs="+constants.ConsulCAFile) } } else { args = append(args, "-tls-disabled") diff --git a/control-plane/api/auth/v2beta1/auth_groupversion_info.go b/control-plane/api/auth/v2beta1/auth_groupversion_info.go deleted file mode 100644 index 3329d86855..0000000000 --- a/control-plane/api/auth/v2beta1/auth_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group -// +kubebuilder:object:generate=true -// +groupName=auth.consul.hashicorp.com -package v2beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - - // AuthGroup is a collection of auth resources. - AuthGroup = "auth.consul.hashicorp.com" - - // AuthGroupVersion is group version used to register these objects. - AuthGroupVersion = schema.GroupVersion{Group: AuthGroup, Version: "v2beta1"} - - // AuthSchemeBuilder is used to add go types to the GroupVersionKind scheme. - AuthSchemeBuilder = &scheme.Builder{GroupVersion: AuthGroupVersion} - - // AddAuthToScheme adds the types in this group-version to the given scheme. - AddAuthToScheme = AuthSchemeBuilder.AddToScheme -) diff --git a/control-plane/api/auth/v2beta1/shared_types.go b/control-plane/api/auth/v2beta1/shared_types.go deleted file mode 100644 index a5225afb71..0000000000 --- a/control-plane/api/auth/v2beta1/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func meshConfigMeta() map[string]string { - return map[string]string{ - common.SourceKey: common.SourceValue, - } -} diff --git a/control-plane/api/auth/v2beta1/status.go b/control-plane/api/auth/v2beta1/status.go deleted file mode 100644 index cc75a1cd82..0000000000 --- a/control-plane/api/auth/v2beta1/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Conditions is the schema for the conditions portion of the payload. -type Conditions []Condition - -// ConditionType is a camel-cased condition type. -type ConditionType string - -const ( - // ConditionSynced specifies that the resource has been synced with Consul. - ConditionSynced ConditionType = "Synced" -) - -// Conditions define a readiness condition for a Consul resource. -// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Condition struct { - // Type of condition. - // +required - Type ConditionType `json:"type" description:"type of status condition"` - - // Status of the condition, one of True, False, Unknown. - // +required - Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` - - // LastTransitionTime is the last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` - - // The reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` - - // A human readable message indicating details about the transition. - // +optional - Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` -} - -// IsTrue is true if the condition is True. -func (c *Condition) IsTrue() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionTrue -} - -// IsFalse is true if the condition is False. -func (c *Condition) IsFalse() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionFalse -} - -// IsUnknown is true if the condition is Unknown. -func (c *Condition) IsUnknown() bool { - if c == nil { - return true - } - return c.Status == corev1.ConditionUnknown -} - -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Status struct { - // Conditions indicate the latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // LastSyncedTime is the last time the resource successfully synced with Consul. - // +optional - LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` -} - -func (s *Status) GetCondition(t ConditionType) *Condition { - for _, cond := range s.Conditions { - if cond.Type == t { - return &cond - } - } - return nil -} diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types.go b/control-plane/api/auth/v2beta1/traffic_permissions_types.go deleted file mode 100644 index e3a0d32f1a..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - trafficpermissionsKubeKind = "trafficpermissions" -) - -func init() { - AuthSchemeBuilder.Register(&TrafficPermissions{}, &TrafficPermissionsList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// TrafficPermissions is the Schema for the traffic-permissions API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="traffic-permissions" -type TrafficPermissions struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbauth.TrafficPermissions `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TrafficPermissionsList contains a list of TrafficPermissions. -type TrafficPermissionsList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*TrafficPermissions `json:"items"` -} - -func (in *TrafficPermissions) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbauth.TrafficPermissionsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *TrafficPermissions) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *TrafficPermissions) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *TrafficPermissions) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *TrafficPermissions) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *TrafficPermissions) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *TrafficPermissions) KubeKind() string { - return trafficpermissionsKubeKind -} - -func (in *TrafficPermissions) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *TrafficPermissions) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *TrafficPermissions) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *TrafficPermissions) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *TrafficPermissions) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *TrafficPermissions) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - path := field.NewPath("spec") - var tp pbauth.TrafficPermissions - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - if err := res.Data.UnmarshalTo(&tp); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &tp, err) - } - - switch tp.Action { - case pbauth.Action_ACTION_ALLOW: - case pbauth.Action_ACTION_DENY: - case pbauth.Action_ACTION_UNSPECIFIED: - fallthrough - default: - errs = append(errs, field.Invalid(path.Child("action"), tp.Action, "action must be either allow or deny")) - } - - if tp.Destination == nil || (len(tp.Destination.IdentityName) == 0) { - errs = append(errs, field.Invalid(path.Child("destination"), tp.Destination, "cannot be empty")) - } - // Validate permissions - for i, permission := range tp.Permissions { - if err := validatePermission(permission, path.Child("permissions").Index(i)); err != nil { - errs = append(errs, err...) - } - } - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: AuthGroup, Kind: common.TrafficPermissions}, - in.KubernetesName(), errs) - } - return nil -} - -func validatePermission(p *pbauth.Permission, path *field.Path) field.ErrorList { - var errs field.ErrorList - - for s, src := range p.Sources { - if sourceHasIncompatibleTenancies(src) { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not specify partitions, peers, and sameness_groups together")) - } - - if src.Namespace == "" && src.IdentityName != "" { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "permission sources may not have wildcard namespaces and explicit names")) - } - - // Excludes are only valid for wildcard sources. - if src.IdentityName != "" && len(src.Exclude) > 0 { - errs = append(errs, field.Invalid(path.Child("sources").Index(s), src, "must be defined on wildcard sources")) - continue - } - - for e, d := range src.Exclude { - if sourceHasIncompatibleTenancies(d) { - errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permissions sources may not specify partitions, peers, and sameness_groups together")) - } - - if d.Namespace == "" && d.IdentityName != "" { - errs = append(errs, field.Invalid(path.Child("sources").Index(s).Child("exclude").Index(e), d, "permission sources may not have wildcard namespaces and explicit names")) - } - } - } - for d, dest := range p.DestinationRules { - if (len(dest.PathExact) > 0 && len(dest.PathPrefix) > 0) || - (len(dest.PathRegex) > 0 && len(dest.PathExact) > 0) || - (len(dest.PathRegex) > 0 && len(dest.PathPrefix) > 0) { - errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d), dest, "prefix values, regex values, and explicit names must not combined")) - } - if len(dest.Exclude) > 0 { - for e, excl := range dest.Exclude { - if (len(excl.PathExact) > 0 && len(excl.PathPrefix) > 0) || - (len(excl.PathRegex) > 0 && len(excl.PathExact) > 0) || - (len(excl.PathRegex) > 0 && len(excl.PathPrefix) > 0) { - errs = append(errs, field.Invalid(path.Child("destinationRules").Index(d).Child("exclude").Index(e), excl, "prefix values, regex values, and explicit names must not combined")) - } - } - } - } - - return errs -} - -func sourceHasIncompatibleTenancies(src pbauth.SourceToSpiffe) bool { - peerSet := src.GetPeer() != common.DefaultPeerName && src.GetPeer() != "" - apSet := src.GetPartition() != common.DefaultPartitionName && src.GetPartition() != "" - sgSet := src.GetSamenessGroup() != "" - - return (apSet && peerSet) || (apSet && sgSet) || (peerSet && sgSet) -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *TrafficPermissions) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go b/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go deleted file mode 100644 index 79b5e71b85..0000000000 --- a/control-plane/api/auth/v2beta1/traffic_permissions_types_test.go +++ /dev/null @@ -1,1036 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestTrafficPermissions_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *TrafficPermissions - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbauth.TrafficPermissions - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbauth.TrafficPermissions{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbauth.TrafficPermissions{ - Destination: nil, - Action: pbauth.Action_ACTION_UNSPECIFIED, - Permissions: nil, - }, - Matches: true, - }, - "source namespaces and partitions are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: "the space namespace space", - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: "not space namespace", - }, - }, - }, - }, - }, - Matches: false, - }, - "destination namespaces and partitions are compared": { - OurConsulNamespace: "not-consul-ns", - OurConsulPartition: "not-consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_DENY, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - // These are intentionally in a different order to show that it doesn't matter - { - IdentityName: "source-identity", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbauth.TrafficPermissions{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructTrafficPermissionResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestTrafficPermissions_Resource also includes test to verify ResourceID(). -func TestTrafficPermissions_Resource(t *testing.T) { - cases := map[string]struct { - Ours *TrafficPermissions - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbauth.TrafficPermissions - }{ - "empty fields": { - Ours: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbauth.TrafficPermissions{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbauth.TrafficPermissions{}, - }, - "every field set": { - Ours: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - // These are intentionally in a different order to show that it doesn't matter - { - IdentityName: "source-identity", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - SamenessGroup: "space-group", - }, - }, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "/world", - PathRegex: "/.*/foo", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructTrafficPermissionResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - }) - } -} - -func TestTrafficPermissions_SetSyncedCondition(t *testing.T) { - trafficPermissions := &TrafficPermissions{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestTrafficPermissions_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &TrafficPermissions{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestTrafficPermissions_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &TrafficPermissions{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestTrafficPermissions_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&TrafficPermissions{}).GetCondition(ConditionSynced)) -} - -func TestTrafficPermissions_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&TrafficPermissions{}).SyncedConditionStatus()) -} - -func TestTrafficPermissions_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&TrafficPermissions{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestTrafficPermissions_KubeKind(t *testing.T) { - require.Equal(t, "trafficpermissions", (&TrafficPermissions{}).KubeKind()) -} - -func TestTrafficPermissions_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "foo", - }, - }, - }).KubernetesName()) -} - -func TestTrafficPermissions_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &TrafficPermissions{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestTrafficPermissions_DefaultNamespaceFields(t *testing.T) - -func TestTrafficPermissions_Validate(t *testing.T) { - cases := []struct { - name string - input *TrafficPermissions - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - Namespace: "the space namespace space", - SamenessGroup: "space-group", - }, - }, - }, - { - IdentityName: "source-identity", - Namespace: "another-namespace", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-test", - Present: true, - Exact: "true", - Prefix: "prefix", - Suffix: "suffix", - Regex: "reg.*ex", - Invert: true, - }, - Methods: []string{"GET", "POST"}, - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathPrefix: "/world", - Header: &pbauth.DestinationRuleHeader{ - Name: "x-consul-not-test", - Present: true, - Exact: "false", - Prefix: "~prefix", - Suffix: "~suffix", - Regex: "~reg.*ex", - Invert: true, - }, - Methods: []string{"DELETE"}, - PortNames: []string{"log"}, - }, - }, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "must have an action", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "dest-service", - }, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.action: Invalid value: ACTION_UNSPECIFIED: action must be either allow or deny`, - }, - }, - { - name: "destination is required", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Action: pbauth.Action_ACTION_ALLOW, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: "null": cannot be empty`, - }, - }, - { - name: "destination.identityName is required", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Action: pbauth.Action_ACTION_ALLOW, - Destination: &pbauth.Destination{}, - }, - }, - expectedErrMsgs: []string{ - `trafficpermissions.auth.consul.hashicorp.com "does-not-matter" is invalid: spec.destination: Invalid value: authv2beta1.Destination{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:""}: cannot be empty`, - }, - }, - { - name: "permission.sources: partitions, peers, and sameness_groups", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - SamenessGroup: "space-sameness", - }, - { - Namespace: "the space namespace space", - Peer: "space-peer", - SamenessGroup: "space-sameness", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[1]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[2]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not specify partitions, peers, and sameness_groups together`, - }, - }, - { - name: "permission.sources: identity name without namespace", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "false-identity", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0]: Invalid value: authv2beta1.Source{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:"", Exclude:[]*authv2beta1.ExcludeSource(nil)}: permission sources may not have wildcard namespaces and explicit names`, - }, - }, - { - name: "permission.sources: identity name with excludes", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - IdentityName: "false-identity", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "not-source-identity", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `must be defined on wildcard sources`, - }, - }, - { - name: "permission.sources.exclude: incompatible tenancies", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - Exclude: []*pbauth.ExcludeSource{ - { - Namespace: "the space namespace space", - Partition: "space-partition", - Peer: "space-peer", - }, - { - Namespace: "the space namespace space", - Partition: "space-partition", - SamenessGroup: "space-sameness", - }, - { - Namespace: "the space namespace space", - Peer: "space-peer", - SamenessGroup: "space-sameness", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"space-peer", SamenessGroup:""}: permissions sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[0].exclude[1]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"space-partition", Peer:"", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, - `spec.permissions[0].sources[0].exclude[2]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"", Namespace:"the space namespace space", Partition:"", Peer:"space-peer", SamenessGroup:"space-sameness"}: permissions sources may not specify partitions, peers, and sameness_groups together`, - }, - }, - { - name: "permission.sources.exclude: identity name without namespace", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "default-namespace", - Exclude: []*pbauth.ExcludeSource{ - { - IdentityName: "false-identity", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].sources[0].exclude[0]: Invalid value: authv2beta1.ExcludeSource{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), IdentityName:"false-identity", Namespace:"", Partition:"", Peer:"", SamenessGroup:""}: permission sources may not have wildcard namespaces and explicit names`, - }, - }, - { - name: "permission.destinationRules: incompatible destination rules", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - PathPrefix: "foobar", - }, - { - PathExact: "/hello", - PathRegex: "path-regex", - }, - { - PathPrefix: "foobar", - PathRegex: "path-regex", - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[1]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[2]: Invalid value: authv2beta1.DestinationRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil), Exclude:[]*authv2beta1.ExcludePermissionRule(nil)}: prefix values, regex values, and explicit names must not combined`, - }, - }, - { - name: "permission.destinationRules.exclude: incompatible destination rules", - input: &TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "does-not-matter", - Namespace: "not-default-ns", - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - DestinationRules: []*pbauth.DestinationRule{ - { - Exclude: []*pbauth.ExcludePermissionRule{ - { - PathExact: "/hello", - PathPrefix: "foobar", - }, - { - PathExact: "/hello", - PathRegex: "path-regex", - }, - { - PathPrefix: "foobar", - PathRegex: "path-regex", - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.permissions[0].destinationRules[0].exclude[0]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"foobar", PathRegex:"", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[1]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"/hello", PathPrefix:"", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - `spec.permissions[0].destinationRules[0].exclude[2]: Invalid value: authv2beta1.ExcludePermissionRule{state:impl.MessageState{NoUnkeyedLiterals:pragma.NoUnkeyedLiterals{}, DoNotCompare:pragma.DoNotCompare{}, DoNotCopy:pragma.DoNotCopy{}, atomicMessageInfo:(*impl.MessageInfo)(nil)}, sizeCache:0, unknownFields:[]uint8(nil), PathExact:"", PathPrefix:"foobar", PathRegex:"path-regex", Methods:[]string(nil), Header:(*authv2beta1.DestinationRuleHeader)(nil), PortNames:[]string(nil)}: prefix values, regex values, and explicit names must not combined`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructTrafficPermissionResource(tp *pbauth.TrafficPermissions, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbauth.TrafficPermissionsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go b/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go deleted file mode 100644 index c7d8d0189d..0000000000 --- a/control-plane/api/auth/v2beta1/trafficpermissions_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type TrafficPermissionsWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.MeshConfigLister = &TrafficPermissionsWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-trafficpermissions,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=trafficpermissions,versions=v2beta1,name=mutate-trafficpermissions.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *TrafficPermissionsWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource TrafficPermissions - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *TrafficPermissionsWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { - var resourceList TrafficPermissionsList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.MeshConfig - for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) - } - return entries, nil -} - -func (v *TrafficPermissionsWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go b/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 3aa46646cb..0000000000 --- a/control-plane/api/auth/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,136 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissions) DeepCopyInto(out *TrafficPermissions) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissions. -func (in *TrafficPermissions) DeepCopy() *TrafficPermissions { - if in == nil { - return nil - } - out := new(TrafficPermissions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissions) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TrafficPermissionsList) DeepCopyInto(out *TrafficPermissionsList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*TrafficPermissions, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(TrafficPermissions) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficPermissionsList. -func (in *TrafficPermissionsList) DeepCopy() *TrafficPermissionsList { - if in == nil { - return nil - } - out := new(TrafficPermissionsList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TrafficPermissionsList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/control-plane/api/common/common.go b/control-plane/api/common/common.go index 2cf32fdc0b..a4063d6147 100644 --- a/control-plane/api/common/common.go +++ b/control-plane/api/common/common.go @@ -4,10 +4,7 @@ // Package common holds code that isn't tied to a particular CRD version or type. package common -import mapset "github.com/deckarep/golang-set" - const ( - // V1 config entries. ServiceDefaults string = "servicedefaults" ProxyDefaults string = "proxydefaults" ServiceResolver string = "serviceresolver" @@ -20,15 +17,6 @@ const ( SamenessGroup string = "samenessgroup" JWTProvider string = "jwtprovider" ControlPlaneRequestLimit string = "controlplanerequestlimit" - RouteAuthFilter string = "routeauthfilter" - GatewayPolicy string = "gatewaypolicy" - - // V2 config entries. - TrafficPermissions string = "trafficpermissions" - GRPCRoute string = "grpcroute" - HTTPRoute string = "httproute" - TCPRoute string = "tcproute" - ProxyConfiguration string = "proxyconfiguration" Global string = "global" Mesh string = "mesh" @@ -41,38 +29,4 @@ const ( MigrateEntryKey string = "consul.hashicorp.com/migrate-entry" MigrateEntryTrue string = "true" SourceValue string = "kubernetes" - - DefaultPartitionName = "default" - DefaultNamespaceName = "default" - DefaultPeerName = "local" ) - -// ConsulTenancyConfig manages settings related to Consul namespaces and partitions. -type ConsulTenancyConfig struct { - // EnableConsulPartitions indicates that a user is running Consul Enterprise. - EnableConsulPartitions bool - // ConsulPartition is the Consul Partition to which this controller belongs. - ConsulPartition string - // EnableConsulNamespaces indicates that a user is running Consul Enterprise. - EnableConsulNamespaces bool - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all resources in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Resources will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string -} - -// K8sNamespaceConfig manages allow/deny Kubernetes namespaces. -type K8sNamespaceConfig struct { - // Only endpoints in the AllowK8sNamespacesSet are reconciled. - AllowK8sNamespacesSet mapset.Set - // Endpoints in the DenyK8sNamespacesSet are ignored. - DenyK8sNamespacesSet mapset.Set -} diff --git a/control-plane/api/common/meshconfig.go b/control-plane/api/common/meshconfig.go deleted file mode 100644 index 139818d307..0000000000 --- a/control-plane/api/common/meshconfig.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "github.com/hashicorp/consul/proto-public/pbresource" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -type MeshConfig interface { - ResourceID(namespace, partition string) *pbresource.ID - Resource(namespace, partition string) *pbresource.Resource - - // GetObjectKind should be implemented by the generated code. - GetObjectKind() schema.ObjectKind - // DeepCopyObject should be implemented by the generated code. - DeepCopyObject() runtime.Object - - // AddFinalizer adds a finalizer to the list of finalizers. - AddFinalizer(name string) - // RemoveFinalizer removes this finalizer from the list. - RemoveFinalizer(name string) - // Finalizers returns the list of finalizers for this object. - Finalizers() []string - - // MatchesConsul returns true if the resource has the same fields as the Consul - // config entry. - MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool - - // KubeKind returns the Kube config entry kind, i.e. servicedefaults, not - // service-defaults. - KubeKind() string - // KubernetesName returns the name of the Kubernetes resource. - KubernetesName() string - - // SetSyncedCondition updates the synced condition. - SetSyncedCondition(status corev1.ConditionStatus, reason, message string) - // SetLastSyncedTime updates the last synced time. - SetLastSyncedTime(time *metav1.Time) - // SyncedCondition gets the synced condition. - SyncedCondition() (status corev1.ConditionStatus, reason, message string) - // SyncedConditionStatus returns the status of the synced condition. - SyncedConditionStatus() corev1.ConditionStatus - - // Validate returns an error if the resource is invalid. - Validate(tenancy ConsulTenancyConfig) error - - // DefaultNamespaceFields sets Consul namespace fields on the resource - // spec to their default values if namespaces are enabled. - DefaultNamespaceFields(tenancy ConsulTenancyConfig) - - // Object is required so that MeshConfig implements metav1.Object, which is - // the interface supported by controller-runtime reconcile-able resources. - metav1.Object -} diff --git a/control-plane/api/common/meshconfig_webhook.go b/control-plane/api/common/meshconfig_webhook.go deleted file mode 100644 index 004b47a589..0000000000 --- a/control-plane/api/common/meshconfig_webhook.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/go-logr/logr" - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// MeshConfigLister is implemented by CRD-specific webhooks. -type MeshConfigLister interface { - // List returns all resources of this type across all namespaces in a - // Kubernetes cluster. - List(ctx context.Context) ([]MeshConfig, error) -} - -// ValidateMeshConfig validates a MeshConfig. It is a generic method that -// can be used by all CRD-specific validators. -// Callers should pass themselves as validator and kind should be the custom -// resource name, e.g. "TrafficPermissions". -func ValidateMeshConfig( - ctx context.Context, - req admission.Request, - logger logr.Logger, - meshConfigLister MeshConfigLister, - meshConfig MeshConfig, - tenancy ConsulTenancyConfig) admission.Response { - - defaultingPatches, err := MeshConfigDefaultingPatches(meshConfig, tenancy) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - // On create we need to validate that there isn't already a resource with - // the same name in a different namespace if we're mapping all Kube - // resources to a single Consul namespace. The only case where we're not - // mapping all kube resources to a single Consul namespace is when we - // are running Consul enterprise with namespace mirroring. - singleConsulDestNS := !(tenancy.EnableConsulNamespaces && tenancy.EnableNSMirroring) - if req.Operation == admissionv1.Create && singleConsulDestNS { - logger.Info("validate create", "name", meshConfig.KubernetesName()) - - list, err := meshConfigLister.List(ctx) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - for _, item := range list { - if item.KubernetesName() == meshConfig.KubernetesName() { - return admission.Errored(http.StatusBadRequest, - fmt.Errorf("%s resource with name %q is already defined – all %s resources must have unique names across namespaces", - meshConfig.KubeKind(), - meshConfig.KubernetesName(), - meshConfig.KubeKind())) - } - } - } - if err := meshConfig.Validate(tenancy); err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - return admission.Patched(fmt.Sprintf("valid %s request", meshConfig.KubeKind()), defaultingPatches...) -} - -// MeshConfigDefaultingPatches returns the patches needed to set fields to their defaults. -func MeshConfigDefaultingPatches(meshConfig MeshConfig, tenancy ConsulTenancyConfig) ([]jsonpatch.Operation, error) { - beforeDefaulting, err := json.Marshal(meshConfig) - if err != nil { - return nil, fmt.Errorf("marshalling input: %s", err) - } - meshConfig.DefaultNamespaceFields(tenancy) - afterDefaulting, err := json.Marshal(meshConfig) - if err != nil { - return nil, fmt.Errorf("marshalling after defaulting: %s", err) - } - - defaultingPatches, err := jsonpatch.CreatePatch(beforeDefaulting, afterDefaulting) - if err != nil { - return nil, fmt.Errorf("creating patches: %s", err) - } - return defaultingPatches, nil -} diff --git a/control-plane/api/common/meshconfig_webhook_test.go b/control-plane/api/common/meshconfig_webhook_test.go deleted file mode 100644 index 1da2c143dd..0000000000 --- a/control-plane/api/common/meshconfig_webhook_test.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "context" - "encoding/json" - "errors" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/proto-public/pbresource" - "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" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -func TestValidateMeshConfig(t *testing.T) { - otherNS := "other" - - cases := map[string]struct { - existingResources []MeshConfig - newResource MeshConfig - enableNamespaces bool - nsMirroring bool - consulDestinationNS string - nsMirroringPrefix string - expAllow bool - expErrMessage string - }{ - "no duplicates, valid": { - existingResources: nil, - newResource: &mockMeshConfig{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - expAllow: true, - }, - "no duplicates, invalid": { - existingResources: nil, - newResource: &mockMeshConfig{ - MockName: "foo", - MockNamespace: otherNS, - Valid: false, - }, - expAllow: false, - expErrMessage: "invalid", - }, - "duplicate name": { - existingResources: []MeshConfig{&mockMeshConfig{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockMeshConfig{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - expAllow: false, - expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", - }, - "duplicate name, namespaces enabled": { - existingResources: []MeshConfig{&mockMeshConfig{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockMeshConfig{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - enableNamespaces: true, - expAllow: false, - expErrMessage: "mockkind resource with name \"foo\" is already defined – all mockkind resources must have unique names across namespaces", - }, - "duplicate name, namespaces enabled, mirroring enabled": { - existingResources: []MeshConfig{&mockMeshConfig{ - MockName: "foo", - MockNamespace: "default", - }}, - newResource: &mockMeshConfig{ - MockName: "foo", - MockNamespace: otherNS, - Valid: true, - }, - enableNamespaces: true, - nsMirroring: true, - expAllow: true, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - ctx := context.Background() - marshalledRequestObject, err := json.Marshal(c.newResource) - require.NoError(t, err) - - lister := &mockMeshConfigLister{ - Resources: c.existingResources, - } - response := ValidateMeshConfig(ctx, admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Name: c.newResource.KubernetesName(), - Namespace: otherNS, - Operation: admissionv1.Create, - Object: runtime.RawExtension{ - Raw: marshalledRequestObject, - }, - }, - }, - logrtest.New(t), - lister, - c.newResource, - ConsulTenancyConfig{ - EnableConsulNamespaces: c.enableNamespaces, - ConsulDestinationNamespace: c.consulDestinationNS, - EnableNSMirroring: c.nsMirroring, - NSMirroringPrefix: c.nsMirroringPrefix, - }) - require.Equal(t, c.expAllow, response.Allowed) - if c.expErrMessage != "" { - require.Equal(t, c.expErrMessage, response.AdmissionResponse.Result.Message) - } - }) - } -} - -func TestMeshConfigDefaultingPatches(t *testing.T) { - meshConfig := &mockMeshConfig{ - MockName: "test", - Valid: true, - } - - // This test validates that DefaultingPatches invokes DefaultNamespaceFields on the Config Entry. - patches, err := MeshConfigDefaultingPatches(meshConfig, ConsulTenancyConfig{}) - require.NoError(t, err) - - require.Equal(t, []jsonpatch.Operation{ - { - Operation: "replace", - Path: "/MockNamespace", - Value: "bar", - }, - }, patches) -} - -type mockMeshConfigLister struct { - Resources []MeshConfig -} - -var _ MeshConfigLister = &mockMeshConfigLister{} - -func (in *mockMeshConfigLister) List(_ context.Context) ([]MeshConfig, error) { - return in.Resources, nil -} - -type mockMeshConfig struct { - MockName string - MockNamespace string - Valid bool -} - -var _ MeshConfig = &mockMeshConfig{} - -func (in *mockMeshConfig) ResourceID(_, _ string) *pbresource.ID { - return nil -} - -func (in *mockMeshConfig) Resource(_, _ string) *pbresource.Resource { - return nil -} - -func (in *mockMeshConfig) GetNamespace() string { - return in.MockNamespace -} - -func (in *mockMeshConfig) SetNamespace(namespace string) { - in.MockNamespace = namespace -} - -func (in *mockMeshConfig) GetName() string { - return in.MockName -} - -func (in *mockMeshConfig) SetName(name string) { - in.MockName = name -} - -func (in *mockMeshConfig) GetGenerateName() string { - return "" -} - -func (in *mockMeshConfig) SetGenerateName(_ string) {} - -func (in *mockMeshConfig) GetUID() types.UID { - return "" -} - -func (in *mockMeshConfig) SetUID(_ types.UID) {} - -func (in *mockMeshConfig) GetResourceVersion() string { - return "" -} - -func (in *mockMeshConfig) SetResourceVersion(_ string) {} - -func (in *mockMeshConfig) GetGeneration() int64 { - return 0 -} - -func (in *mockMeshConfig) SetGeneration(_ int64) {} - -func (in *mockMeshConfig) GetSelfLink() string { - return "" -} - -func (in *mockMeshConfig) SetSelfLink(_ string) {} - -func (in *mockMeshConfig) GetCreationTimestamp() metav1.Time { - return metav1.Time{} -} - -func (in *mockMeshConfig) SetCreationTimestamp(_ metav1.Time) {} - -func (in *mockMeshConfig) GetDeletionTimestamp() *metav1.Time { - return nil -} - -func (in *mockMeshConfig) SetDeletionTimestamp(_ *metav1.Time) {} - -func (in *mockMeshConfig) GetDeletionGracePeriodSeconds() *int64 { - return nil -} - -func (in *mockMeshConfig) SetDeletionGracePeriodSeconds(_ *int64) {} - -func (in *mockMeshConfig) GetLabels() map[string]string { - return nil -} - -func (in *mockMeshConfig) SetLabels(_ map[string]string) {} - -func (in *mockMeshConfig) GetAnnotations() map[string]string { - return nil -} - -func (in *mockMeshConfig) SetAnnotations(_ map[string]string) {} - -func (in *mockMeshConfig) GetFinalizers() []string { - return nil -} - -func (in *mockMeshConfig) SetFinalizers(_ []string) {} - -func (in *mockMeshConfig) GetOwnerReferences() []metav1.OwnerReference { - return nil -} - -func (in *mockMeshConfig) SetOwnerReferences(_ []metav1.OwnerReference) {} - -func (in *mockMeshConfig) GetClusterName() string { - return "" -} - -func (in *mockMeshConfig) SetClusterName(_ string) {} - -func (in *mockMeshConfig) GetManagedFields() []metav1.ManagedFieldsEntry { - return nil -} - -func (in *mockMeshConfig) SetManagedFields(_ []metav1.ManagedFieldsEntry) {} - -func (in *mockMeshConfig) KubernetesName() string { - return in.MockName -} - -func (in *mockMeshConfig) GetObjectMeta() metav1.ObjectMeta { - return metav1.ObjectMeta{} -} - -func (in *mockMeshConfig) GetObjectKind() schema.ObjectKind { - return schema.EmptyObjectKind -} - -func (in *mockMeshConfig) DeepCopyObject() runtime.Object { - return in -} - -func (in *mockMeshConfig) AddFinalizer(_ string) {} - -func (in *mockMeshConfig) RemoveFinalizer(_ string) {} - -func (in *mockMeshConfig) Finalizers() []string { - return nil -} - -func (in *mockMeshConfig) KubeKind() string { - return "mockkind" -} - -func (in *mockMeshConfig) SetSyncedCondition(_ corev1.ConditionStatus, _ string, _ string) {} - -func (in *mockMeshConfig) SetLastSyncedTime(_ *metav1.Time) {} - -func (in *mockMeshConfig) SyncedCondition() (status corev1.ConditionStatus, reason string, message string) { - return corev1.ConditionTrue, "", "" -} - -func (in *mockMeshConfig) SyncedConditionStatus() corev1.ConditionStatus { - return corev1.ConditionTrue -} - -func (in *mockMeshConfig) Validate(_ ConsulTenancyConfig) error { - if !in.Valid { - return errors.New("invalid") - } - return nil -} - -func (in *mockMeshConfig) DefaultNamespaceFields(_ ConsulTenancyConfig) { - in.MockNamespace = "bar" -} - -func (in *mockMeshConfig) MatchesConsul(_ *pbresource.Resource, _, _ string) bool { - return false -} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types.go b/control-plane/api/mesh/v2beta1/grpc_route_types.go deleted file mode 100644 index 44a8949e1d..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types.go +++ /dev/null @@ -1,327 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - grpcRouteKubeKind = "grpcroute" -) - -func init() { - MeshSchemeBuilder.Register(&GRPCRoute{}, &GRPCRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// GRPCRoute is the Schema for the GRPC Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="grpc-route" -type GRPCRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.GRPCRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// GRPCRouteList contains a list of GRPCRoute. -type GRPCRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*GRPCRoute `json:"items"` -} - -func (in *GRPCRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.GRPCRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *GRPCRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *GRPCRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *GRPCRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *GRPCRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *GRPCRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *GRPCRoute) KubeKind() string { - return grpcRouteKubeKind -} - -func (in *GRPCRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *GRPCRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *GRPCRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *GRPCRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *GRPCRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *GRPCRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.GRPCRoute - path := field.NewPath("spec") - - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Hostnames) > 0 { - errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - for j, match := range rule.Matches { - ruleMatchPath := rulePath.Child("matches").Index(j) - if match.Method != nil { - switch match.Method.Type { - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, "missing required field")) - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT: - case pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_REGEX: - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("type"), match.Method.Type, fmt.Sprintf("not a supported enum value: %v", match.Method.Type))) - } - if match.Method.Service == "" && match.Method.Method == "" { - errs = append(errs, field.Invalid(ruleMatchPath.Child("method").Child("service"), match.Method.Service, "at least one of \"service\" or \"method\" must be set")) - } - } - - for k, header := range match.Headers { - ruleHeaderPath := ruleMatchPath.Child("headers").Index(k) - if err := validateHeaderMatchType(header.Type); err != nil { - errs = append(errs, field.Invalid(ruleHeaderPath.Child("type"), header.Type, err.Error())) - } - - if header.Name == "" { - errs = append(errs, field.Required(ruleHeaderPath.Child("name"), "missing required field")) - } - } - } - - for j, filter := range rule.Filters { - set := 0 - if filter.RequestHeaderModifier != nil { - set++ - } - if filter.ResponseHeaderModifier != nil { - set++ - } - if filter.UrlRewrite != nil { - set++ - if filter.UrlRewrite.PathPrefix == "" { - errs = append(errs, field.Required(rulePath.Child("filters").Index(j).Child("urlRewrite").Child("pathPrefix"), "field should not be empty if enclosing section is set")) - } - } - if set != 1 { - errs = append(errs, field.Invalid(rulePath.Child("filters").Index(j), filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) - } - } - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - - if len(hbref.Filters) > 0 { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) - } - } - - if rule.Timeouts != nil { - errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) - } - if rule.Retries != nil { - errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.GRPCRoute}, - in.KubernetesName(), errs) - } - return nil -} - -func validateHeaderMatchType(typ pbmesh.HeaderMatchType) error { - switch typ { - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED: - return fmt.Errorf("missing required field") - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_REGEX: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PRESENT: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX: - case pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_SUFFIX: - default: - return fmt.Errorf("not a supported enum value: %v", typ) - } - return nil -} - -func validateHTTPTimeouts(timeouts *pbmesh.HTTPRouteTimeouts, path *field.Path) field.ErrorList { - if timeouts == nil { - return nil - } - - var errs field.ErrorList - - if timeouts.Request != nil { - val := timeouts.Request.AsDuration() - if val < 0 { - errs = append(errs, field.Invalid(path.Child("request"), val, "timeout cannot be negative")) - } - } - if timeouts.Idle != nil { - val := timeouts.Idle.AsDuration() - if val < 0 { - errs = append(errs, field.Invalid(path.Child("idle"), val, "timeout cannot be negative")) - } - } - - return errs -} - -func validateHTTPRetries(retries *pbmesh.HTTPRouteRetries, path *field.Path) field.ErrorList { - if retries == nil { - return nil - } - - var errs field.ErrorList - - for i, condition := range retries.OnConditions { - if !isValidRetryCondition(condition) { - errs = append(errs, field.Invalid(path.Child("onConditions").Index(i), condition, "not a valid retry condition")) - } - } - - return errs -} - -func isValidRetryCondition(retryOn string) bool { - switch retryOn { - case "5xx", - "gateway-error", - "reset", - "connect-failure", - "envoy-ratelimited", - "retriable-4xx", - "refused-stream", - "cancelled", - "deadline-exceeded", - "internal", - "resource-exhausted", - "unavailable": - return true - default: - return false - } -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *GRPCRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go b/control-plane/api/mesh/v2beta1/grpc_route_types_test.go deleted file mode 100644 index 69695284d0..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_types_test.go +++ /dev/null @@ -1,1201 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestGRPCRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *GRPCRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.GRPCRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.GRPCRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.GRPCRoute{}, - Matches: true, - }, - "hostnames are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.GRPCRoute{ - Hostnames: []string{ - "not-a-hostname", "another-hostname", - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.GRPCRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructGRPCRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestGRPCRoute_Resource also includes test to verify ResourceID(). -func TestGRPCRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *GRPCRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.GRPCRoute - }{ - "empty fields": { - Ours: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.GRPCRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.GRPCRoute{}, - }, - "every field set": { - Ours: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.GRPCRoute{ - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructGRPCRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "GRPCRoute do not match") - }) - } -} - -func TestGRPCRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &GRPCRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestGRPCRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &GRPCRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestGRPCRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &GRPCRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestGRPCRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&GRPCRoute{}).GetCondition(ConditionSynced)) -} - -func TestGRPCRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&GRPCRoute{}).SyncedConditionStatus()) -} - -func TestGRPCRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&GRPCRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestGRPCRoute_KubeKind(t *testing.T) { - require.Equal(t, "grpcroute", (&GRPCRoute{}).KubeKind()) -} - -func TestGRPCRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.GRPCRoute{}, - }).KubernetesName()) -} - -func TestGRPCRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &GRPCRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestGRPCRoute_DefaultNamespaceFields(t *testing.T) - -func TestGRPCRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *GRPCRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - Service: "test-service", - Method: "GET", - }, - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - }, - }, - Filters: []*pbmesh.GRPCRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "5xx", "resource-exhausted", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "empty parentRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "populated hostnames", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{"a-hostname"}, - }, - }, - expectedErrMsgs: []string{ - `spec.hostnames: Invalid value: []string{"a-hostname"}: should not populate hostnames`, - }, - }, - { - name: "rules.matches.method", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_UNSPECIFIED, - Service: "test-service", - Method: "GET", - }, - }, { - Method: &pbmesh.GRPCMethodMatch{ - Service: "test-service", - Method: "GET", - }, - }, { - Method: &pbmesh.GRPCMethodMatch{ - Type: pbmesh.GRPCMethodMatchType_GRPC_METHOD_MATCH_TYPE_EXACT, - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[1].method.type: Invalid value: GRPC_METHOD_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[2].method.service: Invalid value: "": at least one of "service" or "method" must be set`, - }, - }, - { - name: "rules.matches.headers", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Matches: []*pbmesh.GRPCRouteMatch{ - { - Headers: []*pbmesh.GRPCHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, - Name: "test-header", - Value: "header-value", - }, - { - Name: "test-header", - Value: "header-value", - }, - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Value: "header-value", - }, - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, - }, - }, - { - name: "rules.filters", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Filters: []*pbmesh.GRPCRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "", - }, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].filters[0].urlRewrite.pathPrefix: Required value: field should not be empty if enclosing section is set`, - `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, - }, - }, - { - name: "missing backendRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - BackendRefs: []*pbmesh.GRPCBackendRef{}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: missing required field`, - }, - }, - { - name: "rules.backendRefs", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - Weight: 50, - }, - { - BackendRef: &pbmesh.BackendReference{ - Datacenter: "wrong-datacenter", - Port: "21000", - }, - Weight: 50, - }, - { - BackendRef: &pbmesh.BackendReference{ - Port: "21000", - }, - Filters: []*pbmesh.GRPCRouteFilter{{}}, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "wrong-datacenter": datacenter is not yet supported on backend refs`, - `filters are not supported at this level yet`, - }, - }, - { - name: "rules.timeout", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: -9, - Nanos: -10, - }, - Idle: &durationpb.Duration{ - Seconds: -2, - Nanos: -3, - }, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].timeouts.request: Invalid value: -9.00000001s: timeout cannot be negative`, - `spec.rules[0].timeouts.idle: Invalid value: -2.000000003s: timeout cannot be negative`, - }, - }, - { - name: "rules.retries", - input: &GRPCRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.GRPCRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "20020", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.GRPCRouteRule{ - { - Retries: &pbmesh.HTTPRouteRetries{ - OnConditions: []string{"invalid-condition", "another-invalid-condition", "internal"}, - }, - BackendRefs: []*pbmesh.GRPCBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "reference", - Section: "some-section", - }, - Port: "21000", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, - `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructGRPCRouteResource(tp *pbmesh.GRPCRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.GRPCRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go b/control-plane/api/mesh/v2beta1/grpc_route_webhook.go deleted file mode 100644 index 15b4cbfc46..0000000000 --- a/control-plane/api/mesh/v2beta1/grpc_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type GRPCRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.MeshConfigLister = &GRPCRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-grpcroute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=grpcroute,versions=v2beta1,name=mutate-grpcroute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *GRPCRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource GRPCRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *GRPCRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { - var resourceList GRPCRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.MeshConfig - for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) - } - return entries, nil -} - -func (v *GRPCRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/http_route_types.go b/control-plane/api/mesh/v2beta1/http_route_types.go deleted file mode 100644 index 7ea1b97936..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types.go +++ /dev/null @@ -1,309 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - "net/http" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - httpRouteKubeKind = "httproute" -) - -func init() { - MeshSchemeBuilder.Register(&HTTPRoute{}, &HTTPRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// HTTPRoute is the Schema for the HTTP Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="http-route" -type HTTPRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.HTTPRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// HTTPRouteList contains a list of HTTPRoute. -type HTTPRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*HTTPRoute `json:"items"` -} - -func (in *HTTPRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.HTTPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *HTTPRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *HTTPRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *HTTPRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *HTTPRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *HTTPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *HTTPRoute) KubeKind() string { - return httpRouteKubeKind -} - -func (in *HTTPRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *HTTPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *HTTPRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *HTTPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *HTTPRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *HTTPRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.HTTPRoute - path := field.NewPath("spec") - - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Hostnames) > 0 { - errs = append(errs, field.Invalid(path.Child("hostnames"), route.Hostnames, "should not populate hostnames")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - for j, match := range rule.Matches { - ruleMatchPath := rulePath.Child("matches").Index(j) - if match.Path != nil { - switch match.Path.Type { - case pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, "missing required field")) - case pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT: - if !strings.HasPrefix(match.Path.Value, "/") { - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "exact patch value does not start with '/'")) - } - case pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX: - if !strings.HasPrefix(match.Path.Value, "/") { - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("value"), match.Path.Value, "prefix patch value does not start with '/'")) - } - case pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX: - if match.Path.Value == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("path").Child("value"), "missing required field")) - } - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("path").Child("type"), match.Path, "not a supported enum value")) - } - } - - for k, hdr := range match.Headers { - if err := validateHeaderMatchType(hdr.Type); err != nil { - errs = append(errs, field.Invalid(ruleMatchPath.Child("headers").Index(k).Child("type"), hdr.Type, err.Error())) - } - - if hdr.Name == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("headers").Index(k).Child("name"), "missing required field")) - } - } - - for k, qm := range match.QueryParams { - switch qm.Type { - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED: - errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_UNSPECIFIED, "missing required field")) - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_EXACT: - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_REGEX: - case pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT: - default: - errs = append(errs, field.Invalid(ruleMatchPath.Child("queryParams").Index(k).Child("type"), qm.Type, "not a supported enum value")) - } - - if qm.Name == "" { - errs = append(errs, field.Required(ruleMatchPath.Child("queryParams").Index(k).Child("name"), "missing required field")) - } - } - - if match.Method != "" && !isValidHTTPMethod(match.Method) { - errs = append(errs, field.Invalid(ruleMatchPath.Child("method"), match.Method, "not a valid http method")) - } - } - - var ( - hasReqMod bool - hasUrlRewrite bool - ) - for j, filter := range rule.Filters { - ruleFilterPath := path.Child("filters").Index(j) - set := 0 - if filter.RequestHeaderModifier != nil { - set++ - hasReqMod = true - } - if filter.ResponseHeaderModifier != nil { - set++ - } - if filter.UrlRewrite != nil { - set++ - hasUrlRewrite = true - if filter.UrlRewrite.PathPrefix == "" { - errs = append(errs, field.Invalid(ruleFilterPath.Child("urlRewrite").Child("pathPrefix"), filter.UrlRewrite.PathPrefix, "field should not be empty if enclosing section is set")) - } - } - if set != 1 { - errs = append(errs, field.Invalid(ruleFilterPath, filter, "exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required")) - } - } - - if hasReqMod && hasUrlRewrite { - errs = append(errs, field.Invalid(rulePath.Child("filters"), rule.Filters, "exactly one of request_header_modifier or url_rewrite can be set at a time")) - } - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "missing required field")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - - if len(hbref.Filters) > 0 { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("filters"), hbref.Filters, "filters are not supported at this level yet")) - } - } - - if rule.Timeouts != nil { - errs = append(errs, validateHTTPTimeouts(rule.Timeouts, rulePath.Child("timeouts"))...) - } - if rule.Retries != nil { - errs = append(errs, validateHTTPRetries(rule.Retries, rulePath.Child("retries"))...) - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.HTTPRoute}, - in.KubernetesName(), errs) - } - return nil -} - -func isValidHTTPMethod(method string) bool { - switch method { - case http.MethodGet, - http.MethodHead, - http.MethodPost, - http.MethodPut, - http.MethodPatch, - http.MethodDelete, - http.MethodConnect, - http.MethodOptions, - http.MethodTrace: - return true - default: - return false - } -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *HTTPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/http_route_types_test.go b/control-plane/api/mesh/v2beta1/http_route_types_test.go deleted file mode 100644 index 824d7e35b7..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_types_test.go +++ /dev/null @@ -1,1338 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/timestamppb" - "google.golang.org/protobuf/types/known/wrapperspb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestHTTPRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *HTTPRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.HTTPRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.HTTPRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.HTTPRoute{}, - Matches: true, - }, - "hostnames are compared": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.HTTPRoute{ - Hostnames: []string{ - "not-a-hostname", "another-hostname", - }, - }, - Matches: false, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20211", - Datacenter: "another-datacenter", - }, - Weight: 12, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "adding", - }, - }, - Remove: []string{"removing"}, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "another-set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "another-added-header", - Value: "adding", - }, - }, - Remove: []string{"also-removing"}, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixing-it", - }, - }, - }, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20211", - Datacenter: "another-datacenter", - }, - Weight: 12, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "adding", - }, - }, - Remove: []string{"removing"}, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "another-set-header", - Value: "setting", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "another-added-header", - Value: "adding", - }, - }, - Remove: []string{"also-removing"}, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixing-it", - }, - }, - }, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.HTTPRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructHTTPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestHTTPRoute_Resource also includes test to verify ResourceID(). -func TestHTTPRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *HTTPRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.HTTPRoute - }{ - "empty fields": { - Ours: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.HTTPRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.HTTPRoute{}, - }, - "every field set": { - Ours: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{ - "a-hostname", "another-hostname", - }, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "exact-value", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{ - Set: []*pbmesh.HTTPHeader{ - { - Name: "set-header", - Value: "a-header-value", - }, - }, - Add: []*pbmesh.HTTPHeader{ - { - Name: "added-header", - Value: "another-header-value", - }, - }, - Remove: []string{ - "remove-header", - }, - }, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "condition-one", "condition-two", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructHTTPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "HTTPRoute do not match") - }) - } -} - -func TestHTTPRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &HTTPRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestHTTPRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &HTTPRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestHTTPRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &HTTPRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestHTTPRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&HTTPRoute{}).GetCondition(ConditionSynced)) -} - -func TestHTTPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&HTTPRoute{}).SyncedConditionStatus()) -} - -func TestHTTPRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&HTTPRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestHTTPRoute_KubeKind(t *testing.T) { - require.Equal(t, "httproute", (&HTTPRoute{}).KubeKind()) -} - -func TestHTTPRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.HTTPRoute{}, - }).KubernetesName()) -} - -func TestHTTPRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &HTTPRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestHTTPRoute_DefaultNamespaceFields(t *testing.T) - -func TestHTTPRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *HTTPRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "/exactValue", - }, - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_PREFIX, - Name: "test-header", - Value: "header-value", - }, - }, - QueryParams: []*pbmesh.HTTPQueryParamMatch{ - { - Type: pbmesh.QueryParamMatchType_QUERY_PARAM_MATCH_TYPE_PRESENT, - Name: "query-param-name", - Value: "query-value", - }, - }, - Method: "GET", - }, - }, - Filters: []*pbmesh.HTTPRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "a-path-prefix", - }, - }, - }, - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: 10, - Nanos: 5, - }, - Idle: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - }, - Retries: &pbmesh.HTTPRouteRetries{ - Number: &wrapperspb.UInt32Value{ - Value: 1, - }, - OnConnectFailure: false, - OnConditions: []string{ - "reset", "cancelled", - }, - OnStatusCodes: []uint32{ - 200, 201, 202, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "backend", - Section: "backend-section", - }, - Port: "20101", - }, - Weight: 15, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "missing parentRefs", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "hostnames created", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{"a-hostname", "another-hostname"}, - }, - }, - expectedErrMsgs: []string{ - `spec.hostnames: Invalid value: []string{"a-hostname", "another-hostname"}: should not populate hostnames`, - }, - }, - { - name: "rules.matches.path", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_UNSPECIFIED, - }, - }, - { - Path: &pbmesh.HTTPPathMatch{}, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_EXACT, - Value: "does-not-have-/-prefix", - }, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, - Value: "does-not-have-/-prefix-either", - }, - }, - { - Path: &pbmesh.HTTPPathMatch{ - Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_REGEX, - Value: "", - }, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[1].path.type: Invalid value: PATH_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[2].path.value: Invalid value: "does-not-have-/-prefix": exact patch value does not start with '/'`, - `spec.rules[0].matches[3].path.value: Invalid value: "does-not-have-/-prefix-either": prefix patch value does not start with '/'`, - `spec.rules[0].matches[4].path.value: Required value: missing required field`, - }, - }, - { - name: "rules.matches.headers", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Matches: []*pbmesh.HTTPRouteMatch{ - { - Headers: []*pbmesh.HTTPHeaderMatch{ - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_UNSPECIFIED, - Name: "test-header", - Value: "header-value", - }, - { - // Type: "", - Name: "test-header", - Value: "header-value", - }, - { - Type: pbmesh.HeaderMatchType_HEADER_MATCH_TYPE_EXACT, - Name: "", - }, - }, - Method: "GET", - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].matches[0].headers[0].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[1].type: Invalid value: HEADER_MATCH_TYPE_UNSPECIFIED: missing required field`, - `spec.rules[0].matches[0].headers[2].name: Required value: missing required field`, - }, - }, - { - name: "rules.filters", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Filters: []*pbmesh.HTTPRouteFilter{ - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - }, - { - RequestHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "prefix-1", - }, - }, - { - ResponseHeaderModifier: &pbmesh.HTTPHeaderFilter{}, - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "prefix-2", - }, - }, - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "", - }, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.filters[0]: Invalid value`, - `spec.filters[1]: Invalid value`, - `spec.filters[2]: Invalid value`, - `spec.filters[3].urlRewrite.pathPrefix: Invalid value: "": field should not be empty if enclosing section is set`, - `exactly one of request_header_modifier, response_header_modifier, or url_rewrite is required`, - }, - }, - { - name: "rule.backendRefs", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - BackendRefs: []*pbmesh.HTTPBackendRef{}, - }, - { - BackendRefs: []*pbmesh.HTTPBackendRef{ - {}, - { - BackendRef: &pbmesh.BackendReference{ - Datacenter: "some-datacenter", - }, - }, - { - BackendRef: &pbmesh.BackendReference{}, - Filters: []*pbmesh.HTTPRouteFilter{ - { - UrlRewrite: &pbmesh.HTTPURLRewriteFilter{ - PathPrefix: "/prefixed", - }, - }, - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: missing required field`, - `spec.rules[1].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[1].backendRefs[1].backendRef.datacenter: Invalid value: "some-datacenter": datacenter is not yet supported on backend refs`, - `spec.rules[1].backendRefs[2].filters: Invalid value`, - `filters are not supported at this level yet`, - }, - }, - { - name: "rules.timeouts", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Timeouts: &pbmesh.HTTPRouteTimeouts{ - Request: &durationpb.Duration{ - Seconds: -10, - Nanos: -5, - }, - Idle: &durationpb.Duration{ - Seconds: -5, - Nanos: -10, - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].timeouts.request: Invalid value: -10.000000005s: timeout cannot be negative`, - `spec.rules[0].timeouts.idle: Invalid value: -5.00000001s: timeout cannot be negative`, - }, - }, - { - name: "rules.timeouts", - input: &HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.HTTPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "a-partition", - Namespace: "a-namespace", - }, - Name: "reference-name", - Section: "section-name", - }, - Port: "20201", - }, - }, - Hostnames: []string{}, - Rules: []*pbmesh.HTTPRouteRule{ - { - Retries: &pbmesh.HTTPRouteRetries{ - OnConditions: []string{ - "invalid-condition", "another-invalid-condition", - }, - }, - BackendRefs: []*pbmesh.HTTPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].retries.onConditions[0]: Invalid value: "invalid-condition": not a valid retry condition`, - `spec.rules[0].retries.onConditions[1]: Invalid value: "another-invalid-condition": not a valid retry condition`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructHTTPRouteResource(tp *pbmesh.HTTPRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.HTTPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/http_route_webhook.go b/control-plane/api/mesh/v2beta1/http_route_webhook.go deleted file mode 100644 index e16db17458..0000000000 --- a/control-plane/api/mesh/v2beta1/http_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type HTTPRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.MeshConfigLister = &HTTPRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-httproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=httproute,versions=v2beta1,name=mutate-httproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *HTTPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource HTTPRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *HTTPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { - var resourceList HTTPRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.MeshConfig - for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) - } - return entries, nil -} - -func (v *HTTPRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go b/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go deleted file mode 100644 index a9fe6a6a83..0000000000 --- a/control-plane/api/mesh/v2beta1/mesh_groupversion_info.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Package v2beta1 contains API Schema definitions for the consul.hashicorp.com v2beta1 API group -// +kubebuilder:object:generate=true -// +groupName=mesh.consul.hashicorp.com -package v2beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - - // MeshGroup is a collection of mesh resources. - MeshGroup = "mesh.consul.hashicorp.com" - - // MeshGroupVersion is group version used to register these objects. - MeshGroupVersion = schema.GroupVersion{Group: MeshGroup, Version: "v2beta1"} - - // MeshSchemeBuilder is used to add go types to the GroupVersionKind scheme. - MeshSchemeBuilder = &scheme.Builder{GroupVersion: MeshGroupVersion} - - // AddMeshToScheme adds the types in this group-version to the given scheme. - AddMeshToScheme = MeshSchemeBuilder.AddToScheme -) diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go b/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go deleted file mode 100644 index 711d8c81bc..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type ProxyConfigurationWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.MeshConfigLister = &ProxyConfigurationWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-proxyconfiguration,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=proxyconfiguration,versions=v2beta1,name=mutate-proxyconfiguration.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *ProxyConfigurationWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource ProxyConfiguration - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *ProxyConfigurationWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { - var resourceList ProxyConfigurationList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.MeshConfig - for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) - } - return entries, nil -} - -func (v *ProxyConfigurationWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types.go deleted file mode 100644 index 4df6ff95e1..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types.go +++ /dev/null @@ -1,160 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - proxyConfigurationKubeKind = "proxyconfiguration" -) - -func init() { - MeshSchemeBuilder.Register(&ProxyConfiguration{}, &ProxyConfigurationList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// ProxyConfiguration is the Schema for the TCP Routes API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="proxy-configuration" -type ProxyConfiguration struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.ProxyConfiguration `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// ProxyConfigurationList contains a list of ProxyConfiguration. -type ProxyConfigurationList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*ProxyConfiguration `json:"items"` -} - -func (in *ProxyConfiguration) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *ProxyConfiguration) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *ProxyConfiguration) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *ProxyConfiguration) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *ProxyConfiguration) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *ProxyConfiguration) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *ProxyConfiguration) KubeKind() string { - return proxyConfigurationKubeKind -} - -func (in *ProxyConfiguration) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *ProxyConfiguration) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *ProxyConfiguration) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *ProxyConfiguration) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *ProxyConfiguration) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *ProxyConfiguration) Validate(_ common.ConsulTenancyConfig) error { - var errs field.ErrorList - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.ProxyConfiguration}, - in.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *ProxyConfiguration) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go b/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go deleted file mode 100644 index 5e432175f4..0000000000 --- a/control-plane/api/mesh/v2beta1/proxy_configuration_types_test.go +++ /dev/null @@ -1,608 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/durationpb" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestProxyConfiguration_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *ProxyConfiguration - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.ProxyConfiguration - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.ProxyConfiguration{}, - Matches: true, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructProxyConfigurationResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestProxyConfiguration_Resource also includes test to verify ResourceID(). -func TestProxyConfiguration_Resource(t *testing.T) { - cases := map[string]struct { - Ours *ProxyConfiguration - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.ProxyConfiguration - }{ - "empty fields": { - Ours: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.ProxyConfiguration{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.ProxyConfiguration{}, - }, - "every field set": { - Ours: &ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"prefix-1", "prefix-2"}, - Names: []string{"workload-name"}, - Filter: "first-filter", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: 2, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 1234, - DialedDirectly: true, - }, - MutualTlsMode: 1, - LocalConnection: map[string]*pbmesh.ConnectionConfig{ - "local": { - ConnectTimeout: &durationpb.Duration{ - Seconds: 5, - Nanos: 10, - }, - RequestTimeout: &durationpb.Duration{ - Seconds: 2, - Nanos: 10, - }, - }, - }, - InboundConnections: &pbmesh.InboundConnectionsConfig{ - MaxInboundConnections: 5, - BalanceInboundConnections: 10, - }, - MeshGatewayMode: pbmesh.MeshGatewayMode_MESH_GATEWAY_MODE_LOCAL, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 19000, - Path: "/expose-path", - LocalPathPort: 1901, - Protocol: 2, - }, - }, - }, - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - DisableListenerLogs: true, - Type: 3, - Path: "/path", - JsonFormat: "jsonFormat", - TextFormat: "text format.", - }, - EnvoyExtensions: []*pbmesh.EnvoyExtension{ - { - Name: "extension-1", - Required: true, - Arguments: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "field-1": {}, - "field-2": {}, - }, - }, - ConsulVersion: "1.22.3-beta1", - EnvoyVersion: "1.33.4", - }, - }, - PublicListenerJson: "publicListenerJson{}", - ListenerTracingJson: "listenerTracingJson{}", - LocalClusterJson: "localClusterJson{}", - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsdUrl: "statsdURL", - DogstatsdUrl: "dogstatsdURL", - StatsTags: []string{"statsTags"}, - PrometheusBindAddr: "firstBindAddr", - StatsBindAddr: "secondBindAddr", - ReadyBindAddr: "thirdBindAddr", - OverrideJsonTpl: "overrideJSON", - StaticClustersJson: "staticClusterJSON", - StaticListenersJson: "staticListenersJSON", - StatsSinksJson: "statsSinksJSON", - StatsConfigJson: "statsConfigJSON", - StatsFlushInterval: "45s", - TracingConfigJson: "tracingConfigJSON", - TelemetryCollectorBindSocketDir: "/bindSocketDir", - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructProxyConfigurationResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "ProxyConfiguration do not match") - }) - } -} - -func TestProxyConfiguration_SetSyncedCondition(t *testing.T) { - trafficPermissions := &ProxyConfiguration{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestProxyConfiguration_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &ProxyConfiguration{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestProxyConfiguration_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &ProxyConfiguration{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestProxyConfiguration_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&ProxyConfiguration{}).GetCondition(ConditionSynced)) -} - -func TestProxyConfiguration_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&ProxyConfiguration{}).SyncedConditionStatus()) -} - -func TestProxyConfiguration_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&ProxyConfiguration{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestProxyConfiguration_KubeKind(t *testing.T) { - require.Equal(t, "proxyconfiguration", (&ProxyConfiguration{}).KubeKind()) -} - -func TestProxyConfiguration_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&ProxyConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.ProxyConfiguration{}, - }).KubernetesName()) -} - -func TestProxyConfiguration_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &ProxyConfiguration{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestProxyConfiguration_DefaultNamespaceFields(t *testing.T) - -func constructProxyConfigurationResource(tp *pbmesh.ProxyConfiguration, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/shared_types.go b/control-plane/api/mesh/v2beta1/shared_types.go deleted file mode 100644 index a5225afb71..0000000000 --- a/control-plane/api/mesh/v2beta1/shared_types.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -func meshConfigMeta() map[string]string { - return map[string]string{ - common.SourceKey: common.SourceValue, - } -} diff --git a/control-plane/api/mesh/v2beta1/status.go b/control-plane/api/mesh/v2beta1/status.go deleted file mode 100644 index cc75a1cd82..0000000000 --- a/control-plane/api/mesh/v2beta1/status.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// Conditions is the schema for the conditions portion of the payload. -type Conditions []Condition - -// ConditionType is a camel-cased condition type. -type ConditionType string - -const ( - // ConditionSynced specifies that the resource has been synced with Consul. - ConditionSynced ConditionType = "Synced" -) - -// Conditions define a readiness condition for a Consul resource. -// See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Condition struct { - // Type of condition. - // +required - Type ConditionType `json:"type" description:"type of status condition"` - - // Status of the condition, one of True, False, Unknown. - // +required - Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"` - - // LastTransitionTime is the last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" description:"last time the condition transitioned from one status to another"` - - // The reason for the condition's last transition. - // +optional - Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"` - - // A human readable message indicating details about the transition. - // +optional - Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"` -} - -// IsTrue is true if the condition is True. -func (c *Condition) IsTrue() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionTrue -} - -// IsFalse is true if the condition is False. -func (c *Condition) IsFalse() bool { - if c == nil { - return false - } - return c.Status == corev1.ConditionFalse -} - -// IsUnknown is true if the condition is Unknown. -func (c *Condition) IsUnknown() bool { - if c == nil { - return true - } - return c.Status == corev1.ConditionUnknown -} - -// +k8s:deepcopy-gen=true -// +k8s:openapi-gen=true -type Status struct { - // Conditions indicate the latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` - - // LastSyncedTime is the last time the resource successfully synced with Consul. - // +optional - LastSyncedTime *metav1.Time `json:"lastSyncedTime,omitempty" description:"last time the condition transitioned from one status to another"` -} - -func (s *Status) GetCondition(t ConditionType) *Condition { - for _, cond := range s.Conditions { - if cond.Type == t { - return &cond - } - } - return nil -} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types.go b/control-plane/api/mesh/v2beta1/tcp_route_types.go deleted file mode 100644 index 207ef7afb3..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types.go +++ /dev/null @@ -1,195 +0,0 @@ -// // Copyright (c) HashiCorp, Inc. -// // SPDX-License-Identifier: MPL-2.0 -package v2beta1 - -import ( - "fmt" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - tcpRouteKubeKind = "tcproute" -) - -func init() { - MeshSchemeBuilder.Register(&TCPRoute{}, &TCPRouteList{}) -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// TCPRoute is the Schema for the TCP Route API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -// +kubebuilder:resource:shortName="tcp-route" -type TCPRoute struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec pbmesh.TCPRoute `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TCPRouteList contains a list of TCPRoute. -type TCPRouteList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []*TCPRoute `json:"items"` -} - -func (in *TCPRoute) ResourceID(namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: in.Name, - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func (in *TCPRoute) Resource(namespace, partition string) *pbresource.Resource { - return &pbresource.Resource{ - Id: in.ResourceID(namespace, partition), - Data: inject.ToProtoAny(&in.Spec), - Metadata: meshConfigMeta(), - } -} - -func (in *TCPRoute) AddFinalizer(f string) { - in.ObjectMeta.Finalizers = append(in.Finalizers(), f) -} - -func (in *TCPRoute) RemoveFinalizer(f string) { - var newFinalizers []string - for _, oldF := range in.Finalizers() { - if oldF != f { - newFinalizers = append(newFinalizers, oldF) - } - } - in.ObjectMeta.Finalizers = newFinalizers -} - -func (in *TCPRoute) Finalizers() []string { - return in.ObjectMeta.Finalizers -} - -func (in *TCPRoute) MatchesConsul(candidate *pbresource.Resource, namespace, partition string) bool { - return cmp.Equal( - in.Resource(namespace, partition), - candidate, - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - ) -} - -func (in *TCPRoute) KubeKind() string { - return tcpRouteKubeKind -} - -func (in *TCPRoute) KubernetesName() string { - return in.ObjectMeta.Name -} - -func (in *TCPRoute) SetSyncedCondition(status corev1.ConditionStatus, reason, message string) { - in.Status.Conditions = Conditions{ - { - Type: ConditionSynced, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } -} - -func (in *TCPRoute) SetLastSyncedTime(time *metav1.Time) { - in.Status.LastSyncedTime = time -} - -func (in *TCPRoute) SyncedCondition() (status corev1.ConditionStatus, reason, message string) { - cond := in.Status.GetCondition(ConditionSynced) - if cond == nil { - return corev1.ConditionUnknown, "", "" - } - return cond.Status, cond.Reason, cond.Message -} - -func (in *TCPRoute) SyncedConditionStatus() corev1.ConditionStatus { - condition := in.Status.GetCondition(ConditionSynced) - if condition == nil { - return corev1.ConditionUnknown - } - return condition.Status -} - -func (in *TCPRoute) Validate(tenancy common.ConsulTenancyConfig) error { - var errs field.ErrorList - var route pbmesh.TCPRoute - path := field.NewPath("spec") - res := in.Resource(tenancy.ConsulDestinationNamespace, tenancy.ConsulPartition) - - if err := res.Data.UnmarshalTo(&route); err != nil { - return fmt.Errorf("error parsing resource data as type %q: %s", &route, err) - } - - if len(route.ParentRefs) == 0 { - errs = append(errs, field.Required(path.Child("parentRefs"), "cannot be empty")) - } - - if len(route.Rules) > 1 { - errs = append(errs, field.Invalid(path.Child("rules"), route.Rules, "must only specify a single rule for now")) - } - - for i, rule := range route.Rules { - rulePath := path.Child("rules").Index(i) - - if len(rule.BackendRefs) == 0 { - errs = append(errs, field.Required(rulePath.Child("backendRefs"), "cannot be empty")) - } - for j, hbref := range rule.BackendRefs { - ruleBackendRefsPath := rulePath.Child("backendRefs").Index(j) - if hbref.BackendRef == nil { - errs = append(errs, field.Required(ruleBackendRefsPath.Child("backendRef"), "missing required field")) - continue - } - - if hbref.BackendRef.Datacenter != "" { - errs = append(errs, field.Invalid(ruleBackendRefsPath.Child("backendRef").Child("datacenter"), hbref.BackendRef.Datacenter, "datacenter is not yet supported on backend refs")) - } - } - } - - if len(errs) > 0 { - return apierrors.NewInvalid( - schema.GroupKind{Group: MeshGroup, Kind: common.TCPRoute}, - in.KubernetesName(), errs) - } - return nil -} - -// DefaultNamespaceFields is required as part of the common.MeshConfig interface. -func (in *TCPRoute) DefaultNamespaceFields(tenancy common.ConsulTenancyConfig) {} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go b/control-plane/api/mesh/v2beta1/tcp_route_types_test.go deleted file mode 100644 index 9bedc58293..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_types_test.go +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "testing" - "time" - - "github.com/google/go-cmp/cmp" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/timestamppb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestTCPRoute_MatchesConsul(t *testing.T) { - cases := map[string]struct { - OurConsulNamespace string - OurConsulPartition string - OurData *TCPRoute - - TheirName string - TheirConsulNamespace string - TheirConsulPartition string - TheirData *pbmesh.TCPRoute - ResourceOverride *pbresource.Resource // Used to test that an empty resource of another type will not match - - Matches bool - }{ - "empty fields matches": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.TCPRoute{}, - }, - TheirName: "name", - TheirConsulNamespace: constants.DefaultConsulNS, - TheirConsulPartition: constants.DefaultConsulPartition, - TheirData: &pbmesh.TCPRoute{}, - Matches: true, - }, - "all fields set matches": { - OurConsulNamespace: "consul-ns", - OurConsulPartition: "consul-partition", - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - TheirName: "foo", - TheirConsulNamespace: "consul-ns", - TheirConsulPartition: "consul-partition", - TheirData: &pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - Matches: true, - }, - "different types does not match": { - OurConsulNamespace: constants.DefaultConsulNS, - OurConsulPartition: constants.DefaultConsulPartition, - OurData: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: pbmesh.TCPRoute{}, - }, - ResourceOverride: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "name", - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulNS, - Namespace: constants.DefaultConsulPartition, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: inject.ToProtoAny(&pbmesh.ProxyConfiguration{}), - Metadata: meshConfigMeta(), - }, - Matches: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - consulResource := c.ResourceOverride - if c.TheirName != "" { - consulResource = constructTCPRouteResource(c.TheirData, c.TheirName, c.TheirConsulNamespace, c.TheirConsulPartition) - } - require.Equal(t, c.Matches, c.OurData.MatchesConsul(consulResource, c.OurConsulNamespace, c.OurConsulPartition)) - }) - } -} - -// TestTCPRoute_Resource also includes test to verify ResourceID(). -func TestTCPRoute_Resource(t *testing.T) { - cases := map[string]struct { - Ours *TCPRoute - ConsulNamespace string - ConsulPartition string - ExpectedName string - ExpectedData *pbmesh.TCPRoute - }{ - "empty fields": { - Ours: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: pbmesh.TCPRoute{}, - }, - ConsulNamespace: constants.DefaultConsulNS, - ConsulPartition: constants.DefaultConsulPartition, - ExpectedName: "foo", - ExpectedData: &pbmesh.TCPRoute{}, - }, - "every field set": { - Ours: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - ConsulNamespace: "not-default-namespace", - ConsulPartition: "not-default-partition", - ExpectedName: "foo", - ExpectedData: &pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - Datacenter: "different-datacenter", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - actual := c.Ours.Resource(c.ConsulNamespace, c.ConsulPartition) - expected := constructTCPRouteResource(c.ExpectedData, c.ExpectedName, c.ConsulNamespace, c.ConsulPartition) - - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - }, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "TCPRoute do not match") - }) - } -} - -func TestTCPRoute_SetSyncedCondition(t *testing.T) { - trafficPermissions := &TCPRoute{} - trafficPermissions.SetSyncedCondition(corev1.ConditionTrue, "reason", "message") - - require.Equal(t, corev1.ConditionTrue, trafficPermissions.Status.Conditions[0].Status) - require.Equal(t, "reason", trafficPermissions.Status.Conditions[0].Reason) - require.Equal(t, "message", trafficPermissions.Status.Conditions[0].Message) - now := metav1.Now() - require.True(t, trafficPermissions.Status.Conditions[0].LastTransitionTime.Before(&now)) -} - -func TestTCPRoute_SetLastSyncedTime(t *testing.T) { - trafficPermissions := &TCPRoute{} - syncedTime := metav1.NewTime(time.Now()) - trafficPermissions.SetLastSyncedTime(&syncedTime) - - require.Equal(t, &syncedTime, trafficPermissions.Status.LastSyncedTime) -} - -func TestTCPRoute_GetSyncedConditionStatus(t *testing.T) { - cases := []corev1.ConditionStatus{ - corev1.ConditionUnknown, - corev1.ConditionFalse, - corev1.ConditionTrue, - } - for _, status := range cases { - t.Run(string(status), func(t *testing.T) { - trafficPermissions := &TCPRoute{ - Status: Status{ - Conditions: []Condition{{ - Type: ConditionSynced, - Status: status, - }}, - }, - } - - require.Equal(t, status, trafficPermissions.SyncedConditionStatus()) - }) - } -} - -func TestTCPRoute_GetConditionWhenStatusNil(t *testing.T) { - require.Nil(t, (&TCPRoute{}).GetCondition(ConditionSynced)) -} - -func TestTCPRoute_SyncedConditionStatusWhenStatusNil(t *testing.T) { - require.Equal(t, corev1.ConditionUnknown, (&TCPRoute{}).SyncedConditionStatus()) -} - -func TestTCPRoute_SyncedConditionWhenStatusNil(t *testing.T) { - status, reason, message := (&TCPRoute{}).SyncedCondition() - require.Equal(t, corev1.ConditionUnknown, status) - require.Equal(t, "", reason) - require.Equal(t, "", message) -} - -func TestTCPRoute_KubeKind(t *testing.T) { - require.Equal(t, "tcproute", (&TCPRoute{}).KubeKind()) -} - -func TestTCPRoute_KubernetesName(t *testing.T) { - require.Equal(t, "test", (&TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "bar", - }, - Spec: pbmesh.TCPRoute{}, - }).KubernetesName()) -} - -func TestTCPRoute_ObjectMeta(t *testing.T) { - meta := metav1.ObjectMeta{ - Name: "name", - Namespace: "namespace", - } - trafficPermissions := &TCPRoute{ - ObjectMeta: meta, - } - require.Equal(t, &meta, trafficPermissions.GetObjectMeta()) -} - -// Test defaulting behavior when namespaces are enabled as well as disabled. -// TODO: add when implemented -//func TestTCPRoute_DefaultNamespaceFields(t *testing.T) - -func TestTCPRoute_Validate(t *testing.T) { - cases := []struct { - name string - input *TCPRoute - expectedErrMsgs []string - }{ - { - name: "kitchen sink OK", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{ - { - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Partition: "some-partition", - Namespace: "some-namespace", - }, - Name: "parent-name", - Section: "parent-section", - }, - Port: "20122", - }, - }, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - Tenancy: &pbresource.Tenancy{ - Namespace: "another-namespace", - PeerName: "another-peer", - }, - Name: "backend-name", - Section: "backend-section", - }, - Port: "20111", - }, - Weight: 50, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: nil, - }, - { - name: "no parentRefs", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{}, - }, - }, - expectedErrMsgs: []string{ - `spec.parentRefs: Required value: cannot be empty`, - }, - }, - { - name: "multiple rules", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, - {BackendRefs: []*pbmesh.TCPBackendRef{{BackendRef: &pbmesh.BackendReference{}}}}, - }, - }, - }, - expectedErrMsgs: []string{ - `must only specify a single rule for now`, - }, - }, - { - name: "rules.backendRefs", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - {BackendRefs: []*pbmesh.TCPBackendRef{}}, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs: Required value: cannot be empty`, - }, - }, - { - name: "rules.backendRefs.backendRef", - input: &TCPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "kube-ns", - }, - Spec: pbmesh.TCPRoute{ - ParentRefs: []*pbmesh.ParentReference{{}}, - Rules: []*pbmesh.TCPRouteRule{ - { - BackendRefs: []*pbmesh.TCPBackendRef{ - {}, - { - BackendRef: &pbmesh.BackendReference{ - Ref: &pbresource.Reference{ - Type: pbmesh.ComputedRoutesType, - }, - Datacenter: "backend-datacenter", - }, - }, - }, - }, - }, - }, - }, - expectedErrMsgs: []string{ - `spec.rules[0].backendRefs[0].backendRef: Required value: missing required field`, - `spec.rules[0].backendRefs[1].backendRef.datacenter: Invalid value: "backend-datacenter": datacenter is not yet supported on backend refs`, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.input.Validate(common.ConsulTenancyConfig{}) - if len(tc.expectedErrMsgs) != 0 { - require.Error(t, err) - for _, s := range tc.expectedErrMsgs { - require.Contains(t, err.Error(), s) - } - } else { - require.NoError(t, err) - } - }) - } -} - -func constructTCPRouteResource(tp *pbmesh.TCPRoute, name, namespace, partition string) *pbresource.Resource { - data := inject.ToProtoAny(tp) - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.TCPRouteType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - Uid: "ABCD", // We add this to show it does not factor into the comparison - } - - return &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meshConfigMeta(), - - // We add the fields below to prove that they are not used in the Match when comparing the CRD to Consul. - Version: "123456", - Generation: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Status: map[string]*pbresource.Status{ - "knock": { - ObservedGeneration: "01ARZ3NDEKTSV4RRFFQ69G5FAV", - Conditions: make([]*pbresource.Condition, 0), - UpdatedAt: timestamppb.Now(), - }, - }, - } -} diff --git a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go b/control-plane/api/mesh/v2beta1/tcp_route_webhook.go deleted file mode 100644 index 68e72c6e35..0000000000 --- a/control-plane/api/mesh/v2beta1/tcp_route_webhook.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v2beta1 - -import ( - "context" - "net/http" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -// +kubebuilder:object:generate=false - -type TCPRouteWebhook struct { - Logger logr.Logger - - // ConsulTenancyConfig contains the injector's namespace and partition configuration. - ConsulTenancyConfig common.ConsulTenancyConfig - - decoder *admission.Decoder - client.Client -} - -var _ common.MeshConfigLister = &TCPRouteWebhook{} - -// NOTE: The path value in the below line is the path to the webhook. -// If it is updated, run code-gen, update subcommand/inject-connect/command.go -// and the consul-helm value for the path to the webhook. -// -// NOTE: The below line cannot be combined with any other comment. If it is it will break the code generation. -// -// +kubebuilder:webhook:verbs=create;update,path=/mutate-v2beta1-tcproute,mutating=true,failurePolicy=fail,groups=auth.consul.hashicorp.com,resources=tcproute,versions=v2beta1,name=mutate-tcproute.auth.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *TCPRouteWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource TCPRoute - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - return common.ValidateMeshConfig(ctx, req, v.Logger, v, &resource, v.ConsulTenancyConfig) -} - -func (v *TCPRouteWebhook) List(ctx context.Context) ([]common.MeshConfig, error) { - var resourceList TCPRouteList - if err := v.Client.List(ctx, &resourceList); err != nil { - return nil, err - } - var entries []common.MeshConfig - for _, item := range resourceList.Items { - entries = append(entries, common.MeshConfig(item)) - } - return entries, nil -} - -func (v *TCPRouteWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} diff --git a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go b/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go deleted file mode 100644 index 2d7aadbee7..0000000000 --- a/control-plane/api/mesh/v2beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,325 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by controller-gen. DO NOT EDIT. - -package v2beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in Conditions) DeepCopyInto(out *Conditions) { - { - in := &in - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions. -func (in Conditions) DeepCopy() Conditions { - if in == nil { - return nil - } - out := new(Conditions) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GRPCRoute) DeepCopyInto(out *GRPCRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRoute. -func (in *GRPCRoute) DeepCopy() *GRPCRoute { - if in == nil { - return nil - } - out := new(GRPCRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GRPCRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GRPCRouteList) DeepCopyInto(out *GRPCRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*GRPCRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GRPCRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GRPCRouteList. -func (in *GRPCRouteList) DeepCopy() *GRPCRouteList { - if in == nil { - return nil - } - out := new(GRPCRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GRPCRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. -func (in *HTTPRoute) DeepCopy() *HTTPRoute { - if in == nil { - return nil - } - out := new(HTTPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HTTPRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPRouteList) DeepCopyInto(out *HTTPRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*HTTPRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(HTTPRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteList. -func (in *HTTPRouteList) DeepCopy() *HTTPRouteList { - if in == nil { - return nil - } - out := new(HTTPRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *HTTPRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyConfiguration) DeepCopyInto(out *ProxyConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfiguration. -func (in *ProxyConfiguration) DeepCopy() *ProxyConfiguration { - if in == nil { - return nil - } - out := new(ProxyConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyConfiguration) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxyConfigurationList) DeepCopyInto(out *ProxyConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*ProxyConfiguration, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(ProxyConfiguration) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyConfigurationList. -func (in *ProxyConfigurationList) DeepCopy() *ProxyConfigurationList { - if in == nil { - return nil - } - out := new(ProxyConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ProxyConfigurationList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Status) DeepCopyInto(out *Status) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastSyncedTime != nil { - in, out := &in.LastSyncedTime, &out.LastSyncedTime - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Status. -func (in *Status) DeepCopy() *Status { - if in == nil { - return nil - } - out := new(Status) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPRoute) DeepCopyInto(out *TCPRoute) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRoute. -func (in *TCPRoute) DeepCopy() *TCPRoute { - if in == nil { - return nil - } - out := new(TCPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TCPRoute) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPRouteList) DeepCopyInto(out *TCPRouteList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]*TCPRoute, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(TCPRoute) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRouteList. -func (in *TCPRouteList) DeepCopy() *TCPRouteList { - if in == nil { - return nil - } - out := new(TCPRouteList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TCPRouteList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_types.go b/control-plane/api/v1alpha1/gatewaypolicy_types.go deleted file mode 100644 index 7c5a633e2b..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_types.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func init() { - SchemeBuilder.Register(&GatewayPolicy{}, &GatewayPolicyList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// GatewayPolicy is the Schema for the gatewaypolicies API. -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type GatewayPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec GatewayPolicySpec `json:"spec,omitempty"` - Status GatewayPolicyStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// GatewayPolicyList contains a list of GatewayPolicy. -type GatewayPolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []GatewayPolicy `json:"items"` -} - -// GatewayPolicySpec defines the desired state of GatewayPolicy. -type GatewayPolicySpec struct { - // TargetRef identifies an API object to apply policy to. - TargetRef PolicyTargetReference `json:"targetRef"` - //+kubebuilder:validation:Optional - Override *GatewayPolicyConfig `json:"override,omitempty"` - //+kubebuilder:validation:Optional - Default *GatewayPolicyConfig `json:"default,omitempty"` -} - -// PolicyTargetReference identifies the target that the policy applies to. -type PolicyTargetReference struct { - // Group is the group of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Group string `json:"group"` - - // Kind is kind of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Kind string `json:"kind"` - - // Name is the name of the target resource. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - Name string `json:"name"` - - // Namespace is the namespace of the referent. When unspecified, the local - // namespace is inferred. Even when policy targets a resource in a different - // namespace, it may only apply to traffic originating from the same - // namespace as the policy. - // - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:MaxLength=253 - // +optional - Namespace string `json:"namespace,omitempty"` - - // SectionName refers to the listener targeted by this policy. - SectionName *gwv1beta1.SectionName `json:"sectionName,omitempty"` -} - -type GatewayPolicyConfig struct { - //+kubebuilder:validation:Optional - JWT *GatewayJWTRequirement `json:"jwt,omitempty"` -} - -// GatewayJWTRequirement holds the list of JWT providers to be verified against. -type GatewayJWTRequirement struct { - // Providers is a list of providers to consider when verifying a JWT. - Providers []*GatewayJWTProvider `json:"providers"` -} - -// GatewayJWTProvider holds the provider and claim verification information. -type GatewayJWTProvider struct { - // Name is the name of the JWT provider. There MUST be a corresponding - // "jwt-provider" config entry with this name. - Name string `json:"name"` - - // VerifyClaims is a list of additional claims to verify in a JWT's payload. - VerifyClaims []*GatewayJWTClaimVerification `json:"verifyClaims,omitempty"` -} - -// GatewayJWTClaimVerification holds the actual claim information to be verified. -type GatewayJWTClaimVerification struct { - // Path is the path to the claim in the token JSON. - Path []string `json:"path"` - - // Value is the expected value at the given path: - // - If the type at the path is a list then we verify - // that this value is contained in the list. - // - // - If the type at the path is a string then we verify - // that this value matches. - Value string `json:"value"` -} - -// GatewayPolicyStatus defines the observed state of the gateway. -type GatewayPolicyStatus struct { - // Conditions describe the current conditions of the Policy. - // - // - // Known condition types are: - // - // * "Accepted" - // * "ResolvedRefs" - // - // +optional - // +listType=map - // +listMapKey=type - // +kubebuilder:validation:MaxItems=8 - Conditions []metav1.Condition `json:"conditions,omitempty"` -} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook.go deleted file mode 100644 index 12bc30416e..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_webhook.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "fmt" - "net/http" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" -) - -const Gatewaypolicy_GatewayIndex = "__gatewaypolicy_referencing_gateway" - -// +kubebuilder:object:generate=false - -type GatewayPolicyWebhook struct { - Logger logr.Logger - - // ConsulMeta contains metadata specific to the Consul installation. - ConsulMeta common.ConsulMeta - - decoder *admission.Decoder - client.Client -} - -// +kubebuilder:webhook:verbs=create;update,path=/validate-v1alpha1-gatewaypolicy,mutating=false,failurePolicy=fail,groups=consul.hashicorp.com,resources=gatewaypolicies,versions=v1alpha1,name=validate-gatewaypolicy.consul.hashicorp.com,sideEffects=None,admissionReviewVersions=v1beta1;v1 - -func (v *GatewayPolicyWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var resource GatewayPolicy - err := v.decoder.Decode(req, &resource) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - var list GatewayPolicyList - - gwNamespaceName := types.NamespacedName{Name: resource.Spec.TargetRef.Name, Namespace: resource.Namespace} - err = v.Client.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), - }) - - if err != nil { - v.Logger.Error(err, "error getting list of policies referencing gateway") - return admission.Errored(http.StatusInternalServerError, err) - } - - for _, policy := range list.Items { - if differentPolicySameTarget(resource, policy) { - return admission.Denied(fmt.Sprintf("policy targets gateway listener %q that is already the target of an existing policy %q", DerefStringOr(resource.Spec.TargetRef.SectionName, ""), policy.Name)) - } - } - - return admission.Allowed("gateway policy is valid") -} - -func differentPolicySameTarget(resource, policy GatewayPolicy) bool { - return resource.Name != policy.Name && - resource.Spec.TargetRef.Name == policy.Spec.TargetRef.Name && - resource.Spec.TargetRef.Group == policy.Spec.TargetRef.Group && - resource.Spec.TargetRef.Kind == policy.Spec.TargetRef.Kind && - resource.Spec.TargetRef.Namespace == policy.Spec.TargetRef.Namespace && - DerefStringOr(resource.Spec.TargetRef.SectionName, "") == DerefStringOr(policy.Spec.TargetRef.SectionName, "") -} - -func (v *GatewayPolicyWebhook) InjectDecoder(d *admission.Decoder) error { - v.decoder = d - return nil -} - -func DerefStringOr[T ~string, U ~string](v *T, val U) string { - if v == nil { - return string(val) - } - return string(*v) -} diff --git a/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go b/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go deleted file mode 100644 index 99b2b55896..0000000000 --- a/control-plane/api/v1alpha1/gatewaypolicy_webhook_test.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - "context" - "encoding/json" - "testing" - - logrtest "github.com/go-logr/logr/testr" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" -) - -func TestGatewayPolicyWebhook_Handle(t *testing.T) { - tests := map[string]struct { - existingResources []runtime.Object - newResource *GatewayPolicy - expAllow bool - expErrMessage string - }{ - "valid - no other policy targets listener": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - "valid - existing policy targets different gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "another-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewaypolicy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - - "valid - existing policy targets different listener on the same gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "my-gateway", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l2")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: true, - }, - "invalid - existing policy targets same listener on same gateway": { - existingResources: []runtime.Object{ - &gwv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-gateway", - Namespace: "default", - }, - Spec: gwv1beta1.GatewaySpec{ - GatewayClassName: "", - Listeners: []gwv1beta1.Listener{ - { - Name: "l1", - }, - { - Name: "l2", - }, - }, - }, - }, - &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - }, - newResource: &GatewayPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-policy-2", - Namespace: "default", - }, - Spec: GatewayPolicySpec{ - TargetRef: PolicyTargetReference{ - Group: gwv1beta1.GroupVersion.String(), - Kind: "Gateway", - Name: "my-gateway", - SectionName: ptrTo(gwv1beta1.SectionName("l1")), - }, - }, - }, - expAllow: false, - expErrMessage: "policy targets gateway listener \"l1\" that is already the target of an existing policy \"my-policy\"", - }, - } - for name, tt := range tests { - name := name - tt := tt - t.Run(name, func(t *testing.T) { - t.Parallel() - ctx := context.Background() - marshalledRequestObject, err := json.Marshal(tt.newResource) - require.NoError(t, err) - s := runtime.NewScheme() - s.AddKnownTypes(GroupVersion, &GatewayPolicy{}, &GatewayPolicyList{}) - s.AddKnownTypes(gwv1beta1.SchemeGroupVersion, &gwv1beta1.Gateway{}) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(tt.existingResources...).WithIndex(&GatewayPolicy{}, Gatewaypolicy_GatewayIndex, gatewayForGatewayPolicy).Build() - - var list GatewayPolicyList - - gwNamespaceName := types.NamespacedName{Name: "my-gateway", Namespace: "default"} - fakeClient.List(ctx, &list, &client.ListOptions{ - FieldSelector: fields.OneTermEqualSelector(Gatewaypolicy_GatewayIndex, gwNamespaceName.String()), - }) - - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - v := &GatewayPolicyWebhook{ - Logger: logrtest.New(t), - decoder: decoder, - Client: fakeClient, - } - - response := v.Handle(ctx, admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Name: tt.newResource.Name, - Operation: admissionv1.Create, - Object: runtime.RawExtension{ - Raw: marshalledRequestObject, - }, - }, - }) - - require.Equal(t, tt.expAllow, response.Allowed) - if tt.expErrMessage != "" { - require.Equal(t, tt.expErrMessage, string(response.AdmissionResponse.Result.Reason)) - } - }) - } -} - -func ptrTo[T any](v T) *T { - return &v -} - -func gatewayForGatewayPolicy(o client.Object) []string { - gatewayPolicy := o.(*GatewayPolicy) - - targetGateway := gatewayPolicy.Spec.TargetRef - // gateway policy is 1to1 - if targetGateway.Group == "gateway.networking.k8s.io/v1beta1" && targetGateway.Kind == "Gateway" { - policyNamespace := gatewayPolicy.Namespace - if policyNamespace == "" { - policyNamespace = "default" - } - targetNS := targetGateway.Namespace - if targetNS == "" { - targetNS = policyNamespace - } - - return []string{types.NamespacedName{Name: targetGateway.Name, Namespace: targetNS}.String()} - } - - return []string{} -} diff --git a/control-plane/api/v1alpha1/jwtprovider_types.go b/control-plane/api/v1alpha1/jwtprovider_types.go index a38a1df0a7..8e80ece1dc 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types.go +++ b/control-plane/api/v1alpha1/jwtprovider_types.go @@ -7,9 +7,11 @@ import ( "encoding/base64" "encoding/json" "net/url" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" "github.com/hashicorp/consul/api" capi "github.com/hashicorp/consul/api" corev1 "k8s.io/api/core/v1" @@ -17,8 +19,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) const ( @@ -394,7 +394,7 @@ type RemoteJWKS struct { // should be expired. // // Default value is 5 minutes. - CacheDuration metav1.Duration `json:"cacheDuration,omitempty"` + CacheDuration time.Duration `json:"cacheDuration,omitempty"` // FetchAsynchronously indicates that the JWKS should be fetched // when a client request arrives. Client requests will be paused @@ -421,7 +421,7 @@ func (r *RemoteJWKS) toConsul() *capi.RemoteJWKS { return &capi.RemoteJWKS{ URI: r.URI, RequestTimeoutMs: r.RequestTimeoutMs, - CacheDuration: r.CacheDuration.Duration, + CacheDuration: r.CacheDuration, FetchAsynchronously: r.FetchAsynchronously, RetryPolicy: r.RetryPolicy.toConsul(), JWKSCluster: r.JWKSCluster.toConsul(), @@ -462,7 +462,7 @@ type JWKSCluster struct { // The timeout for new network connections to hosts in the cluster. // If not set, a default value of 5s will be used. - ConnectTimeout metav1.Duration `json:"connectTimeout,omitempty"` + ConnectTimeout time.Duration `json:"connectTimeout,omitempty"` } func (c *JWKSCluster) toConsul() *capi.JWKSCluster { @@ -472,7 +472,7 @@ func (c *JWKSCluster) toConsul() *capi.JWKSCluster { return &capi.JWKSCluster{ DiscoveryType: c.DiscoveryType.toConsul(), TLSCertificates: c.TLSCertificates.toConsul(), - ConnectTimeout: c.ConnectTimeout.Duration, + ConnectTimeout: c.ConnectTimeout, } } @@ -663,13 +663,13 @@ type RetryPolicyBackOff struct { // BaseInterval to be used for the next back off computation. // // The default value from envoy is 1s. - BaseInterval metav1.Duration `json:"baseInterval,omitempty"` + BaseInterval time.Duration `json:"baseInterval,omitempty"` // MaxInternal to be used to specify the maximum interval between retries. // Optional but should be greater or equal to BaseInterval. // // Defaults to 10 times BaseInterval. - MaxInterval metav1.Duration `json:"maxInterval,omitempty"` + MaxInterval time.Duration `json:"maxInterval,omitempty"` } func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { @@ -677,8 +677,8 @@ func (r *RetryPolicyBackOff) toConsul() *capi.RetryPolicyBackOff { return nil } return &capi.RetryPolicyBackOff{ - BaseInterval: r.BaseInterval.Duration, - MaxInterval: r.MaxInterval.Duration, + BaseInterval: r.BaseInterval, + MaxInterval: r.MaxInterval, } } @@ -688,7 +688,7 @@ func (r *RetryPolicyBackOff) validate(path *field.Path) field.ErrorList { return errs } - if (r.MaxInterval.Duration != 0) && (r.BaseInterval.Duration > r.MaxInterval.Duration) { + if (r.MaxInterval != 0) && (r.BaseInterval > r.MaxInterval) { asJSON, _ := json.Marshal(r) errs = append(errs, field.Invalid(path, string(asJSON), "maxInterval should be greater or equal to baseInterval")) } diff --git a/control-plane/api/v1alpha1/jwtprovider_types_test.go b/control-plane/api/v1alpha1/jwtprovider_types_test.go index 098e34494e..8f6219e8d6 100644 --- a/control-plane/api/v1alpha1/jwtprovider_types_test.go +++ b/control-plane/api/v1alpha1/jwtprovider_types_test.go @@ -7,12 +7,11 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/api/common" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" ) // Test MatchesConsul for cases that should return true. @@ -56,13 +55,13 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 567, - CacheDuration: metav1.Duration{Duration: 890}, + CacheDuration: 890, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 1, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 23}, - MaxInterval: metav1.Duration{Duration: 456}, + BaseInterval: 23, + MaxInterval: 456, }, }, JWKSCluster: &JWKSCluster{ @@ -79,7 +78,7 @@ func TestJWTProvider_MatchesConsul(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -239,13 +238,13 @@ func TestJWTProvider_ToConsul(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 567, - CacheDuration: metav1.Duration{Duration: 890}, + CacheDuration: 890, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 1, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 23}, - MaxInterval: metav1.Duration{Duration: 456}, + BaseInterval: 23, + MaxInterval: 456, }, }, JWKSCluster: &JWKSCluster{ @@ -262,7 +261,7 @@ func TestJWTProvider_ToConsul(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -441,13 +440,13 @@ func TestJWTProvider_Validate(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 5000, - CacheDuration: metav1.Duration{Duration: 10 * time.Second}, + CacheDuration: 10 * time.Second, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 3, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 5 * time.Second}, - MaxInterval: metav1.Duration{Duration: 20 * time.Second}, + BaseInterval: 5 * time.Second, + MaxInterval: 20 * time.Second, }, }, JWKSCluster: &JWKSCluster{ @@ -457,7 +456,7 @@ func TestJWTProvider_Validate(t *testing.T) { Filename: "cert.crt", }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -505,13 +504,13 @@ func TestJWTProvider_Validate(t *testing.T) { Remote: &RemoteJWKS{ URI: "https://jwks.example.com", RequestTimeoutMs: 5000, - CacheDuration: metav1.Duration{Duration: 10 * time.Second}, + CacheDuration: 10 * time.Second, FetchAsynchronously: true, RetryPolicy: &JWKSRetryPolicy{ NumRetries: 3, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 5 * time.Second}, - MaxInterval: metav1.Duration{Duration: 20 * time.Second}, + BaseInterval: 5 * time.Second, + MaxInterval: 20 * time.Second, }, }, JWKSCluster: &JWKSCluster{ @@ -522,7 +521,7 @@ func TestJWTProvider_Validate(t *testing.T) { CertificateName: "ROOTCA", }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -621,7 +620,7 @@ func TestJWTProvider_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\",\"cacheDuration\":\"0s\"}}": exactly one of 'local' or 'remote' is required`, + `jwtprovider.consul.hashicorp.com "test-jwks-local-and-remote" is invalid: spec.jsonWebKeySet: Invalid value: "{\"local\":{\"filename\":\"jwks.txt\"},\"remote\":{\"uri\":\"https://jwks.example.com\"}}": exactly one of 'local' or 'remote' is required`, }, }, @@ -680,7 +679,7 @@ func TestJWTProvider_Validate(t *testing.T) { Filename: "cert.crt", }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -703,7 +702,7 @@ func TestJWTProvider_Validate(t *testing.T) { URI: "https://jwks.example.com", JWKSCluster: &JWKSCluster{ DiscoveryType: "FAKE_DNS", - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -733,7 +732,7 @@ func TestJWTProvider_Validate(t *testing.T) { InlineBytes: []byte("inline-bytes"), }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -762,7 +761,7 @@ func TestJWTProvider_Validate(t *testing.T) { EnvironmentVariable: "env-variable", }, }, - ConnectTimeout: metav1.Duration{Duration: 890}, + ConnectTimeout: 890, }, }, }, @@ -845,8 +844,8 @@ func TestJWTProvider_Validate(t *testing.T) { RetryPolicy: &JWKSRetryPolicy{ NumRetries: 0, RetryPolicyBackOff: &RetryPolicyBackOff{ - BaseInterval: metav1.Duration{Duration: 100 * time.Second}, - MaxInterval: metav1.Duration{Duration: 10 * time.Second}, + BaseInterval: 100 * time.Second, + MaxInterval: 10 * time.Second, }, }, }, @@ -854,7 +853,7 @@ func TestJWTProvider_Validate(t *testing.T) { }, }, expectedErrMsgs: []string{ - `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":\"1m40s\",\"maxInterval\":\"10s\"}": maxInterval should be greater or equal to baseInterval`, + `jwtprovider.consul.hashicorp.com "test-jwks-retry-intervals" is invalid: spec.jsonWebKeySet.remote.retryPolicy.retryPolicyBackOff: Invalid value: "{\"baseInterval\":100000000000,\"maxInterval\":10000000000}": maxInterval should be greater or equal to baseInterval`, }, }, } diff --git a/control-plane/api/v1alpha1/proxydefaults_types.go b/control-plane/api/v1alpha1/proxydefaults_types.go index 7b1529b941..1100cd107a 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types.go +++ b/control-plane/api/v1alpha1/proxydefaults_types.go @@ -95,9 +95,6 @@ type ProxyDefaultsSpec struct { EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` // FailoverPolicy specifies the exact mechanism used for failover. FailoverPolicy *FailoverPolicy `json:"failoverPolicy,omitempty"` - // PrioritizeByLocality controls whether the locality of services within the - // local partition will be used to prioritize connectivity. - PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } func (in *ProxyDefaults) GetObjectMeta() metav1.ObjectMeta { @@ -182,18 +179,17 @@ func (in *ProxyDefaults) SetLastSyncedTime(time *metav1.Time) { func (in *ProxyDefaults) ToConsul(datacenter string) capi.ConfigEntry { consulConfig := in.convertConfig() return &capi.ProxyConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - MeshGateway: in.Spec.MeshGateway.toConsul(), - Expose: in.Spec.Expose.toConsul(), - Config: consulConfig, - TransparentProxy: in.Spec.TransparentProxy.toConsul(), - MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), - AccessLogs: in.Spec.AccessLogs.toConsul(), - EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), - FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), - PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + MeshGateway: in.Spec.MeshGateway.toConsul(), + Expose: in.Spec.Expose.toConsul(), + Config: consulConfig, + TransparentProxy: in.Spec.TransparentProxy.toConsul(), + MutualTLSMode: in.Spec.MutualTLSMode.toConsul(), + AccessLogs: in.Spec.AccessLogs.toConsul(), + EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), + FailoverPolicy: in.Spec.FailoverPolicy.toConsul(), + Meta: meta(datacenter), } } @@ -232,7 +228,6 @@ func (in *ProxyDefaults) Validate(_ common.ConsulMeta) error { allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) allErrs = append(allErrs, in.Spec.FailoverPolicy.validate(path.Child("failoverPolicy"))...) - allErrs = append(allErrs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) if len(allErrs) > 0 { return apierrors.NewInvalid( diff --git a/control-plane/api/v1alpha1/proxydefaults_types_test.go b/control-plane/api/v1alpha1/proxydefaults_types_test.go index 6a965fce3a..07f894f322 100644 --- a/control-plane/api/v1alpha1/proxydefaults_types_test.go +++ b/control-plane/api/v1alpha1/proxydefaults_types_test.go @@ -98,9 +98,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "failover", - }, }, }, Theirs: &capi.ProxyConfigEntry{ @@ -164,9 +161,6 @@ func TestProxyDefaults_MatchesConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "failover", - }, }, Matches: true, }, @@ -324,9 +318,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "none", - }, }, }, Exp: &capi.ProxyConfigEntry{ @@ -391,9 +382,6 @@ func TestProxyDefaults_ToConsul(t *testing.T) { Mode: "sequential", Regions: []string{"us-west-1"}, }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "none", - }, Meta: map[string]string{ common.SourceKey: common.SourceValue, common.DatacenterKey: "datacenter", @@ -664,19 +652,6 @@ func TestProxyDefaults_Validate(t *testing.T) { }, expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.failoverPolicy.mode: Invalid value: "wrong-mode": must be one of "", "sequential", "order-by-locality"`, }, - "prioritize by locality invalid": { - input: &ProxyDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "global", - }, - Spec: ProxyDefaultsSpec{ - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "wrong-mode", - }, - }, - }, - expectedErrMsg: `proxydefaults.consul.hashicorp.com "global" is invalid: spec.prioritizeByLocality.mode: Invalid value: "wrong-mode": must be one of "", "none", "failover"`, - }, "multi-error": { input: &ProxyDefaults{ ObjectMeta: metav1.ObjectMeta{ diff --git a/control-plane/api/v1alpha1/routeauthfilter_types.go b/control-plane/api/v1alpha1/routeauthfilter_types.go deleted file mode 100644 index 1fb2b02030..0000000000 --- a/control-plane/api/v1alpha1/routeauthfilter_types.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - RouteAuthFilterKind = "RouteAuthFilter" -) - -func init() { - SchemeBuilder.Register(&RouteAuthFilter{}, &RouteAuthFilterList{}) -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteAuthFilter is the Schema for the routeauthfilters API. -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteAuthFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteAuthFilterSpec `json:"spec,omitempty"` - Status RouteAuthFilterStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteAuthFilterList contains a list of RouteAuthFilter. -type RouteAuthFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteAuthFilter `json:"items"` -} - -// RouteAuthFilterSpec defines the desired state of RouteAuthFilter. -type RouteAuthFilterSpec struct { - // This re-uses the JWT requirement type from Gateway Policy Types. - //+kubebuilder:validation:Optional - JWT *GatewayJWTRequirement `json:"jwt,omitempty"` -} - -// RouteAuthFilterStatus defines the observed state of the gateway. -type RouteAuthFilterStatus struct { - // Conditions describe the current conditions of the Filter. - // - // - // Known condition types are: - // - // * "Accepted" - // * "ResolvedRefs" - // - // +optional - // +listType=map - // +listMapKey=type - // +kubebuilder:validation:MaxItems=8 - // +kubebuilder:default={{type: "Accepted", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"},{type: "ResolvedRefs", status: "Unknown", reason:"Pending", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} - Conditions []metav1.Condition `json:"conditions,omitempty"` -} diff --git a/control-plane/api/v1alpha1/routeretryfilter_types.go b/control-plane/api/v1alpha1/routeretryfilter_types.go deleted file mode 100644 index 79fa85c608..0000000000 --- a/control-plane/api/v1alpha1/routeretryfilter_types.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func init() { - SchemeBuilder.Register(&RouteRetryFilter{}, &RouteRetryFilterList{}) -} - -const RouteRetryFilterKind = "RouteRetryFilter" - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteRetryFilter is the Schema for the routeretryfilters API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteRetryFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteRetryFilterSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteRetryFilterList contains a list of RouteRetryFilter. -type RouteRetryFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteRetryFilter `json:"items"` -} - -// RouteRetryFilterSpec defines the desired state of RouteRetryFilter. -type RouteRetryFilterSpec struct { - // +kubebuilder:validation:Minimum:=0 - // +kubebuilder:validation:Optional - NumRetries *uint32 `json:"numRetries"` - // +kubebuilder:validation:Optional - RetryOn []string `json:"retryOn"` - // +kubebuilder:validation:Optional - RetryOnStatusCodes []uint32 `json:"retryOnStatusCodes"` - // +kubebuilder:validation:Optional - RetryOnConnectFailure *bool `json:"retryOnConnectFailure"` -} - -func (h *RouteRetryFilter) GetNamespace() string { - return h.Namespace -} diff --git a/control-plane/api/v1alpha1/routetimeoutfilter_types.go b/control-plane/api/v1alpha1/routetimeoutfilter_types.go deleted file mode 100644 index 5aed6461bf..0000000000 --- a/control-plane/api/v1alpha1/routetimeoutfilter_types.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func init() { - SchemeBuilder.Register(&RouteTimeoutFilter{}, &RouteTimeoutFilterList{}) -} - -const RouteTimeoutFilterKind = "RouteTimeoutFilter" - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// RouteTimeoutFilter is the Schema for the httproutetimeoutfilters API -// +kubebuilder:printcolumn:name="Synced",type="string",JSONPath=".status.conditions[?(@.type==\"Synced\")].status",description="The sync status of the resource with Consul" -// +kubebuilder:printcolumn:name="Last Synced",type="date",JSONPath=".status.lastSyncedTime",description="The last successful synced time of the resource with Consul" -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="The age of the resource" -type RouteTimeoutFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec RouteTimeoutFilterSpec `json:"spec,omitempty"` - Status `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// RouteTimeoutFilterList contains a list of RouteTimeoutFilter. -type RouteTimeoutFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []RouteTimeoutFilter `json:"items"` -} - -// RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. -type RouteTimeoutFilterSpec struct { - // +kubebuilder:validation:Optional - RequestTimeout metav1.Duration `json:"requestTimeout"` - - // +kubebuilder:validation:Optional - IdleTimeout metav1.Duration `json:"idleTimeout"` -} - -func (h *RouteTimeoutFilter) GetNamespace() string { - return h.Namespace -} diff --git a/control-plane/api/v1alpha1/servicedefaults_types.go b/control-plane/api/v1alpha1/servicedefaults_types.go index fd764b32b2..2896475f75 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types.go +++ b/control-plane/api/v1alpha1/servicedefaults_types.go @@ -18,9 +18,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" - capi "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/api/common" + capi "github.com/hashicorp/consul/api" ) const ( @@ -116,9 +115,6 @@ type ServiceDefaultsSpec struct { // proxy threads. The only supported value is exact_balance. By default, no connection balancing is used. // Refer to the Envoy Connection Balance config for details. BalanceInboundConnections string `json:"balanceInboundConnections,omitempty"` - // RateLimits is rate limiting configuration that is applied to - // inbound traffic for a service. Rate limiting is a Consul enterprise feature. - RateLimits *RateLimits `json:"rateLimits,omitempty"` // EnvoyExtensions are a list of extensions to modify Envoy proxy configuration. EnvoyExtensions EnvoyExtensions `json:"envoyExtensions,omitempty"` } @@ -220,150 +216,6 @@ type ServiceDefaultsDestination struct { Port uint32 `json:"port,omitempty"` } -// RateLimits is rate limiting configuration that is applied to -// inbound traffic for a service. -// Rate limiting is a Consul Enterprise feature. -type RateLimits struct { - // InstanceLevel represents rate limit configuration - // that is applied per service instance. - InstanceLevel InstanceLevelRateLimits `json:"instanceLevel,omitempty"` -} - -func (rl *RateLimits) toConsul() *capi.RateLimits { - if rl == nil { - return nil - } - routes := make([]capi.InstanceLevelRouteRateLimits, len(rl.InstanceLevel.Routes)) - for i, r := range rl.InstanceLevel.Routes { - routes[i] = capi.InstanceLevelRouteRateLimits{ - PathExact: r.PathExact, - PathPrefix: r.PathPrefix, - PathRegex: r.PathRegex, - RequestsPerSecond: r.RequestsPerSecond, - RequestsMaxBurst: r.RequestsMaxBurst, - } - } - return &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: rl.InstanceLevel.RequestsPerSecond, - RequestsMaxBurst: rl.InstanceLevel.RequestsMaxBurst, - Routes: routes, - }, - } -} - -func (rl *RateLimits) validate(path *field.Path) field.ErrorList { - if rl == nil { - return nil - } - - return rl.InstanceLevel.validate(path.Child("instanceLevel")) -} - -type InstanceLevelRateLimits struct { - // RequestsPerSecond is the average number of requests per second that can be - // made without being throttled. This field is required if RequestsMaxBurst - // is set. The allowed number of requests may exceed RequestsPerSecond up to - // the value specified in RequestsMaxBurst. - // - // Internally, this is the refill rate of the token bucket used for rate limiting. - RequestsPerSecond int `json:"requestsPerSecond,omitempty"` - - // RequestsMaxBurst is the maximum number of requests that can be sent - // in a burst. Should be equal to or greater than RequestsPerSecond. - // If unset, defaults to RequestsPerSecond. - // - // Internally, this is the maximum size of the token bucket used for rate limiting. - RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` - - // Routes is a list of rate limits applied to specific routes. - // For a given request, the first matching route will be applied, if any. - // Overrides any top-level configuration. - Routes []InstanceLevelRouteRateLimits `json:"routes,omitempty"` -} - -func (irl InstanceLevelRateLimits) validate(path *field.Path) field.ErrorList { - var allErrs field.ErrorList - - // Track if RequestsPerSecond is set in at least one place in the config - isRateLimitSet := irl.RequestsPerSecond > 0 - - // Top-level RequestsPerSecond can be 0 (unset) or a positive number. - if irl.RequestsPerSecond < 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsPerSecond"), - irl.RequestsPerSecond, - "RequestsPerSecond must be positive")) - } - - if irl.RequestsPerSecond == 0 && irl.RequestsMaxBurst > 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsPerSecond"), - irl.RequestsPerSecond, - "RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set")) - } - - if irl.RequestsMaxBurst < 0 { - allErrs = append(allErrs, - field.Invalid(path.Child("requestsMaxBurst"), - irl.RequestsMaxBurst, - "RequestsMaxBurst must be positive")) - } - - for i, route := range irl.Routes { - if exact, prefix, regex := route.PathExact != "", route.PathPrefix != "", route.PathRegex != ""; (!exact && !prefix && !regex) || - (exact && prefix) || (exact && regex) || (prefix && regex) { - allErrs = append(allErrs, field.Required( - path.Child("routes").Index(i), - "Route must define exactly one of PathExact, PathPrefix, or PathRegex")) - } - - isRateLimitSet = isRateLimitSet || route.RequestsPerSecond > 0 - - // Unlike top-level RequestsPerSecond, any route MUST have a RequestsPerSecond defined. - if route.RequestsPerSecond <= 0 { - allErrs = append(allErrs, field.Invalid( - path.Child("routes").Index(i).Child("requestsPerSecond"), - route.RequestsPerSecond, "RequestsPerSecond must be greater than 0")) - } - - if route.RequestsMaxBurst < 0 { - allErrs = append(allErrs, field.Invalid( - path.Child("routes").Index(i).Child("requestsMaxBurst"), - route.RequestsMaxBurst, "RequestsMaxBurst must be positive")) - } - } - - if !isRateLimitSet { - allErrs = append(allErrs, field.Invalid( - path.Child("requestsPerSecond"), - irl.RequestsPerSecond, "At least one of top-level or route-level RequestsPerSecond must be set")) - } - return allErrs -} - -type InstanceLevelRouteRateLimits struct { - // Exact path to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathExact string `json:"pathExact,omitempty"` - // Prefix to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathPrefix string `json:"pathPrefix,omitempty"` - // Regex to match. Exactly one of PathExact, PathPrefix, or PathRegex must be specified. - PathRegex string `json:"pathRegex,omitempty"` - - // RequestsPerSecond is the average number of requests per - // second that can be made without being throttled. This field is required - // if RequestsMaxBurst is set. The allowed number of requests may exceed - // RequestsPerSecond up to the value specified in RequestsMaxBurst. - // Internally, this is the refill rate of the token bucket used for rate limiting. - RequestsPerSecond int `json:"requestsPerSecond,omitempty"` - - // RequestsMaxBurst is the maximum number of requests that can be sent - // in a burst. Should be equal to or greater than RequestsPerSecond. If unset, - // defaults to RequestsPerSecond. Internally, this is the maximum size of the token - // bucket used for rate limiting. - RequestsMaxBurst int `json:"requestsMaxBurst,omitempty"` -} - func (in *ServiceDefaults) ConsulKind() string { return capi.ServiceDefaults } @@ -456,7 +308,6 @@ func (in *ServiceDefaults) ToConsul(datacenter string) capi.ConfigEntry { LocalConnectTimeoutMs: in.Spec.LocalConnectTimeoutMs, LocalRequestTimeoutMs: in.Spec.LocalRequestTimeoutMs, BalanceInboundConnections: in.Spec.BalanceInboundConnections, - RateLimits: in.Spec.RateLimits.toConsul(), EnvoyExtensions: in.Spec.EnvoyExtensions.toConsul(), } } @@ -505,7 +356,6 @@ func (in *ServiceDefaults) Validate(consulMeta common.ConsulMeta) error { allErrs = append(allErrs, in.Spec.UpstreamConfig.validate(path.Child("upstreamConfig"), consulMeta.PartitionsEnabled)...) allErrs = append(allErrs, in.Spec.Expose.validate(path.Child("expose"))...) - allErrs = append(allErrs, in.Spec.RateLimits.validate(path.Child("rateLimits"))...) allErrs = append(allErrs, in.Spec.EnvoyExtensions.validate(path.Child("envoyExtensions"))...) if len(allErrs) > 0 { diff --git a/control-plane/api/v1alpha1/servicedefaults_types_test.go b/control-plane/api/v1alpha1/servicedefaults_types_test.go index 2292e55791..31a41f3f06 100644 --- a/control-plane/api/v1alpha1/servicedefaults_types_test.go +++ b/control-plane/api/v1alpha1/servicedefaults_types_test.go @@ -160,23 +160,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, EnvoyExtensions: EnvoyExtensions{ EnvoyExtension{ Name: "aws_request_signing", @@ -305,23 +288,6 @@ func TestServiceDefaults_ToConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", - RateLimits: &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []capi.InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, EnvoyExtensions: []capi.EnvoyExtension{ { Name: "aws_request_signing", @@ -593,23 +559,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, EnvoyExtensions: EnvoyExtensions{ EnvoyExtension{ Name: "aws_request_signing", @@ -731,23 +680,6 @@ func TestServiceDefaults_MatchesConsul(t *testing.T) { }, }, BalanceInboundConnections: "exact_balance", - RateLimits: &capi.RateLimits{ - InstanceLevel: capi.InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []capi.InstanceLevelRouteRateLimits{ - { - PathExact: "/foo", - RequestsPerSecond: 111, - RequestsMaxBurst: 222, - }, - { - PathPrefix: "/admin", - RequestsPerSecond: 333, - }, - }, - }, - }, EnvoyExtensions: []capi.EnvoyExtension{ { Name: "aws_request_signing", @@ -1397,152 +1329,6 @@ func TestServiceDefaults_Validate(t *testing.T) { }, expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.envoyExtensions.envoyExtension[0].arguments: Invalid value: "{\"SOME_INVALID_JSON\"}": must be valid map value: invalid character '}' after object key`, }, - "rateLimits.instanceLevel.requestsPerSecond (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: -1, - RequestsMaxBurst: 0, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: -1: RequestsPerSecond must be positive`, - }, - "rateLimits.instanceLevel.requestsPerSecond (invalid value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsMaxBurst: 1000, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0 if RequestsMaxBurst is set`, - }, - "rateLimits.instanceLevel.requestsMaxBurst (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsMaxBurst: -1, - Routes: []InstanceLevelRouteRateLimits{ - { - PathPrefix: "/admin", - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, - }, - "rateLimits.instanceLevel.routes (invalid path)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - RequestsMaxBurst: 2345, - Routes: []InstanceLevelRouteRateLimits{ - { - RequestsPerSecond: 222, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0]: Required value: Route must define exactly one of PathExact, PathPrefix, or PathRegex`, - }, - "rateLimits.instanceLevel.routes.requestsPerSecond (zero value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0`, - }, - "rateLimits.instanceLevel.routes.requestsMaxBurst (negative value)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - RequestsPerSecond: 1234, - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - RequestsPerSecond: 222, - RequestsMaxBurst: -1, - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: spec.rateLimits.instanceLevel.routes[0].requestsMaxBurst: Invalid value: -1: RequestsMaxBurst must be positive`, - }, - "rateLimits.requestsMaxBurst (top-level and route-level unset)": { - input: &ServiceDefaults{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-service", - }, - Spec: ServiceDefaultsSpec{ - RateLimits: &RateLimits{ - InstanceLevel: InstanceLevelRateLimits{ - Routes: []InstanceLevelRouteRateLimits{ - { - PathExact: "/", - }, - }, - }, - }, - }, - }, - expectedErrMsg: `servicedefaults.consul.hashicorp.com "my-service" is invalid: [spec.rateLimits.instanceLevel.routes[0].requestsPerSecond: Invalid value: 0: RequestsPerSecond must be greater than 0, spec.rateLimits.instanceLevel.requestsPerSecond: Invalid value: 0: At least one of top-level or route-level RequestsPerSecond must be set]`, - }, } for name, testCase := range cases { diff --git a/control-plane/api/v1alpha1/serviceresolver_types.go b/control-plane/api/v1alpha1/serviceresolver_types.go index 3a8e907222..b7644a6ec1 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types.go +++ b/control-plane/api/v1alpha1/serviceresolver_types.go @@ -82,9 +82,6 @@ type ServiceResolverSpec struct { // LoadBalancer determines the load balancing policy and configuration for services // issuing requests to this upstream service. LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"` - // PrioritizeByLocality controls whether the locality of services within the - // local partition will be used to prioritize connectivity. - PrioritizeByLocality *PrioritizeByLocality `json:"prioritizeByLocality,omitempty"` } type ServiceResolverRedirect struct { @@ -306,17 +303,16 @@ func (in *ServiceResolver) SyncedConditionStatus() corev1.ConditionStatus { // ToConsul converts the entry into its Consul equivalent struct. func (in *ServiceResolver) ToConsul(datacenter string) capi.ConfigEntry { return &capi.ServiceResolverConfigEntry{ - Kind: in.ConsulKind(), - Name: in.ConsulName(), - DefaultSubset: in.Spec.DefaultSubset, - Subsets: in.Spec.Subsets.toConsul(), - Redirect: in.Spec.Redirect.toConsul(), - Failover: in.Spec.Failover.toConsul(), - ConnectTimeout: in.Spec.ConnectTimeout.Duration, - RequestTimeout: in.Spec.RequestTimeout.Duration, - LoadBalancer: in.Spec.LoadBalancer.toConsul(), - PrioritizeByLocality: in.Spec.PrioritizeByLocality.toConsul(), - Meta: meta(datacenter), + Kind: in.ConsulKind(), + Name: in.ConsulName(), + DefaultSubset: in.Spec.DefaultSubset, + Subsets: in.Spec.Subsets.toConsul(), + Redirect: in.Spec.Redirect.toConsul(), + Failover: in.Spec.Failover.toConsul(), + ConnectTimeout: in.Spec.ConnectTimeout.Duration, + RequestTimeout: in.Spec.RequestTimeout.Duration, + LoadBalancer: in.Spec.LoadBalancer.toConsul(), + Meta: meta(datacenter), } } @@ -346,7 +342,6 @@ func (in *ServiceResolver) Validate(consulMeta common.ConsulMeta) error { } errs = append(errs, in.Spec.Redirect.validate(path.Child("redirect"), consulMeta)...) - errs = append(errs, in.Spec.PrioritizeByLocality.validate(path.Child("prioritizeByLocality"))...) errs = append(errs, in.Spec.Subsets.validate(path.Child("subsets"))...) errs = append(errs, in.Spec.LoadBalancer.validate(path.Child("loadBalancer"))...) errs = append(errs, in.validateEnterprise(consulMeta)...) diff --git a/control-plane/api/v1alpha1/serviceresolver_types_test.go b/control-plane/api/v1alpha1/serviceresolver_types_test.go index 3fbc2b4fce..70791b606c 100644 --- a/control-plane/api/v1alpha1/serviceresolver_types_test.go +++ b/control-plane/api/v1alpha1/serviceresolver_types_test.go @@ -66,9 +66,6 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "failover", - }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -151,9 +148,6 @@ func TestServiceResolver_MatchesConsul(t *testing.T) { Datacenter: "redirect_datacenter", Peer: "redirect_peer", }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "failover", - }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -285,9 +279,6 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "none", - }, Failover: map[string]ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -370,9 +361,6 @@ func TestServiceResolver_ToConsul(t *testing.T) { Datacenter: "redirect_datacenter", Partition: "redirect_partition", }, - PrioritizeByLocality: &capi.ServiceResolverPrioritizeByLocality{ - Mode: "none", - }, Failover: map[string]capi.ServiceResolverFailover{ "failover1": { Service: "failover1", @@ -898,22 +886,6 @@ func TestServiceResolver_Validate(t *testing.T) { "spec.failover[failB].namespace: Invalid value: \"namespace-b\": Consul Enterprise namespaces must be enabled to set failover.namespace", }, }, - "prioritize by locality invalid": { - input: &ServiceResolver{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - }, - Spec: ServiceResolverSpec{ - PrioritizeByLocality: &PrioritizeByLocality{ - Mode: "bad", - }, - }, - }, - namespacesEnabled: false, - expectedErrMsgs: []string{ - "serviceresolver.consul.hashicorp.com \"foo\" is invalid: spec.prioritizeByLocality.mode: Invalid value: \"bad\": must be one of \"\", \"none\", \"failover\"", - }, - }, } for name, testCase := range cases { t.Run(name, func(t *testing.T) { diff --git a/control-plane/api/v1alpha1/shared_types.go b/control-plane/api/v1alpha1/shared_types.go index 148376a393..aa19c339da 100644 --- a/control-plane/api/v1alpha1/shared_types.go +++ b/control-plane/api/v1alpha1/shared_types.go @@ -310,37 +310,6 @@ func (in *FailoverPolicy) validate(path *field.Path) field.ErrorList { return errs } -// PrioritizeByLocality controls whether the locality of services within the -// local partition will be used to prioritize connectivity. -type PrioritizeByLocality struct { - // Mode specifies the type of prioritization that will be performed - // when selecting nodes in the local partition. - // Valid values are: "" (default "none"), "none", and "failover". - Mode string `json:"mode,omitempty"` -} - -func (in *PrioritizeByLocality) toConsul() *capi.ServiceResolverPrioritizeByLocality { - if in == nil { - return nil - } - - return &capi.ServiceResolverPrioritizeByLocality{ - Mode: in.Mode, - } -} - -func (in *PrioritizeByLocality) validate(path *field.Path) field.ErrorList { - var errs field.ErrorList - if in == nil { - return nil - } - modes := []string{"", "none", "failover"} - if !sliceContains(modes, in.Mode) { - errs = append(errs, field.Invalid(path.Child("mode"), in.Mode, notInSliceMessage(modes))) - } - return errs -} - func notInSliceMessage(slice []string) string { return fmt.Sprintf(`must be one of "%s"`, strings.Join(slice, `", "`)) } diff --git a/control-plane/api/v1alpha1/zz_generated.deepcopy.go b/control-plane/api/v1alpha1/zz_generated.deepcopy.go index 045bd5144e..17308d2a4b 100644 --- a/control-plane/api/v1alpha1/zz_generated.deepcopy.go +++ b/control-plane/api/v1alpha1/zz_generated.deepcopy.go @@ -10,7 +10,6 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/gateway-api/apis/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -567,205 +566,6 @@ func (in *GatewayClassConfigSpec) DeepCopy() *GatewayClassConfigSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTClaimVerification) DeepCopyInto(out *GatewayJWTClaimVerification) { - *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTClaimVerification. -func (in *GatewayJWTClaimVerification) DeepCopy() *GatewayJWTClaimVerification { - if in == nil { - return nil - } - out := new(GatewayJWTClaimVerification) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTProvider) DeepCopyInto(out *GatewayJWTProvider) { - *out = *in - if in.VerifyClaims != nil { - in, out := &in.VerifyClaims, &out.VerifyClaims - *out = make([]*GatewayJWTClaimVerification, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayJWTClaimVerification) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTProvider. -func (in *GatewayJWTProvider) DeepCopy() *GatewayJWTProvider { - if in == nil { - return nil - } - out := new(GatewayJWTProvider) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayJWTRequirement) DeepCopyInto(out *GatewayJWTRequirement) { - *out = *in - if in.Providers != nil { - in, out := &in.Providers, &out.Providers - *out = make([]*GatewayJWTProvider, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(GatewayJWTProvider) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayJWTRequirement. -func (in *GatewayJWTRequirement) DeepCopy() *GatewayJWTRequirement { - if in == nil { - return nil - } - out := new(GatewayJWTRequirement) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicy) DeepCopyInto(out *GatewayPolicy) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicy. -func (in *GatewayPolicy) DeepCopy() *GatewayPolicy { - if in == nil { - return nil - } - out := new(GatewayPolicy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayPolicy) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyConfig) DeepCopyInto(out *GatewayPolicyConfig) { - *out = *in - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(GatewayJWTRequirement) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyConfig. -func (in *GatewayPolicyConfig) DeepCopy() *GatewayPolicyConfig { - if in == nil { - return nil - } - out := new(GatewayPolicyConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyList) DeepCopyInto(out *GatewayPolicyList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]GatewayPolicy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyList. -func (in *GatewayPolicyList) DeepCopy() *GatewayPolicyList { - if in == nil { - return nil - } - out := new(GatewayPolicyList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GatewayPolicyList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicySpec) DeepCopyInto(out *GatewayPolicySpec) { - *out = *in - in.TargetRef.DeepCopyInto(&out.TargetRef) - if in.Override != nil { - in, out := &in.Override, &out.Override - *out = new(GatewayPolicyConfig) - (*in).DeepCopyInto(*out) - } - if in.Default != nil { - in, out := &in.Default, &out.Default - *out = new(GatewayPolicyConfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicySpec. -func (in *GatewayPolicySpec) DeepCopy() *GatewayPolicySpec { - if in == nil { - return nil - } - out := new(GatewayPolicySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GatewayPolicyStatus) DeepCopyInto(out *GatewayPolicyStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayPolicyStatus. -func (in *GatewayPolicyStatus) DeepCopy() *GatewayPolicyStatus { - if in == nil { - return nil - } - out := new(GatewayPolicyStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GatewayServiceTLSConfig) DeepCopyInto(out *GatewayServiceTLSConfig) { *out = *in @@ -1065,41 +865,6 @@ func (in *IngressServiceConfig) DeepCopy() *IngressServiceConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InstanceLevelRateLimits) DeepCopyInto(out *InstanceLevelRateLimits) { - *out = *in - if in.Routes != nil { - in, out := &in.Routes, &out.Routes - *out = make([]InstanceLevelRouteRateLimits, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRateLimits. -func (in *InstanceLevelRateLimits) DeepCopy() *InstanceLevelRateLimits { - if in == nil { - return nil - } - out := new(InstanceLevelRateLimits) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InstanceLevelRouteRateLimits) DeepCopyInto(out *InstanceLevelRouteRateLimits) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InstanceLevelRouteRateLimits. -func (in *InstanceLevelRouteRateLimits) DeepCopy() *InstanceLevelRouteRateLimits { - if in == nil { - return nil - } - out := new(InstanceLevelRouteRateLimits) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IntentionDestination) DeepCopyInto(out *IntentionDestination) { *out = *in @@ -1329,7 +1094,6 @@ func (in *JWKSCluster) DeepCopyInto(out *JWKSCluster) { *out = new(JWKSTLSCertificate) (*in).DeepCopyInto(*out) } - out.ConnectTimeout = in.ConnectTimeout } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWKSCluster. @@ -2273,41 +2037,6 @@ func (in *PeeringMeshConfig) DeepCopy() *PeeringMeshConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PolicyTargetReference) DeepCopyInto(out *PolicyTargetReference) { - *out = *in - if in.SectionName != nil { - in, out := &in.SectionName, &out.SectionName - *out = new(v1beta1.SectionName) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyTargetReference. -func (in *PolicyTargetReference) DeepCopy() *PolicyTargetReference { - if in == nil { - return nil - } - out := new(PolicyTargetReference) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PrioritizeByLocality) DeepCopyInto(out *PrioritizeByLocality) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrioritizeByLocality. -func (in *PrioritizeByLocality) DeepCopy() *PrioritizeByLocality { - if in == nil { - return nil - } - out := new(PrioritizeByLocality) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyDefaults) DeepCopyInto(out *ProxyDefaults) { *out = *in @@ -2404,11 +2133,6 @@ func (in *ProxyDefaultsSpec) DeepCopyInto(out *ProxyDefaultsSpec) { *out = new(FailoverPolicy) (*in).DeepCopyInto(*out) } - if in.PrioritizeByLocality != nil { - in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality - *out = new(PrioritizeByLocality) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyDefaultsSpec. @@ -2421,22 +2145,6 @@ func (in *ProxyDefaultsSpec) DeepCopy() *ProxyDefaultsSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RateLimits) DeepCopyInto(out *RateLimits) { - *out = *in - in.InstanceLevel.DeepCopyInto(&out.InstanceLevel) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimits. -func (in *RateLimits) DeepCopy() *RateLimits { - if in == nil { - return nil - } - out := new(RateLimits) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReadWriteRatesConfig) DeepCopyInto(out *ReadWriteRatesConfig) { *out = *in @@ -2455,7 +2163,6 @@ func (in *ReadWriteRatesConfig) DeepCopy() *ReadWriteRatesConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RemoteJWKS) DeepCopyInto(out *RemoteJWKS) { *out = *in - out.CacheDuration = in.CacheDuration if in.RetryPolicy != nil { in, out := &in.RetryPolicy, &out.RetryPolicy *out = new(JWKSRetryPolicy) @@ -2481,8 +2188,6 @@ func (in *RemoteJWKS) DeepCopy() *RemoteJWKS { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RetryPolicyBackOff) DeepCopyInto(out *RetryPolicyBackOff) { *out = *in - out.BaseInterval = in.BaseInterval - out.MaxInterval = in.MaxInterval } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RetryPolicyBackOff. @@ -2510,277 +2215,6 @@ func (in *RingHashConfig) DeepCopy() *RingHashConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilter) DeepCopyInto(out *RouteAuthFilter) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilter. -func (in *RouteAuthFilter) DeepCopy() *RouteAuthFilter { - if in == nil { - return nil - } - out := new(RouteAuthFilter) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteAuthFilter) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterList) DeepCopyInto(out *RouteAuthFilterList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RouteAuthFilter, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterList. -func (in *RouteAuthFilterList) DeepCopy() *RouteAuthFilterList { - if in == nil { - return nil - } - out := new(RouteAuthFilterList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteAuthFilterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterSpec) DeepCopyInto(out *RouteAuthFilterSpec) { - *out = *in - if in.JWT != nil { - in, out := &in.JWT, &out.JWT - *out = new(GatewayJWTRequirement) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterSpec. -func (in *RouteAuthFilterSpec) DeepCopy() *RouteAuthFilterSpec { - if in == nil { - return nil - } - out := new(RouteAuthFilterSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteAuthFilterStatus) DeepCopyInto(out *RouteAuthFilterStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteAuthFilterStatus. -func (in *RouteAuthFilterStatus) DeepCopy() *RouteAuthFilterStatus { - if in == nil { - return nil - } - out := new(RouteAuthFilterStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilter) DeepCopyInto(out *RouteRetryFilter) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilter. -func (in *RouteRetryFilter) DeepCopy() *RouteRetryFilter { - if in == nil { - return nil - } - out := new(RouteRetryFilter) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRetryFilter) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilterList) DeepCopyInto(out *RouteRetryFilterList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RouteRetryFilter, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterList. -func (in *RouteRetryFilterList) DeepCopy() *RouteRetryFilterList { - if in == nil { - return nil - } - out := new(RouteRetryFilterList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteRetryFilterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteRetryFilterSpec) DeepCopyInto(out *RouteRetryFilterSpec) { - *out = *in - if in.NumRetries != nil { - in, out := &in.NumRetries, &out.NumRetries - *out = new(uint32) - **out = **in - } - if in.RetryOn != nil { - in, out := &in.RetryOn, &out.RetryOn - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.RetryOnStatusCodes != nil { - in, out := &in.RetryOnStatusCodes, &out.RetryOnStatusCodes - *out = make([]uint32, len(*in)) - copy(*out, *in) - } - if in.RetryOnConnectFailure != nil { - in, out := &in.RetryOnConnectFailure, &out.RetryOnConnectFailure - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteRetryFilterSpec. -func (in *RouteRetryFilterSpec) DeepCopy() *RouteRetryFilterSpec { - if in == nil { - return nil - } - out := new(RouteRetryFilterSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilter) DeepCopyInto(out *RouteTimeoutFilter) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilter. -func (in *RouteTimeoutFilter) DeepCopy() *RouteTimeoutFilter { - if in == nil { - return nil - } - out := new(RouteTimeoutFilter) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteTimeoutFilter) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilterList) DeepCopyInto(out *RouteTimeoutFilterList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]RouteTimeoutFilter, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterList. -func (in *RouteTimeoutFilterList) DeepCopy() *RouteTimeoutFilterList { - if in == nil { - return nil - } - out := new(RouteTimeoutFilterList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *RouteTimeoutFilterList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RouteTimeoutFilterSpec) DeepCopyInto(out *RouteTimeoutFilterSpec) { - *out = *in - out.RequestTimeout = in.RequestTimeout - out.IdleTimeout = in.IdleTimeout -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteTimeoutFilterSpec. -func (in *RouteTimeoutFilterSpec) DeepCopy() *RouteTimeoutFilterSpec { - if in == nil { - return nil - } - out := new(RouteTimeoutFilterSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SamenessGroup) DeepCopyInto(out *SamenessGroup) { *out = *in @@ -3044,11 +2478,6 @@ func (in *ServiceDefaultsSpec) DeepCopyInto(out *ServiceDefaultsSpec) { *out = new(ServiceDefaultsDestination) (*in).DeepCopyInto(*out) } - if in.RateLimits != nil { - in, out := &in.RateLimits, &out.RateLimits - *out = new(RateLimits) - (*in).DeepCopyInto(*out) - } if in.EnvoyExtensions != nil { in, out := &in.EnvoyExtensions, &out.EnvoyExtensions *out = make(EnvoyExtensions, len(*in)) @@ -3328,11 +2757,6 @@ func (in *ServiceResolverSpec) DeepCopyInto(out *ServiceResolverSpec) { *out = new(LoadBalancer) (*in).DeepCopyInto(*out) } - if in.PrioritizeByLocality != nil { - in, out := &in.PrioritizeByLocality, &out.PrioritizeByLocality - *out = new(PrioritizeByLocality) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceResolverSpec. diff --git a/control-plane/build-support/functions/10-util.sh b/control-plane/build-support/functions/10-util.sh index 9473226b2c..d22d15f9a8 100644 --- a/control-plane/build-support/functions/10-util.sh +++ b/control-plane/build-support/functions/10-util.sh @@ -733,7 +733,11 @@ function set_changelog { rel_date="$3" fi local last_release_date_git_tag=$4 - local preReleaseVersion="-$5" + + local preReleaseVersion + if test -n "$5"; then + local preReleaseVersion="-$5" + fi if test -z "${version}"; then err "ERROR: Must specify a version to put into the changelog" @@ -784,33 +788,6 @@ function prepare_release { set_changelog "${curDir}" "${version}" "${releaseDate}" "${lastGitTag}" "${prereleaseVersion}" } -function prepare_rc_branch { - # Arguments: - # $1 - Path to top level Consul source - # $2 - The version of the release - # $3 - The release date - # $4 - The last release git tag for this branch (eg. v1.1.0) - # $5 - The consul version - # $6 - The consul-dataplane version - # $7 - The pre-release version - # - # - # Returns: - # 0 - success - # * - error - - local curDir=$1 - local version=$2 - local releaseDate=$3 - local lastGitTag=$4 - local consulVersion=$5 - local consulDataplaneVersion=$6 - local prereleaseVersion=$7 - - echo "prepare_rc: dir:${curDir} consul-k8s:${version} consul:${consulVersion} consul-dataplane:${consulDataplaneVersion} date:"${releaseDate}" git tag:${lastGitTag}" - set_version "${curDir}" "${version}" "${releaseDate}" "${prereleaseVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-k8s-control-plane:" "${consulVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul" "${consulDataplaneVersion}" "docker.mirror.hashicorp.services\/hashicorppreview\/consul-dataplane" -} - function prepare_dev { # Arguments: # $1 - Path to top level Consul source diff --git a/control-plane/build-support/functions/20-build.sh b/control-plane/build-support/functions/20-build.sh index dac626b88f..e9540956c9 100644 --- a/control-plane/build-support/functions/20-build.sh +++ b/control-plane/build-support/functions/20-build.sh @@ -188,7 +188,7 @@ function build_consul_local { # build with go install. # The GOXPARALLEL environment variable is used if set - if [ "${GOTAGS:-}" == "fips" ]; then + if [ $GOTAGS == "fips" ]; then CGO_ENABLED=1 else CGO_ENABLED=0 diff --git a/control-plane/build-support/scripts/consul-enterprise-version.sh b/control-plane/build-support/scripts/consul-enterprise-version.sh index d0a1a40b53..37df85dfc5 100755 --- a/control-plane/build-support/scripts/consul-enterprise-version.sh +++ b/control-plane/build-support/scripts/consul-enterprise-version.sh @@ -4,12 +4,10 @@ FILE=$1 VERSION=$(yq .global.image $FILE) -if [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then +if [[ !"${VERSION}" == *"hashicorppreview/consul:"* ]]; then + VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") +elif [[ !"${VERSION}" == *"hashicorp/consul:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") -elif [[ !"${VERSION}" == *"-rc"* ]]; then - VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g" | sed "s/$/-ent/g") -else - VERSION=$(echo ${VERSION} | sed "s/consul:/consul-enterprise:/g") fi -echo "${VERSION}" \ No newline at end of file +echo "${VERSION}" diff --git a/control-plane/build-support/scripts/consul-version.sh b/control-plane/build-support/scripts/consul-version.sh index faaed33b20..4761e3e923 100755 --- a/control-plane/build-support/scripts/consul-version.sh +++ b/control-plane/build-support/scripts/consul-version.sh @@ -6,6 +6,7 @@ VERSION=$(yq .global.image $FILE) if [[ "${VERSION}" == *"consul-enterprise:"* ]]; then VERSION=$(echo ${VERSION} | sed "s/consul-enterprise:/consul:/g") + VERSION=$(echo ${VERSION} | sed "s/\-ent//") fi echo "${VERSION}" diff --git a/control-plane/catalog/to-consul/resource.go b/control-plane/catalog/to-consul/resource.go index 2d29d6c15a..0c319d90ee 100644 --- a/control-plane/catalog/to-consul/resource.go +++ b/control-plane/catalog/to-consul/resource.go @@ -512,7 +512,7 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service.ID = serviceID(r.Service.Service, ip) r.Service.Address = ip // Adding information about service weight. - // Overrides the existing weight if present. + // Overrides the existing weight if present if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { weightI, err := getServiceWeight(weight) if err == nil { @@ -561,7 +561,7 @@ func (t *ServiceResource) generateRegistrations(key string) { r.Service.Address = addr // Adding information about service weight. - // Overrides the existing weight if present. + // Overrides the existing weight if present if weight, ok := svc.Annotations[annotationServiceWeight]; ok && weight != "" { weightI, err := getServiceWeight(weight) if err == nil { @@ -1028,7 +1028,7 @@ func consulHealthCheckID(k8sNS string, serviceID string) string { // Calculates the passing service weight. func getServiceWeight(weight string) (int, error) { - // error validation if the input param is a number. + // error validation if the input param is a number weightI, err := strconv.Atoi(weight) if err != nil { return -1, err diff --git a/control-plane/catalog/to-consul/syncer_test.go b/control-plane/catalog/to-consul/syncer_test.go index ab2cfee0a2..3fae7a3d16 100644 --- a/control-plane/catalog/to-consul/syncer_test.go +++ b/control-plane/catalog/to-consul/syncer_test.go @@ -13,13 +13,12 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -234,7 +233,7 @@ func TestConsulSyncer_stopsGracefully(t *testing.T) { testClient := &test.TestServerClient{ Cfg: &consul.Config{APIClientConfig: &api.Config{}, HTTPPort: port}, - Watcher: test.MockConnMgrForIPAndPort(t, parsedURL.Host, port, false), + Watcher: test.MockConnMgrForIPAndPort(parsedURL.Host, port), } // Start the syncer. diff --git a/control-plane/cni/main.go b/control-plane/cni/main.go index d642af45a8..e35f5ad811 100644 --- a/control-plane/cni/main.go +++ b/control-plane/cni/main.go @@ -33,10 +33,6 @@ const ( // a pod after an injection is done. keyInjectStatus = "consul.hashicorp.com/connect-inject-status" - // keyMeshInjectStatus is the mesh v2 key of the annotation that is added to - // a pod after an injection is done. - keyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" - // keyTransparentProxyStatus is the key of the annotation that is added to // a pod when transparent proxy is done. keyTransparentProxyStatus = "consul.hashicorp.com/transparent-proxy-status" @@ -250,15 +246,11 @@ func main() { } // skipTrafficRedirection looks for annotations on the pod and determines if it should skip traffic redirection. -// The absence of the annotations is the equivalent of "disabled" because it means that the connect-inject mutating +// The absence of the annotations is the equivalent of "disabled" because it means that the connect inject mutating // webhook did not run against the pod. func skipTrafficRedirection(pod corev1.Pod) bool { - // If keyInjectStatus exists, then we are dealing with a mesh v1 pod - // else we have a mesh v2 pod. We need to check for both before we can skip. if anno, ok := pod.Annotations[keyInjectStatus]; !ok || anno == "" { - if anno, ok := pod.Annotations[keyMeshInjectStatus]; !ok || anno == "" { - return true - } + return true } if anno, ok := pod.Annotations[keyTransparentProxyStatus]; !ok || anno == "" { diff --git a/control-plane/cni/main_test.go b/control-plane/cni/main_test.go index dc929f1408..7c289a9825 100644 --- a/control-plane/cni/main_test.go +++ b/control-plane/cni/main_test.go @@ -174,15 +174,6 @@ func TestSkipTrafficRedirection(t *testing.T) { }, expectedSkip: false, }, - { - name: "Pod with v2 annotations correctly set", - annotatedPod: func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[keyMeshInjectStatus] = "foo" - pod.Annotations[keyTransparentProxyStatus] = "bar" - return pod - }, - expectedSkip: false, - }, { name: "Pod without annotations, will timeout waiting", annotatedPod: func(pod *corev1.Pod) *corev1.Pod { diff --git a/control-plane/commands.go b/control-plane/commands.go index 01f5163bc3..e2bcb0f693 100644 --- a/control-plane/commands.go +++ b/control-plane/commands.go @@ -6,8 +6,6 @@ package main import ( "os" - "github.com/mitchellh/cli" - cmdACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/acl-init" cmdConnectInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/connect-init" cmdConsulLogout "github.com/hashicorp/consul-k8s/control-plane/subcommand/consul-logout" @@ -20,7 +18,6 @@ import ( cmdGossipEncryptionAutogenerate "github.com/hashicorp/consul-k8s/control-plane/subcommand/gossip-encryption-autogenerate" cmdInjectConnect "github.com/hashicorp/consul-k8s/control-plane/subcommand/inject-connect" cmdInstallCNI "github.com/hashicorp/consul-k8s/control-plane/subcommand/install-cni" - cmdMeshInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/mesh-init" cmdPartitionInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/partition-init" cmdServerACLInit "github.com/hashicorp/consul-k8s/control-plane/subcommand/server-acl-init" cmdSyncCatalog "github.com/hashicorp/consul-k8s/control-plane/subcommand/sync-catalog" @@ -28,6 +25,7 @@ import ( cmdVersion "github.com/hashicorp/consul-k8s/control-plane/subcommand/version" webhookCertManager "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager" "github.com/hashicorp/consul-k8s/control-plane/version" + "github.com/mitchellh/cli" ) // Commands is the mapping of all available consul-k8s commands. @@ -45,10 +43,6 @@ func init() { return &cmdConnectInit.Command{UI: ui}, nil }, - "mesh-init": func() (cli.Command, error) { - return &cmdMeshInit.Command{UI: ui}, nil - }, - "inject-connect": func() (cli.Command, error) { return &cmdInjectConnect.Command{UI: ui}, nil }, diff --git a/control-plane/config-entries/controllersv2/grpc_route_controller.go b/control-plane/config-entries/controllersv2/grpc_route_controller.go deleted file mode 100644 index 06accae268..0000000000 --- a/control-plane/config-entries/controllersv2/grpc_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// GRPCRouteController reconciles a GRPCRoute object. -type GRPCRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=grpcroute/status,verbs=get;update;patch - -func (r *GRPCRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.GRPCRoute{}) -} - -func (r *GRPCRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *GRPCRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *GRPCRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.GRPCRoute{}, r) -} diff --git a/control-plane/config-entries/controllersv2/http_route_controller.go b/control-plane/config-entries/controllersv2/http_route_controller.go deleted file mode 100644 index 545250af74..0000000000 --- a/control-plane/config-entries/controllersv2/http_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// HTTPRouteController reconciles a HTTPRoute object. -type HTTPRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=httproute/status,verbs=get;update;patch - -func (r *HTTPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.HTTPRoute{}) -} - -func (r *HTTPRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *HTTPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *HTTPRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.HTTPRoute{}, r) -} diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller.go b/control-plane/config-entries/controllersv2/meshconfig_controller.go deleted file mode 100644 index c2b2501ebc..0000000000 --- a/control-plane/config-entries/controllersv2/meshconfig_controller.go +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - "fmt" - "time" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul/proto-public/pbresource" - "golang.org/x/time/rate" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - corev1 "k8s.io/api/core/v1" - k8serr "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/workqueue" - "k8s.io/utils/strings/slices" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - tenancy "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - FinalizerName = "finalizers.consul.hashicorp.com" - ConsulAgentError = "ConsulAgentError" - ExternallyManagedConfigError = "ExternallyManagedConfigError" -) - -// Controller is implemented by CRD-specific config-entries. It is used by -// MeshConfigController to abstract CRD-specific config-entries. -type Controller interface { - // Update updates the state of the whole object. - Update(context.Context, client.Object, ...client.UpdateOption) error - // UpdateStatus updates the state of just the object's status. - UpdateStatus(context.Context, client.Object, ...client.SubResourceUpdateOption) error - // Get retrieves an obj for the given object key from the Kubernetes Cluster. - // obj must be a struct pointer so that obj can be updated with the response - // returned by the Server. - Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error - // Logger returns a logger with values added for the specific controller - // and request name. - Logger(types.NamespacedName) logr.Logger -} - -// MeshConfigController is a generic controller that is used to reconcile -// all resource types, e.g. TrafficPermissions, ProxyConfiguration, etc., since -// they share the same reconcile behaviour. -type MeshConfigController struct { - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - - common.ConsulTenancyConfig -} - -// ReconcileEntry reconciles an update to a resource. CRD-specific controller's -// call this function because it handles reconciliation of config entries -// generically. -// CRD-specific controller should pass themselves in as updater since we -// need to call back into their own update methods to ensure they update their -// internal state. -func (r *MeshConfigController) ReconcileEntry(ctx context.Context, crdCtrl Controller, req ctrl.Request, meshConfig common.MeshConfig) (ctrl.Result, error) { - logger := crdCtrl.Logger(req.NamespacedName) - err := crdCtrl.Get(ctx, req.NamespacedName, meshConfig) - if k8serr.IsNotFound(err) { - return ctrl.Result{}, client.IgnoreNotFound(err) - } else if err != nil { - logger.Error(err, "retrieving resource") - return ctrl.Result{}, err - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - logger.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - logger.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - if meshConfig.GetDeletionTimestamp().IsZero() { - // The object is not being deleted, so if it does not have our finalizer, - // then let's add the finalizer and update the object. This is equivalent - // registering our finalizer. - if !slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { - meshConfig.AddFinalizer(FinalizerName) - if err := r.syncUnknown(ctx, crdCtrl, meshConfig); err != nil { - return ctrl.Result{}, err - } - } - } - - if !meshConfig.GetDeletionTimestamp().IsZero() { - if slices.Contains(meshConfig.GetFinalizers(), FinalizerName) { - // The object is being deleted - logger.Info("deletion event") - // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - - // Ignore the error where the resource isn't found in Consul. - // It is indicative of desired state. - if err != nil && !isNotFoundErr(err) { - return ctrl.Result{}, fmt.Errorf("getting resource from Consul: %w", err) - } - - // In the case this resource was created outside of consul, skip the deletion process and continue - if !managedByMeshController(res.GetResource()) { - logger.Info("resource in Consul was created outside of kubernetes - skipping delete from Consul") - } - - if err == nil && managedByMeshController(res.GetResource()) { - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, - fmt.Errorf("deleting config entry from consul: %w", err)) - } - logger.Info("deletion from Consul successful") - } - // remove our finalizer from the list and update it. - meshConfig.RemoveFinalizer(FinalizerName) - if err := crdCtrl.Update(ctx, meshConfig); err != nil { - return ctrl.Result{}, err - } - logger.Info("finalizer removed") - } - - // Stop reconciliation as the item is being deleted - return ctrl.Result{}, nil - } - - // Check to see if consul has config entry with the same name - res, err := resourceClient.Read(ctx, &pbresource.ReadRequest{Id: meshConfig.ResourceID(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - - // In the case the namespace doesn't exist in Consul yet, assume we are racing with the namespace controller - // and requeue. - if tenancy.ConsulNamespaceIsNotFound(err) { - logger.Info("Consul namespace not found; re-queueing request", - "name", req.Name, "ns", req.Namespace, "consul-ns", - r.consulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - - // If resource with this name does not exist - if isNotFoundErr(err) { - logger.Info("resource not found in consul") - - // Create the config entry - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, - fmt.Errorf("writing resource to consul: %w", err)) - } - - logger.Info("resource created") - return r.syncSuccessful(ctx, crdCtrl, meshConfig) - } - - // If there is an error when trying to get the resource from the api server, - // fail the reconcile. - if err != nil { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, err) - } - - // TODO: consider the case where we want to migrate a resource existing into Consul to a CRD with an annotation - if !managedByMeshController(res.Resource) { - return r.syncFailed(ctx, logger, crdCtrl, meshConfig, ExternallyManagedConfigError, - fmt.Errorf("resource already exists in Consul")) - } - - if !meshConfig.MatchesConsul(res.Resource, r.consulNamespace(req.Namespace), r.getConsulPartition()) { - logger.Info("resource does not match Consul") - _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: meshConfig.Resource(r.consulNamespace(req.Namespace), r.getConsulPartition())}) - if err != nil { - return r.syncUnknownWithError(ctx, logger, crdCtrl, meshConfig, ConsulAgentError, - fmt.Errorf("updating resource in Consul: %w", err)) - } - logger.Info("config entry updated") - return r.syncSuccessful(ctx, crdCtrl, meshConfig) - } else if meshConfig.SyncedConditionStatus() != corev1.ConditionTrue { - return r.syncSuccessful(ctx, crdCtrl, meshConfig) - } - - return ctrl.Result{}, nil -} - -// setupWithManager sets up the controller manager for the given resource -// with our default options. -func setupWithManager(mgr ctrl.Manager, resource client.Object, reconciler reconcile.Reconciler) error { - options := controller.Options{ - // Taken from https://github.com/kubernetes/client-go/blob/master/util/workqueue/default_rate_limiters.go#L39 - // and modified from a starting backoff of 5ms and max of 1000s to a - // starting backoff of 200ms and a max of 5s to better fit our most - // common error cases and performance characteristics. - // - // One common error case is that a resource is applied that requires - // a protocol like http or grpc. Often the user will apply a new resource - // to set the protocol in a minute or two. During this time, the - // default backoff could then be set up to 5m or more which means the - // original resource takes a long time to re-sync. - // - // In terms of performance, Consul servers can handle tens of thousands - // of writes per second, so retrying at max every 5s isn't an issue and - // provides a better UX. - RateLimiter: workqueue.NewMaxOfRateLimiter( - workqueue.NewItemExponentialFailureRateLimiter(200*time.Millisecond, 5*time.Second), - // 10 qps, 100 bucket size. This is only for retry speed, and it's only the overall factor (not per item) - &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)}, - ), - } - - return ctrl.NewControllerManagedBy(mgr). - For(resource). - WithOptions(options). - Complete(reconciler) -} - -func (r *MeshConfigController) syncFailed(ctx context.Context, logger logr.Logger, updater Controller, resource common.MeshConfig, errType string, err error) (ctrl.Result, error) { - resource.SetSyncedCondition(corev1.ConditionFalse, errType, err.Error()) - if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { - // Log the original error here because we are returning the updateErr. - // Otherwise, the original error would be lost. - logger.Error(err, "sync failed") - return ctrl.Result{}, updateErr - } - return ctrl.Result{}, err -} - -func (r *MeshConfigController) syncSuccessful(ctx context.Context, updater Controller, resource common.MeshConfig) (ctrl.Result, error) { - resource.SetSyncedCondition(corev1.ConditionTrue, "", "") - timeNow := metav1.NewTime(time.Now()) - resource.SetLastSyncedTime(&timeNow) - return ctrl.Result{}, updater.UpdateStatus(ctx, resource) -} - -func (r *MeshConfigController) syncUnknown(ctx context.Context, updater Controller, resource common.MeshConfig) error { - resource.SetSyncedCondition(corev1.ConditionUnknown, "", "") - return updater.Update(ctx, resource) -} - -func (r *MeshConfigController) syncUnknownWithError(ctx context.Context, - logger logr.Logger, - updater Controller, - resource common.MeshConfig, - errType string, - err error) (ctrl.Result, error) { - - resource.SetSyncedCondition(corev1.ConditionUnknown, errType, err.Error()) - if updateErr := updater.UpdateStatus(ctx, resource); updateErr != nil { - // Log the original error here because we are returning the updateErr. - // Otherwise, the original error would be lost. - logger.Error(err, "sync status unknown") - return ctrl.Result{}, updateErr - } - return ctrl.Result{}, err -} - -// isNotFoundErr checks the grpc response code for "NotFound". -func isNotFoundErr(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - if !ok { - return false - } - return codes.NotFound == s.Code() -} - -func (r *MeshConfigController) consulNamespace(namespace string) string { - ns := namespaces.ConsulNamespace( - namespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *MeshConfigController) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func managedByMeshController(resource *pbresource.Resource) bool { - if resource == nil { - return false - } - - consulMeta := resource.GetMetadata() - if consulMeta == nil { - return false - } - - if val, ok := consulMeta[common.SourceKey]; ok && val == common.SourceValue { - return true - } - return false -} diff --git a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go b/control-plane/config-entries/controllersv2/meshconfig_controller_test.go deleted file mode 100644 index e95e722ea6..0000000000 --- a/control-plane/config-entries/controllersv2/meshconfig_controller_test.go +++ /dev/null @@ -1,814 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - "testing" - "time" - - "github.com/go-logr/logr" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -type testReconciler interface { - Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) -} - -// TestMeshConfigController_createsMeshConfig validated resources are created in Consul from kube objects. -func TestMeshConfigController_createsMeshConfig(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - meshConfig common.MeshConfig - expected *pbauth.TrafficPermissions - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message - }{ - { - name: "TrafficPermissions", - meshConfig: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-traffic-permission", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - expected: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - { - Namespace: "the space namespace space", - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { - data := resource.Data - - trafficPermission := &pbauth.TrafficPermissions{} - require.NoError(t, data.UnmarshalTo(trafficPermission)) - return trafficPermission - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.meshConfig) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.meshConfig.KubernetesName(), - } - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := resourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) - - actual := c.unmarshal(t, res.GetResource()) - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(c.expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the status is "synced". - err = fakeClient.Get(ctx, namespacedName, c.meshConfig) - require.NoError(t, err) - require.Equal(t, corev1.ConditionTrue, c.meshConfig.SyncedConditionStatus()) - - // Check that the finalizer is added. - require.Contains(t, c.meshConfig.Finalizers(), FinalizerName) - }) - } -} - -func TestMeshConfigController_updatesMeshConfig(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - meshConfig common.MeshConfig - expected *pbauth.TrafficPermissions - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - updateF func(config common.MeshConfig) - unmarshal func(t *testing.T, consul *pbresource.Resource) proto.Message - }{ - { - name: "TrafficPermissions", - meshConfig: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-traffic-permission", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - expected: &pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_DENY, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - updateF: func(resource common.MeshConfig) { - trafficPermissions := resource.(*v2beta1.TrafficPermissions) - trafficPermissions.Spec.Action = pbauth.Action_ACTION_DENY - trafficPermissions.Spec.Permissions[0].Sources = trafficPermissions.Spec.Permissions[0].Sources[:1] - }, - unmarshal: func(t *testing.T, resource *pbresource.Resource) proto.Message { - data := resource.Data - - trafficPermission := &pbauth.TrafficPermissions{} - require.NoError(t, data.UnmarshalTo(trafficPermission)) - return trafficPermission - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v1alpha1.GroupVersion, c.meshConfig) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.meshConfig).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // We haven't run reconcile yet, so we must create the MeshConfig - // in Consul ourselves. - { - resource := c.meshConfig.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile which should update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.meshConfig.KubernetesName(), - } - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, c.meshConfig) - require.NoError(t, err) - - // Update the entry in Kube and run reconcile. - c.updateF(c.meshConfig) - err = fakeClient.Update(ctx, c.meshConfig) - require.NoError(t, err) - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - resp, err := r.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.meshConfig.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - res, err := resourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, c.meshConfig.GetName(), res.GetResource().GetId().GetName()) - - actual := c.unmarshal(t, res.GetResource()) - opts := append([]cmp.Option{protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version")}, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(c.expected, actual, opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - } - }) - } -} - -func TestMeshConfigController_deletesMeshConfig(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - MeshConfigWithDeletion common.MeshConfig - reconciler func(client.Client, *consul.Config, consul.ServerConnectionManager, logr.Logger) testReconciler - }{ - { - name: "TrafficPermissions", - MeshConfigWithDeletion: &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-name", - Namespace: metav1.NamespaceDefault, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - Namespace: "the space namespace space", - }, - { - IdentityName: "source-identity", - }, - }, - DestinationRules: []*pbauth.DestinationRule{ - { - PathExact: "/hello", - Methods: []string{"GET", "POST"}, - PortNames: []string{"web", "admin"}, - }, - }, - }, - }, - }, - }, - reconciler: func(client client.Client, cfg *consul.Config, watcher consul.ServerConnectionManager, logger logr.Logger) testReconciler { - return &TrafficPermissionsController{ - Client: client, - Log: logger, - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: cfg, - ConsulServerConnMgr: watcher, - }, - } - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - ctx := context.Background() - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, c.MeshConfigWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(c.MeshConfigWithDeletion).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // We haven't run reconcile yet, so we must create the config entry - // in Consul ourselves. - { - resource := c.MeshConfigWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile. It's marked for deletion so this should delete it. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: c.MeshConfigWithDeletion.KubernetesName(), - } - r := c.reconciler(fakeClient, testClient.Cfg, testClient.Watcher, logrtest.New(t)) - resp, err := r.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: c.MeshConfigWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - _, err = resourceClient.Read(ctx, req) - require.Error(t, err) - require.True(t, isNotFoundErr(err)) - } - }) - } -} - -func TestMeshConfigController_errorUpdatesSyncStatus(t *testing.T) { - t.Parallel() - - ctx := context.Background() - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - } - - s := runtime.NewScheme() - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Stop the server before calling reconcile imitating a server that's not running. - _ = testClient.TestServer.Stop() - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // ReconcileEntry should result in an error. - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.Error(t, err) - require.False(t, resp.Requeue) - actualErrMsg := err.Error() - - // Check that the status is "synced=false". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - status, reason, errMsg := trafficpermissions.SyncedCondition() - require.Equal(t, corev1.ConditionFalse, status) - require.Equal(t, "ConsulAgentError", reason) - require.Contains(t, errMsg, actualErrMsg) -} - -// TestMeshConfigController_setsSyncedToTrue tests that if the resource hasn't changed in -// Consul but our resource's synced status isn't set to true, then we update its status. -func TestMeshConfigController_setsSyncedToTrue(t *testing.T) { - t.Parallel() - - ctx := context.Background() - s := runtime.NewScheme() - - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - }, - }, - }, - }, - }, - Status: v2beta1.Status{ - Conditions: v2beta1.Conditions{ - { - Type: v2beta1.ConditionSynced, - Status: corev1.ConditionUnknown, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - - // The config entry exists in kube but its status will be nil. - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - // Create the resource in Consul to mimic that it was created - // successfully (but its status hasn't been updated). - { - resource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - req := &pbresource.WriteRequest{Resource: resource} - _, err := resourceClient.Write(ctx, req) - require.NoError(t, err) - } - - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Check that the status is now "synced". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - require.Equal(t, corev1.ConditionTrue, trafficpermissions.SyncedConditionStatus()) -} - -// TestMeshConfigController_doesNotCreateUnownedMeshConfig test that if the resource -// exists in Consul but is not managed by the controller, creating/updating the resource fails. -func TestMeshConfigController_doesNotCreateUnownedMeshConfig(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - s := runtime.NewScheme() - trafficpermissions := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissions) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissions).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - unmanagedResource := trafficpermissions.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata - - // We haven't run reconcile yet. We must create the resource - // in Consul ourselves, without the metadata indicating it is owned by the controller. - { - req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := resourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile which should **not** update the entry in Consul. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissions.KubernetesName(), - } - // First get it, so we have the latest revision number. - err := fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - - // Attempt to create the entry in Kube and run reconcile. - reconciler := TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.EqualError(t, err, "resource already exists in Consul") - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: trafficpermissions.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := resourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, readResp.GetResource()) - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, - test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the status is "synced=false". - err = fakeClient.Get(ctx, namespacedName, trafficpermissions) - require.NoError(t, err) - status, reason, errMsg := trafficpermissions.SyncedCondition() - require.Equal(t, corev1.ConditionFalse, status) - require.Equal(t, "ExternallyManagedConfigError", reason) - require.Equal(t, errMsg, "resource already exists in Consul") - } - -} - -// TestMeshConfigController_doesNotDeleteUnownedConfig tests that if the resource -// exists in Consul but is not managed by the controller, deleting the resource does -// not delete the Consul resource. -func TestMeshConfigController_doesNotDeleteUnownedConfig(t *testing.T) { - t.Parallel() - - ctx := context.Background() - s := runtime.NewScheme() - - trafficpermissionsWithDeletion := &v2beta1.TrafficPermissions{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - DeletionTimestamp: &metav1.Time{Time: time.Now()}, - Finalizers: []string{FinalizerName}, - }, - Spec: pbauth.TrafficPermissions{ - Destination: &pbauth.Destination{ - IdentityName: "destination-identity", - }, - Action: pbauth.Action_ACTION_ALLOW, - Permissions: []*pbauth.Permission{ - { - Sources: []*pbauth.Source{ - { - IdentityName: "source-identity", - Namespace: common.DefaultConsulNamespace, - Partition: common.DefaultConsulPartition, - Peer: constants.DefaultConsulPeer, - }, - }, - }, - }, - }, - } - s.AddKnownTypes(v2beta1.AuthGroupVersion, trafficpermissionsWithDeletion) - fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(trafficpermissionsWithDeletion).Build() - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - reconciler := &TrafficPermissionsController{ - Client: fakeClient, - Log: logrtest.New(t), - MeshConfigController: &MeshConfigController{ - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - }, - } - - unmanagedResource := trafficpermissionsWithDeletion.Resource(constants.DefaultConsulNS, constants.DefaultConsulPartition) - unmanagedResource.Metadata = make(map[string]string) // Zero out the metadata - - // We haven't run reconcile yet. We must create the resource - // in Consul ourselves, without the metadata indicating it is owned by the controller. - { - req := &pbresource.WriteRequest{Resource: unmanagedResource} - _, err := resourceClient.Write(ctx, req) - require.NoError(t, err) - } - - // Now run reconcile. It's marked for deletion so this should delete the kubernetes resource - // but not the consul config entry. - { - namespacedName := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: trafficpermissionsWithDeletion.KubernetesName(), - } - resp, err := reconciler.Reconcile(ctx, ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - // Now check that the object in Consul is as expected. - req := &pbresource.ReadRequest{Id: trafficpermissionsWithDeletion.ResourceID(constants.DefaultConsulNS, constants.DefaultConsulPartition)} - readResp, err := resourceClient.Read(ctx, req) - require.NoError(t, err) - require.NotNil(t, readResp.GetResource()) - opts := append([]cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid")}, - test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(unmanagedResource, readResp.GetResource(), opts...) - require.Equal(t, "", diff, "TrafficPermissions do not match") - - // Check that the resource is deleted from cluster. - tp := &v2beta1.TrafficPermissions{} - _ = fakeClient.Get(ctx, namespacedName, tp) - require.Empty(t, tp.Finalizers()) - } -} diff --git a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go b/control-plane/config-entries/controllersv2/proxy_configuration_controller.go deleted file mode 100644 index f45dda1962..0000000000 --- a/control-plane/config-entries/controllersv2/proxy_configuration_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// ProxyConfigurationController reconciles a ProxyConfiguration object. -type ProxyConfigurationController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=proxyconfiguration/status,verbs=get;update;patch - -func (r *ProxyConfigurationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.ProxyConfiguration{}) -} - -func (r *ProxyConfigurationController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *ProxyConfigurationController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *ProxyConfigurationController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.ProxyConfiguration{}, r) -} diff --git a/control-plane/config-entries/controllersv2/tcp_route_controller.go b/control-plane/config-entries/controllersv2/tcp_route_controller.go deleted file mode 100644 index 170dbb9fd4..0000000000 --- a/control-plane/config-entries/controllersv2/tcp_route_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" -) - -// TCPRouteController reconciles a TCPRoute object. -type TCPRouteController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController -} - -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=mesh.consul.hashicorp.com,resources=tcproute/status,verbs=get;update;patch - -func (r *TCPRouteController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &meshv2beta1.TCPRoute{}) -} - -func (r *TCPRouteController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *TCPRouteController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *TCPRouteController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &meshv2beta1.TCPRoute{}, r) -} diff --git a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go b/control-plane/config-entries/controllersv2/traffic_permissions_controller.go deleted file mode 100644 index abcaf98906..0000000000 --- a/control-plane/config-entries/controllersv2/traffic_permissions_controller.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package controllersv2 - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - consulv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" -) - -// TrafficPermissionsController reconciles a TrafficPermissions object. -type TrafficPermissionsController struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - MeshConfigController *MeshConfigController -} - -// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=auth.consul.hashicorp.com,resources=trafficpermissions/status,verbs=get;update;patch - -func (r *TrafficPermissionsController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - return r.MeshConfigController.ReconcileEntry(ctx, r, req, &consulv2beta1.TrafficPermissions{}) -} - -func (r *TrafficPermissionsController) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *TrafficPermissionsController) UpdateStatus(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { - return r.Status().Update(ctx, obj, opts...) -} - -func (r *TrafficPermissionsController) SetupWithManager(mgr ctrl.Manager) error { - return setupWithManager(mgr, &consulv2beta1.TrafficPermissions{}, r) -} diff --git a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml b/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml deleted file mode 100644 index 3a7699dce4..0000000000 --- a/control-plane/config/crd/bases/auth.consul.hashicorp.com_trafficpermissions.yaml +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: trafficpermissions.auth.consul.hashicorp.com -spec: - group: auth.consul.hashicorp.com - names: - kind: TrafficPermissions - listKind: TrafficPermissionsList - plural: trafficpermissions - shortNames: - - traffic-permissions - singular: trafficpermissions - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TrafficPermissions is the Schema for the traffic-permissions - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - properties: - action: - description: "Action can be either allow or deny for the entire object. - It will default to allow. \n If action is allow, we will allow the - connection if one of the rules in Rules matches, in other words, - we will deny all requests except for the ones that match Rules. - If Consul is in default allow mode, then allow actions have no effect - without a deny permission as everything is allowed by default. \n - If action is deny, we will deny the connection if one of the rules - in Rules match, in other words, we will allow all requests except - for the ones that match Rules. If Consul is default deny mode, then - deny permissions have no effect without an allow permission as everything - is denied by default. \n Action unspecified is reserved for compatibility - with the addition of future actions." - enum: - - ACTION_ALLOW - - ACTION_DENY - - ACTION_UNKNOWN - format: int32 - type: string - destination: - description: Destination is a configuration of the destination proxies - where these traffic permissions should apply. - properties: - identityName: - type: string - type: object - permissions: - description: Permissions is a list of permissions to match on. They - are applied using OR semantics. - items: - description: Permissions is a list of permissions to match on. - properties: - destinationRules: - description: DestinationRules is a list of rules to apply for - matching sources in this Permission. These rules are specific - to the request or connection that is going to the destination(s) - selected by the TrafficPermissions resource. - items: - description: DestinationRule contains rules rules to apply - to the incoming connection. - properties: - exclude: - description: Exclude contains a list of rules to exclude - when evaluating rules for the incoming connection. - items: - properties: - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - description: PortNames is a list of workload ports - to apply this rule to. The ports specified here - must be the ports used in the connection. - items: - type: string - type: array - type: object - type: array - header: - properties: - exact: - type: string - invert: - type: boolean - name: - type: string - prefix: - type: string - present: - type: boolean - regex: - type: string - suffix: - type: string - type: object - methods: - description: Methods is the list of HTTP methods. If no - methods are specified, this rule will apply to all methods. - items: - type: string - type: array - pathExact: - type: string - pathPrefix: - type: string - pathRegex: - type: string - portNames: - items: - type: string - type: array - type: object - type: array - sources: - description: Sources is a list of sources in this traffic permission. - items: - description: Source represents the source identity. To specify - any of the wildcard sources, the specific fields need to - be omitted. For example, for a wildcard namespace, identity_name - should be omitted. - properties: - exclude: - description: Exclude is a list of sources to exclude from - this source. - items: - description: ExcludeSource is almost the same as source - but it prevents the addition of matching sources. - properties: - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - identityName: - type: string - namespace: - type: string - partition: - type: string - peer: - type: string - samenessGroup: - type: string - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml index 49fc1ae135..f74743655e 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_controlplanerequestlimits.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: controlplanerequestlimits.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -188,3 +190,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml index 22f816cb18..0b6b969856 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_exportedservices.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: exportedservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -132,3 +134,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml index ff3158f2a7..c4a510ffad 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_gatewayclassconfigs.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: gatewayclassconfigs.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -194,3 +196,9 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml deleted file mode 100644 index e12db4cf20..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_gatewaypolicies.yaml +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: gatewaypolicies.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: GatewayPolicy - listKind: GatewayPolicyList - plural: gatewaypolicies - singular: gatewaypolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: GatewayPolicy is the Schema for the gatewaypolicies API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GatewayPolicySpec defines the desired state of GatewayPolicy. - properties: - default: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - override: - properties: - jwt: - description: GatewayJWTRequirement holds the list of JWT providers - to be verified against. - properties: - providers: - description: Providers is a list of providers to consider - when verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry - with this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the - actual claim information to be verified. - properties: - path: - description: Path is the path to the claim in - the token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the - given path: - If the type at the path is a list - then we verify that this value is contained - in the list. \n - If the type at the path is - a string then we verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - targetRef: - description: TargetRef identifies an API object to apply policy to. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - minLength: 1 - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 253 - minLength: 1 - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace is the namespace of the referent. When - unspecified, the local namespace is inferred. Even when policy - targets a resource in a different namespace, it may only apply - to traffic originating from the same namespace as the policy. - maxLength: 253 - minLength: 1 - type: string - sectionName: - description: SectionName refers to the listener targeted by this - policy. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - group - - kind - - name - type: object - required: - - targetRef - type: object - status: - description: GatewayPolicyStatus defines the observed state of the gateway. - properties: - conditions: - description: "Conditions describe the current conditions of the Policy. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml index 79450327cb..e9994d8457 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_ingressgateways.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: ingressgateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -440,3 +442,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml index df234ae1eb..8584404c56 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_jwtproviders.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: jwtproviders.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -105,7 +107,8 @@ spec: cacheDuration: description: "CacheDuration is the duration after which cached keys should be expired. \n Default value is 5 minutes." - type: string + format: int64 + type: integer fetchAsynchronously: description: "FetchAsynchronously indicates that the JWKS should be fetched when a client request arrives. Client @@ -114,60 +117,61 @@ spec: before being activated. \n Default value is false." type: boolean jwksCluster: - description: JWKSCluster defines how the specified Remote - JWKS URI is to be fetched. + description: "JWKSCluster defines how the specified Remote JWKS + URI is to be fetched." properties: connectTimeout: - description: The timeout for new network connections to - hosts in the cluster. If not set, a default value of - 5s will be used. - type: string + description: "The timeout for new network connections to hosts + in the cluster. \n If not set, a default value of 5s will be + used." + format: int64 + type: integer discoveryType: - description: "DiscoveryType refers to the service discovery - type to use for resolving the cluster. \n This defaults - to STRICT_DNS. Other options include STATIC, LOGICAL_DNS, - EDS or ORIGINAL_DST." + description: "DiscoveryType refers to the service discovery type + to use for resolving the cluster. \n Defaults to STRICT_DNS." type: string tlsCertificates: description: "TLSCertificates refers to the data containing - certificate authority certificates to use in verifying - a presented peer certificate. If not specified and a - peer certificate is presented it will not be verified. - \n Must be either CaCertificateProviderInstance or TrustedCA." + certificate authority certificates to use in verifying a presented + peer certificate." properties: caCertificateProviderInstance: - description: CaCertificateProviderInstance Certificate - provider instance for fetching TLS certificates. + description: "CaCertificateProviderInstance Certificate provider + instance for fetching TLS certificates." properties: - certificateName: - description: "CertificateName is used to specify - certificate instances or types. For example, - \"ROOTCA\" to specify a root-certificate (validation - context) or \"example.com\" to specify a certificate - for a particular domain. \n The default value - is the empty string." - type: string instanceName: - description: "InstanceName refers to the certificate - provider instance name. \n The default value - is \"default\"." + description: "InstanceName refers to the certificate provider + instance name. \n The default value is 'default'." + type: string + certificateName: + description: "CertificateName is used to specify certificate + instances or types. For example, \"ROOTCA\" to specify a + root-certificate (validation context) or \"example.com\" + to specify a certificate for a particular domain. \n + The default value is the empty string." type: string type: object trustedCA: - description: "TrustedCA defines TLS certificate data - containing certificate authority certificates to - use in verifying a presented peer certificate. \n - Exactly one of Filename, EnvironmentVariable, InlineString - or InlineBytes must be specified." + description: "TrustedCA defines TLS certificate data containing + certificate authority certificates to use in verifying a presented + peer certificate. \n Exactly one of Filename, EnvironmentVariable, + InlineString or InlineBytes must be specified." properties: + filename: + description: "The name of the file on the local system to use a + data source for trusted CA certificates." + type: string environmentVariable: + description: "The environment variable on the local system to use + a data source for trusted CA certificates." type: string - filename: + inlineString: + description: "A string to inline in the configuration for use as + a data source for trusted CA certificates." type: string inlineBytes: - format: byte - type: string - inlineString: + description: "A sequence of bytes to inline in the configuration + for use as a data source for trusted CA certificates." type: string type: object type: object @@ -187,20 +191,22 @@ spec: of 10s. \n Default value is 0." type: integer retryPolicyBackOff: - description: "Retry's backoff policy. \n Defaults to Envoy's - backoff policy." + description: "Backoff policy \n Defaults to Envoy's backoff + policy" properties: baseInterval: description: "BaseInterval to be used for the next - back off computation. \n The default value from - envoy is 1s." - type: string + back off computation \n The default value from envoy + is 1s" + format: int64 + type: integer maxInterval: description: "MaxInternal to be used to specify the maximum interval between retries. Optional but should be greater or equal to BaseInterval. \n Defaults - to 10 times BaseInterval." - type: string + to 10 times BaseInterval" + format: int64 + type: integer type: object type: object uri: @@ -306,3 +312,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml index 3c22a4842e..adbb12bba6 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshes.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshes.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -200,3 +202,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml index 9eccd85cad..04f8f493e7 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_meshservices.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: meshservices.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -49,3 +51,9 @@ spec: type: object served: true storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml index b568a94962..50df179f04 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringacceptors.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringacceptors.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -139,3 +141,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml index ebf64adf67..01e4363f14 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_peeringdialers.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: peeringdialers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -139,3 +141,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml index 20f2faeb63..7084980bf0 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_proxydefaults.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: proxydefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -186,16 +188,6 @@ spec: your services secure, we recommend using "strict" mode whenever possible and enabling "permissive" mode only when necessary.' type: string - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -258,3 +250,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml deleted file mode 100644 index 5072fdf391..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeauthfilters.yaml +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routeauthfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteAuthFilter - listKind: RouteAuthFilterList - plural: routeauthfilters - singular: routeauthfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteAuthFilter is the Schema for the routeauthfilters API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteAuthFilterSpec defines the desired state of RouteAuthFilter. - properties: - jwt: - description: This re-uses the JWT requirement type from Gateway Policy - Types. - properties: - providers: - description: Providers is a list of providers to consider when - verifying a JWT. - items: - description: GatewayJWTProvider holds the provider and claim - verification information. - properties: - name: - description: Name is the name of the JWT provider. There - MUST be a corresponding "jwt-provider" config entry with - this name. - type: string - verifyClaims: - description: VerifyClaims is a list of additional claims - to verify in a JWT's payload. - items: - description: GatewayJWTClaimVerification holds the actual - claim information to be verified. - properties: - path: - description: Path is the path to the claim in the - token JSON. - items: - type: string - type: array - value: - description: "Value is the expected value at the given - path: - If the type at the path is a list then we - verify that this value is contained in the list. - \n - If the type at the path is a string then we - verify that this value matches." - type: string - required: - - path - - value - type: object - type: array - required: - - name - type: object - type: array - required: - - providers - type: object - type: object - status: - description: RouteAuthFilterStatus defines the observed state of the gateway. - properties: - conditions: - default: - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: Accepted - - lastTransitionTime: "1970-01-01T00:00:00Z" - message: Waiting for controller - reason: Pending - status: Unknown - type: ResolvedRefs - description: "Conditions describe the current conditions of the Filter. - \n Known condition types are: \n * \"Accepted\" * \"ResolvedRefs\"" - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml deleted file mode 100644 index 8fa61cb683..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routeretryfilters.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routeretryfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteRetryFilter - listKind: RouteRetryFilterList - plural: routeretryfilters - singular: routeretryfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteRetryFilter is the Schema for the routeretryfilters API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteRetryFilterSpec defines the desired state of RouteRetryFilter. - properties: - numRetries: - format: int32 - minimum: 0 - type: integer - retryOn: - items: - type: string - type: array - retryOnConnectFailure: - type: boolean - retryOnStatusCodes: - items: - format: int32 - type: integer - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml deleted file mode 100644 index 0822050cb2..0000000000 --- a/control-plane/config/crd/bases/consul.hashicorp.com_routetimeoutfilters.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: routetimeoutfilters.consul.hashicorp.com -spec: - group: consul.hashicorp.com - names: - kind: RouteTimeoutFilter - listKind: RouteTimeoutFilterList - plural: routetimeoutfilters - singular: routetimeoutfilter - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: RouteTimeoutFilter is the Schema for the httproutetimeoutfilters - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: RouteTimeoutFilterSpec defines the desired state of RouteTimeoutFilter. - properties: - idleTimeout: - type: string - requestTimeout: - type: string - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml index 4274efffc8..c71a211f63 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_samenessgroups.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: samenessgroups.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -122,3 +124,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml index 7e7bcfaacc..d4d639e55c 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicedefaults.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicedefaults.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -183,69 +185,6 @@ spec: unlock usage of the service-splitter and service-router config entries for a service. type: string - rateLimits: - description: RateLimits is rate limiting configuration that is applied - to inbound traffic for a service. Rate limiting is a Consul enterprise - feature. - properties: - instanceLevel: - description: InstanceLevel represents rate limit configuration - that is applied per service instance. - properties: - requestsMaxBurst: - description: "RequestsMaxBurst is the maximum number of requests - that can be sent in a burst. Should be equal to or greater - than RequestsPerSecond. If unset, defaults to RequestsPerSecond. - \n Internally, this is the maximum size of the token bucket - used for rate limiting." - type: integer - requestsPerSecond: - description: "RequestsPerSecond is the average number of requests - per second that can be made without being throttled. This - field is required if RequestsMaxBurst is set. The allowed - number of requests may exceed RequestsPerSecond up to the - value specified in RequestsMaxBurst. \n Internally, this - is the refill rate of the token bucket used for rate limiting." - type: integer - routes: - description: Routes is a list of rate limits applied to specific - routes. For a given request, the first matching route will - be applied, if any. Overrides any top-level configuration. - items: - properties: - pathExact: - description: Exact path to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathPrefix: - description: Prefix to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - pathRegex: - description: Regex to match. Exactly one of PathExact, - PathPrefix, or PathRegex must be specified. - type: string - requestsMaxBurst: - description: RequestsMaxBurst is the maximum number - of requests that can be sent in a burst. Should be - equal to or greater than RequestsPerSecond. If unset, - defaults to RequestsPerSecond. Internally, this is - the maximum size of the token bucket used for rate - limiting. - type: integer - requestsPerSecond: - description: RequestsPerSecond is the average number - of requests per second that can be made without being - throttled. This field is required if RequestsMaxBurst - is set. The allowed number of requests may exceed - RequestsPerSecond up to the value specified in RequestsMaxBurst. - Internally, this is the refill rate of the token bucket - used for rate limiting. - type: integer - type: object - type: array - type: object - type: object transparentProxy: description: 'TransparentProxy controls configuration specific to proxies in transparent mode. Note: This cannot be set using the @@ -558,3 +497,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml index 4718ee24e5..a4efd6e958 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceintentions.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceintentions.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -304,3 +306,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml index a1e3844b9c..e869982b59 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_serviceresolvers.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: serviceresolvers.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -221,16 +223,6 @@ spec: type: integer type: object type: object - prioritizeByLocality: - description: PrioritizeByLocality controls whether the locality of - services within the local partition will be used to prioritize connectivity. - properties: - mode: - description: 'Mode specifies the type of prioritization that will - be performed when selecting nodes in the local partition. Valid - values are: "" (default "none"), "none", and "failover".' - type: string - type: object redirect: description: Redirect when configured, all attempts to resolve the service this resolver defines will be substituted for the supplied @@ -341,3 +333,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml index 764b4614f3..31f5ee2924 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicerouters.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicerouters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -305,3 +307,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml index 36f9c9f6c9..aa2b592c94 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_servicesplitters.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: servicesplitters.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -179,3 +181,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml index 7f22c65d09..b465cd9494 100644 --- a/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml +++ b/control-plane/config/crd/bases/consul.hashicorp.com_terminatinggateways.yaml @@ -1,11 +1,13 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.8.0 + creationTimestamp: null name: terminatinggateways.consul.hashicorp.com spec: group: consul.hashicorp.com @@ -130,3 +132,9 @@ spec: storage: true subresources: status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml deleted file mode 100644 index fda3e4255e..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_grpcroutes.yaml +++ /dev/null @@ -1,612 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: grpcroutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: GRPCRoute - listKind: GRPCRouteList - plural: grpcroutes - shortNames: - - grpc-route - singular: grpcroute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: GRPCRoute is the Schema for the GRPC Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this GRPCRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of GRPC matchers, filters and actions. - items: - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. Failure behavior here depends on - how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the GRPCBackendRef definition for the rules about what - makes a single GRPCBackendRef invalid. \n When a GRPCBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies gRPC request header matchers. - Multiple match values are ANDed together, meaning, a - request MUST match all the specified headers to select - the route. - items: - properties: - name: - type: string - type: - description: "HeaderMatchType specifies the semantics - of how HTTP header values should be compared. - Valid HeaderMatchType values, along with their - conformance levels, are: \n Note that values may - be added to this enum, implementations must ensure - that unknown values will not cause a crash. \n - Unknown values here must result in the implementation - setting the Accepted Condition for the Route to - status: False, with a Reason of UnsupportedValue." - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - type: string - type: object - type: array - method: - description: Method specifies a gRPC request service/method - matcher. If this field is not specified, all services - and methods will match. - properties: - method: - description: "Value of the method to match against. - If left empty or omitted, will match all services. - \n At least one of Service and Method MUST be a - non-empty string.}" - type: string - service: - description: "Value of the service to match against. - If left empty or omitted, will match any service. - \n At least one of Service and Method MUST be a - non-empty string." - type: string - type: - description: 'Type specifies how to match against - the service and/or method. Support: Core (Exact - with service and method specified)' - enum: - - GRPC_METHOD_MATCH_TYPE_UNSPECIFIED - - GRPC_METHOD_MATCH_TYPE_EXACT - - GRPC_METHOD_MATCH_TYPE_REGEX - format: int32 - type: string - type: object - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml deleted file mode 100644 index 46bf7162a6..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_httproutes.yaml +++ /dev/null @@ -1,668 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: httproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: HTTPRoute - listKind: HTTPRouteList - plural: httproutes - shortNames: - - http-route - singular: httproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: HTTPRoute is the Schema for the HTTP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute - \n This is a Resource type." - properties: - hostnames: - description: "Hostnames are the hostnames for which this HTTPRoute - should respond to requests. \n This is only valid for north/south." - items: - type: string - type: array - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of HTTP-based routing rules that this - route should use for constructing a routing table. - items: - description: HTTPRouteRule specifies the routing rules used to determine - what upstream service an HTTP request is routed to. - properties: - backendRefs: - description: "BackendRefs defines the backend(s) where matching - requests should be sent. \n Failure behavior here depends - on how many BackendRefs are specified and how many are invalid. - \n If all entries in BackendRefs are invalid, and there are - also no filters specified in this route rule, all traffic - which matches this rule MUST receive a 500 status code. \n - See the HTTPBackendRef definition for the rules about what - makes a single HTTPBackendRef invalid. \n When a HTTPBackendRef - is invalid, 500 status codes MUST be returned for requests - that would have otherwise been routed to an invalid backend. - If multiple backends are specified, and some are invalid, - the proportion of requests that would otherwise have been - routed to an invalid backend MUST receive a 500 status code. - \n For example, if two backends are specified with equal weights, - and one is invalid, 50 percent of traffic must receive a 500. - Implementations may choose how that 50 percent is determined." - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - filters: - description: Filters defined at this level should be executed - if and only if the request is being forwarded to the - backend defined here. - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema - for a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema - for a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, - value) to the request before the action. It - appends to any existing values associated - with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from - the HTTP request before the action. The value - of Remove is a list of HTTP header names. - Note that the header names are case-insensitive - (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with - the given header (name, value) before the - action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - filters: - items: - properties: - requestHeaderModifier: - description: RequestHeaderModifier defines a schema for - a filter that modifies request headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - responseHeaderModifier: - description: ResponseHeaderModifier defines a schema for - a filter that modifies response headers. - properties: - add: - description: Add adds the given header(s) (name, value) - to the request before the action. It appends to - any existing values associated with the header name. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - remove: - description: Remove the given header(s) from the HTTP - request before the action. The value of Remove is - a list of HTTP header names. Note that the header - names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). - items: - type: string - type: array - set: - description: Set overwrites the request with the given - header (name, value) before the action. - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - type: object - urlRewrite: - description: URLRewrite defines a schema for a filter - that modifies a request during forwarding. - properties: - pathPrefix: - type: string - type: object - type: object - type: array - matches: - items: - properties: - headers: - description: Headers specifies HTTP request header matchers. - Multiple match values are ANDed together, meaning, a - request must match all the specified headers to select - the route. - items: - properties: - invert: - description: 'NOTE: not in gamma; service-router - compat' - type: boolean - name: - description: "Name is the name of the HTTP Header - to be matched. Name matching MUST be case insensitive. - (See https://tools.ietf.org/html/rfc7230#section-3.2). - \n If multiple entries specify equivalent header - names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent header name MUST be - ignored. Due to the case-insensitivity of header - names, “foo” and “Foo” are considered equivalent. - \n When a header is repeated in an HTTP request, - it is implementation-specific behavior as to how - this is represented. Generally, proxies should - follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 - regarding processing a repeated header, with special - handling for “Set-Cookie”." - type: string - type: - description: Type specifies how to match against - the value of the header. - enum: - - HEADER_MATCH_TYPE_UNSPECIFIED - - HEADER_MATCH_TYPE_EXACT - - HEADER_MATCH_TYPE_REGEX - - HEADER_MATCH_TYPE_PRESENT - - HEADER_MATCH_TYPE_PREFIX - - HEADER_MATCH_TYPE_SUFFIX - format: int32 - type: string - value: - description: Value is the value of HTTP Header to - be matched. - type: string - type: object - type: array - method: - description: Method specifies HTTP method matcher. When - specified, this route will be matched only if the request - has the specified method. - type: string - path: - description: Path specifies a HTTP request path matcher. - If this field is not specified, a default prefix match - on the “/” path is provided. - properties: - type: - description: Type specifies how to match against the - path Value. - enum: - - PATH_MATCH_TYPE_UNSPECIFIED - - PATH_MATCH_TYPE_EXACT - - PATH_MATCH_TYPE_PREFIX - - PATH_MATCH_TYPE_REGEX - format: int32 - type: string - value: - description: Value of the HTTP path to match against. - type: string - type: object - queryParams: - description: QueryParams specifies HTTP query parameter - matchers. Multiple match values are ANDed together, - meaning, a request must match all the specified query - parameters to select the route. - items: - properties: - name: - description: "Name is the name of the HTTP query - param to be matched. This must be an exact string - match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3). - \n If multiple entries specify equivalent query - param names, only the first entry with an equivalent - name MUST be considered for a match. Subsequent - entries with an equivalent query param name MUST - be ignored. \n If a query param is repeated in - an HTTP request, the behavior is purposely left - undefined, since different data planes have different - capabilities. However, it is recommended that - implementations should match against the first - value of the param if the data plane supports - it, as this behavior is expected in other load - balancing contexts outside of the Gateway API. - \n Users SHOULD NOT route traffic based on repeated - query params to guard themselves against potential - differences in the implementations." - type: string - type: - description: Type specifies how to match against - the value of the query parameter. - enum: - - QUERY_PARAM_MATCH_TYPE_UNSPECIFIED - - QUERY_PARAM_MATCH_TYPE_EXACT - - QUERY_PARAM_MATCH_TYPE_REGEX - - QUERY_PARAM_MATCH_TYPE_PRESENT - format: int32 - type: string - value: - description: Value is the value of HTTP query param - to be matched. - type: string - type: object - type: array - type: object - type: array - retries: - properties: - number: - description: Number is the number of times to retry the - request when a retryable result occurs. - properties: - value: - description: The uint32 value. - format: int32 - type: integer - type: object - onConditions: - description: RetryOn allows setting envoy specific conditions - when a request should be automatically retried. - items: - type: string - type: array - onConnectFailure: - description: RetryOnConnectFailure allows for connection - failure errors to trigger a retry. - type: boolean - onStatusCodes: - description: RetryOnStatusCodes is a flat list of http response - status codes that are eligible for retry. This again should - be feasible in any reasonable proxy. - items: - format: int32 - type: integer - type: array - type: object - timeouts: - description: HTTPRouteTimeouts defines timeouts that can be - configured for an HTTPRoute or GRPCRoute. - properties: - idle: - description: Idle specifies the total amount of time permitted - for the request stream to be idle. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - request: - description: RequestTimeout is the total amount of time - permitted for the entire downstream request (and retries) - to be processed. - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml deleted file mode 100644 index 1d15b34111..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_proxyconfigurations.yaml +++ /dev/null @@ -1,418 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: proxyconfigurations.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: ProxyConfiguration - listKind: ProxyConfigurationList - plural: proxyconfigurations - shortNames: - - proxy-configuration - singular: proxyconfiguration - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: ProxyConfiguration is the Schema for the TCP Routes API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: This is a Resource type. - properties: - bootstrapConfig: - description: bootstrap_config is the configuration that requires proxies - to be restarted to be applied. - properties: - dogstatsdUrl: - type: string - overrideJsonTpl: - type: string - prometheusBindAddr: - type: string - readyBindAddr: - type: string - staticClustersJson: - type: string - staticListenersJson: - type: string - statsBindAddr: - type: string - statsConfigJson: - type: string - statsFlushInterval: - type: string - statsSinksJson: - type: string - statsTags: - items: - type: string - type: array - statsdUrl: - type: string - telemetryCollectorBindSocketDir: - type: string - tracingConfigJson: - type: string - type: object - dynamicConfig: - description: dynamic_config is the configuration that could be changed - dynamically (i.e. without needing restart). - properties: - accessLogs: - description: AccessLogs configures the output and format of Envoy - access logs - properties: - disableListenerLogs: - description: DisableListenerLogs turns off just listener logs - for connections rejected by Envoy because they don't have - a matching listener filter. - type: boolean - enabled: - description: Enabled turns off all access logging - type: boolean - jsonFormat: - description: The presence of one format string or the other - implies the access log string encoding. Defining both is - invalid. - type: string - path: - description: Path is the output file to write logs - type: string - textFormat: - type: string - type: - description: 'Type selects the output for logs: "file", "stderr". - "stdout"' - enum: - - LOG_SINK_TYPE_DEFAULT - - LOG_SINK_TYPE_FILE - - LOG_SINK_TYPE_STDERR - - LOG_SINK_TYPE_STDOUT - format: int32 - type: string - type: object - envoyExtensions: - items: - description: EnvoyExtension has configuration for an extension - that patches Envoy resources. - properties: - arguments: - type: object - x-kubernetes-preserve-unknown-fields: true - consulVersion: - type: string - envoyVersion: - type: string - name: - type: string - required: - type: boolean - type: object - type: array - exposeConfig: - properties: - exposePaths: - items: - properties: - listenerPort: - format: int32 - type: integer - localPathPort: - format: int32 - type: integer - path: - type: string - protocol: - enum: - - EXPOSE_PATH_PROTOCOL_HTTP - - EXPOSE_PATH_PROTOCOL_HTTP2 - format: int32 - type: string - type: object - type: array - type: object - inboundConnections: - description: inbound_connections configures inbound connections - to the proxy. - properties: - balanceInboundConnections: - enum: - - BALANCE_CONNECTIONS_DEFAULT - - BALANCE_CONNECTIONS_EXACT - format: int32 - type: string - maxInboundConnections: - format: int64 - type: integer - type: object - listenerTracingJson: - type: string - localClusterJson: - type: string - localConnection: - additionalProperties: - description: Referenced by ProxyConfiguration - properties: - connectTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - requestTimeout: - description: "A Duration represents a signed, fixed-length - span of time represented as a count of seconds and fractions - of seconds at nanosecond resolution. It is independent - of any calendar and concepts like \"day\" or \"month\". - It is related to Timestamp in that the difference between - two Timestamp values is a Duration and it can be added - or subtracted from a Timestamp. Range is approximately - +-10,000 years. \n # Examples \n Example 1: Compute Duration - from two Timestamps in pseudo code. \n Timestamp start - = ...; Timestamp end = ...; Duration duration = ...; \n - duration.seconds = end.seconds - start.seconds; duration.nanos - = end.nanos - start.nanos; \n if (duration.seconds < 0 - && duration.nanos > 0) { duration.seconds += 1; duration.nanos - -= 1000000000; } else if (duration.seconds > 0 && duration.nanos - < 0) { duration.seconds -= 1; duration.nanos += 1000000000; - } \n Example 2: Compute Timestamp from Timestamp + Duration - in pseudo code. \n Timestamp start = ...; Duration duration - = ...; Timestamp end = ...; \n end.seconds = start.seconds - + duration.seconds; end.nanos = start.nanos + duration.nanos; - \n if (end.nanos < 0) { end.seconds -= 1; end.nanos += - 1000000000; } else if (end.nanos >= 1000000000) { end.seconds - += 1; end.nanos -= 1000000000; } \n Example 3: Compute - Duration from datetime.timedelta in Python. \n td = datetime.timedelta(days=3, - minutes=10) duration = Duration() duration.FromTimedelta(td) - \n # JSON Mapping \n In JSON format, the Duration type - is encoded as a string rather than an object, where the - string ends in the suffix \"s\" (indicating seconds) and - is preceded by the number of seconds, with nanoseconds - expressed as fractional seconds. For example, 3 seconds - with 0 nanoseconds should be encoded in JSON format as - \"3s\", while 3 seconds and 1 nanosecond should be expressed - in JSON format as \"3.000000001s\", and 3 seconds and - 1 microsecond should be expressed in JSON format as \"3.000001s\"." - format: duration - properties: - nanos: - description: Signed fractions of a second at nanosecond - resolution of the span of time. Durations less than - one second are represented with a 0 `seconds` field - and a positive or negative `nanos` field. For durations - of one second or more, a non-zero value for the `nanos` - field must be of the same sign as the `seconds` field. - Must be from -999,999,999 to +999,999,999 inclusive. - format: int32 - type: integer - seconds: - description: 'Signed seconds of the span of time. Must - be from -315,576,000,000 to +315,576,000,000 inclusive. - Note: these bounds are computed from: 60 sec/min * - 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years' - format: int64 - type: integer - type: object - type: object - description: local_connection is the configuration that should - be used to connect to the local application provided per-port. - The map keys should correspond to port names on the workload. - type: object - localWorkloadAddress: - description: "deprecated: local_workload_address, local_workload_port, - and local_workload_socket_path are deprecated and are only needed - for migration of existing resources. \n Deprecated: Marked as - deprecated in pbmesh/v2beta1/proxy_configuration.proto." - type: string - localWorkloadPort: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - format: int32 - type: integer - localWorkloadSocketPath: - description: 'Deprecated: Marked as deprecated in pbmesh/v2beta1/proxy_configuration.proto.' - type: string - meshGatewayMode: - enum: - - MESH_GATEWAY_MODE_UNSPECIFIED - - MESH_GATEWAY_MODE_NONE - - MESH_GATEWAY_MODE_LOCAL - - MESH_GATEWAY_MODE_REMOTE - format: int32 - type: string - mode: - description: mode indicates the proxy's mode. This will default - to 'transparent'. - enum: - - PROXY_MODE_DEFAULT - - PROXY_MODE_TRANSPARENT - - PROXY_MODE_DIRECT - format: int32 - type: string - mutualTlsMode: - enum: - - MUTUAL_TLS_MODE_DEFAULT - - MUTUAL_TLS_MODE_STRICT - - MUTUAL_TLS_MODE_PERMISSIVE - format: int32 - type: string - publicListenerJson: - type: string - transparentProxy: - properties: - dialedDirectly: - description: dialed_directly indicates whether this proxy - should be dialed using original destination IP in the connection - rather than load balance between all endpoints. - type: boolean - outboundListenerPort: - description: outbound_listener_port is the port for the proxy's - outbound listener. This defaults to 15001. - format: int32 - type: integer - type: object - type: object - opaqueConfig: - description: "deprecated: prevent usage when using v2 APIs directly. - needed for backwards compatibility \n Deprecated: Marked as deprecated - in pbmesh/v2beta1/proxy_configuration.proto." - type: object - x-kubernetes-preserve-unknown-fields: true - workloads: - description: Selection of workloads this proxy configuration should - apply to. These can be prefixes or specific workload names. - properties: - filter: - type: string - names: - items: - type: string - type: array - prefixes: - items: - type: string - type: array - type: object - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml b/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml deleted file mode 100644 index 21a3a9c5ec..0000000000 --- a/control-plane/config/crd/bases/mesh.consul.hashicorp.com_tcproutes.yaml +++ /dev/null @@ -1,273 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.12.1 - name: tcproutes.mesh.consul.hashicorp.com -spec: - group: mesh.consul.hashicorp.com - names: - kind: TCPRoute - listKind: TCPRouteList - plural: tcproutes - shortNames: - - tcp-route - singular: tcproute - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: The sync status of the resource with Consul - jsonPath: .status.conditions[?(@.type=="Synced")].status - name: Synced - type: string - - description: The last successful synced time of the resource with Consul - jsonPath: .status.lastSyncedTime - name: Last Synced - type: date - - description: The age of the resource - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v2beta1 - schema: - openAPIV3Schema: - description: TCPRoute is the Schema for the TCP Route API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: "NOTE: this should align to the GAMMA/gateway-api version, - or at least be easily translatable. \n https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute - \n This is a Resource type." - properties: - parentRefs: - description: "ParentRefs references the resources (usually Services) - that a Route wants to be attached to. \n It is invalid to reference - an identical parent more than once. It is valid to reference multiple - distinct sections within the same parent resource." - items: - description: 'NOTE: roughly equivalent to structs.ResourceReference' - properties: - port: - description: For east/west this is the name of the Consul Service - port to direct traffic to or empty to imply all. For north/south - this is TBD. - type: string - ref: - description: For east/west configuration, this should point - to a Service. For north/south it should point to a Gateway. - properties: - name: - description: Name is the user-given name of the resource - (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of the resource - the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units (i.e. - partition, namespace) in which the resource resides. - properties: - namespace: - description: "Namespace further isolates resources within - a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all partitions." - type: string - peerName: - description: "PeerName identifies which peer the resource - is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, provide - the wildcard value \"*\" to list resources across - all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. "catalog", - "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when sweeping - or backward-incompatible changes are made to the group's - resource types. - type: string - kind: - description: Kind identifies the specific resource type - within the group. - type: string - type: object - type: object - type: object - type: array - rules: - description: Rules are a list of TCP matchers and actions. - items: - properties: - backendRefs: - description: BackendRefs defines the backend(s) where matching - requests should be sent. If unspecified or invalid (refers - to a non-existent resource or a Service with no endpoints), - the underlying implementation MUST actively reject connection - attempts to this backend. Connection rejections must respect - weight; if an invalid backend is requested to have 80% of - connections, then 80% of connections must be rejected instead. - items: - properties: - backendRef: - properties: - datacenter: - type: string - port: - description: "For east/west this is the name of the - Consul Service port to direct traffic to or empty - to imply using the same value as the parent ref. - \n For north/south this is TBD." - type: string - ref: - description: For east/west configuration, this should - point to a Service. - properties: - name: - description: Name is the user-given name of the - resource (e.g. the "billing" service). - type: string - section: - description: Section identifies which part of - the resource the condition relates to. - type: string - tenancy: - description: Tenancy identifies the tenancy units - (i.e. partition, namespace) in which the resource - resides. - properties: - namespace: - description: "Namespace further isolates resources - within a partition. https://developer.hashicorp.com/consul/docs/enterprise/namespaces - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all namespaces." - type: string - partition: - description: "Partition is the topmost administrative - boundary within a cluster. https://developer.hashicorp.com/consul/docs/enterprise/admin-partitions - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all partitions." - type: string - peerName: - description: "PeerName identifies which peer - the resource is imported from. https://developer.hashicorp.com/consul/docs/connect/cluster-peering - \n When using the List and WatchList endpoints, - provide the wildcard value \"*\" to list - resources across all peers." - type: string - type: object - type: - description: Type identifies the resource's type. - properties: - group: - description: Group describes the area of functionality - to which this resource type relates (e.g. - "catalog", "authorization"). - type: string - groupVersion: - description: GroupVersion is incremented when - sweeping or backward-incompatible changes - are made to the group's resource types. - type: string - kind: - description: Kind identifies the specific - resource type within the group. - type: string - type: object - type: object - type: object - weight: - description: "Weight specifies the proportion of requests - forwarded to the referenced backend. This is computed - as weight/(sum of all weights in this BackendRefs list). - For non-zero values, there may be some epsilon from - the exact proportion defined here depending on the precision - an implementation supports. Weight is not a percentage - and the sum of weights does not need to equal 100. \n - If only one backend is specified and it has a weight - greater than 0, 100% of the traffic is forwarded to - that backend. If weight is set to 0, no traffic should - be forwarded for this entry. If unspecified, weight - defaults to 1." - format: int32 - type: integer - type: object - type: array - type: object - type: array - type: object - status: - properties: - conditions: - description: Conditions indicate the latest available observations - of a resource's current state. - items: - description: 'Conditions define a readiness condition for a Consul - resource. See: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties' - properties: - lastTransitionTime: - description: LastTransitionTime is the last time the condition - transitioned from one status to another. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition. - type: string - required: - - status - - type - type: object - type: array - lastSyncedTime: - description: LastSyncedTime is the last time the resource successfully - synced with Consul. - format: date-time - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/control-plane/config/crd/kustomization.yaml b/control-plane/config/crd/kustomization.yaml new file mode 100644 index 0000000000..2c8358a48b --- /dev/null +++ b/control-plane/config/crd/kustomization.yaml @@ -0,0 +1,24 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/consul.hashicorp.com_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_controlplanerequestlimits.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/control-plane/config/rbac/role.yaml b/control-plane/config/rbac/role.yaml index cce5208eda..7f90780e02 100644 --- a/control-plane/config/rbac/role.yaml +++ b/control-plane/config/rbac/role.yaml @@ -5,6 +5,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: + creationTimestamp: null name: manager-role rules: - apiGroups: @@ -25,26 +26,6 @@ rules: - secrets/status verbs: - get -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - auth.consul.hashicorp.com - resources: - - trafficpermissions/status - verbs: - - get - - patch - - update - apiGroups: - consul.hashicorp.com resources: @@ -345,83 +326,3 @@ rules: - get - patch - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - grpcroute/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - httproute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - httproute/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - proxyconfiguration - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - proxyconfiguration/status - verbs: - - get - - patch - - update -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - tcproute - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mesh.consul.hashicorp.com - resources: - - tcproute/status - verbs: - - get - - patch - - update diff --git a/control-plane/config/webhook/manifests.yaml b/control-plane/config/webhook/manifests.yaml index b5e3bb52fd..0861f9253a 100644 --- a/control-plane/config/webhook/manifests.yaml +++ b/control-plane/config/webhook/manifests.yaml @@ -5,6 +5,7 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: + creationTimestamp: null name: mutating-webhook-configuration webhooks: - admissionReviewVersions: @@ -322,135 +323,3 @@ webhooks: resources: - terminatinggateways sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-trafficpermissions - failurePolicy: Fail - name: mutate-trafficpermissions.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - trafficpermissions - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-grpcroute - failurePolicy: Fail - name: mutate-grpcroute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - grpcroute - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-httproute - failurePolicy: Fail - name: mutate-httproute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - httproute - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-proxyconfiguration - failurePolicy: Fail - name: mutate-proxyconfiguration.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - proxyconfiguration - sideEffects: None -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v2beta1-tcproute - failurePolicy: Fail - name: mutate-tcproute.auth.consul.hashicorp.com - rules: - - apiGroups: - - auth.consul.hashicorp.com - apiVersions: - - v2beta1 - operations: - - CREATE - - UPDATE - resources: - - tcproute - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1beta1 - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-v1alpha1-gatewaypolicy - failurePolicy: Fail - name: validate-gatewaypolicy.consul.hashicorp.com - rules: - - apiGroups: - - consul.hashicorp.com - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - gatewaypolicies - sideEffects: None diff --git a/control-plane/connect-inject/common/annotation_processor.go b/control-plane/connect-inject/common/annotation_processor.go deleted file mode 100644 index 778f630049..0000000000 --- a/control-plane/connect-inject/common/annotation_processor.go +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "fmt" - "strings" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - ConsulNodeAddress = "127.0.0.1" -) - -// ProcessPodDestinationsForMeshWebhook reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations -// object. -func ProcessPodDestinationsForMeshWebhook(pod corev1.Pod) (*pbmesh.Destinations, error) { - return ProcessPodDestinations(pod, true, true) -} - -// ProcessPodDestinations reads the list of destinations from the Pod annotation and converts them into a pbmesh.Destinations -// object. -func ProcessPodDestinations(pod corev1.Pod, enablePartitions, enableNamespaces bool) (*pbmesh.Destinations, error) { - destinations := &pbmesh.Destinations{} - raw, ok := pod.Annotations[constants.AnnotationMeshDestinations] - if !ok || raw == "" { - return nil, nil - } - - destinations.Workloads = &pbcatalog.WorkloadSelector{ - Names: []string{pod.Name}, - } - - for _, raw := range strings.Split(raw, ",") { - var destination *pbmesh.Destination - - // Determine the type of processing required unlabeled or labeled - // [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter] - // or - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] - // [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port] - - // Scan the string for the annotation keys. - // Even if the first key is missing, and the order is unexpected, we should let the processing - // provide us with errors - labeledFormat := false - keys := []string{"port", "svc", "ns", "ap", "peer", "dc"} - for _, v := range keys { - if strings.Contains(raw, fmt.Sprintf(".%s.", v)) || strings.Contains(raw, fmt.Sprintf(".%s:", v)) { - labeledFormat = true - break - } - } - - if labeledFormat { - var err error - destination, err = processPodLabeledDestination(pod, raw, enablePartitions, enableNamespaces) - if err != nil { - return nil, err - } - } else { - var err error - destination, err = processPodUnlabeledDestination(pod, raw, enablePartitions, enableNamespaces) - if err != nil { - return nil, err - } - } - - destinations.Destinations = append(destinations.Destinations, destination) - } - - return destinations, nil -} - -// processPodLabeledDestination processes a destination in the format: -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-peer].peer:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-partition].ap:[port] -// [service-port-name].port.[service-name].svc.[service-namespace].ns.[service-datacenter].dc:[port]. -// peer/ap/dc are mutually exclusive. At minimum service-port-name and service-name are required. -// The ordering matters for labeled as well as unlabeled. The ordering of the labeled parameters should follow -// the order and requirements of the unlabeled parameters. -// TODO: enable dc and peer support when ready, currently return errors if set. -func processPodLabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { - parts := strings.SplitN(rawUpstream, ":", 3) - var port int32 - port, _ = PortValue(pod, strings.TrimSpace(parts[1])) - if port <= 0 { - return nil, fmt.Errorf("port value %d in destination is invalid: %s", port, rawUpstream) - } - - service := parts[0] - pieces := strings.Split(service, ".") - - var portName, datacenter, svcName, namespace, partition, peer string - if enablePartitions || enableNamespaces { - switch len(pieces) { - case 8: - end := strings.TrimSpace(pieces[7]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[6]) - return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) - case "ap": - partition = strings.TrimSpace(pieces[6]) - case "dc": - // TODO: uncomment and remove error when datacenters are supported - //datacenter = strings.TrimSpace(pieces[6]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - fallthrough - case 6: - if strings.TrimSpace(pieces[5]) == "ns" { - namespace = strings.TrimSpace(pieces[4]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } else { - switch len(pieces) { - case 6: - end := strings.TrimSpace(pieces[5]) - switch end { - case "peer": - // TODO: uncomment and remove error when peers supported - //peer = strings.TrimSpace(pieces[4]) - return nil, fmt.Errorf("destination currently does not support peers: %s", rawUpstream) - case "dc": - // TODO: uncomment and remove error when datacenter supported - //datacenter = strings.TrimSpace(pieces[4]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - // TODO: uncomment and remove error when datacenter and/or peers supported - //fallthrough - case 4: - if strings.TrimSpace(pieces[3]) == "svc" { - svcName = strings.TrimSpace(pieces[2]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - if strings.TrimSpace(pieces[1]) == "port" { - portName = strings.TrimSpace(pieces[0]) - } else { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } - - destination := pbmesh.Destination{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(partition), - Namespace: constants.GetNormalizedConsulNamespace(namespace), - PeerName: constants.GetNormalizedConsulPeer(peer), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: ConsulNodeAddress, - }, - }, - } - - return &destination, nil -} - -// processPodUnlabeledDestination processes a destination in the format: -// [service-port-name].[service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. -// There is no unlabeled field for peering. -// TODO: enable dc and peer support when ready, currently return errors if set. -func processPodUnlabeledDestination(pod corev1.Pod, rawUpstream string, enablePartitions, enableNamespaces bool) (*pbmesh.Destination, error) { - var portName, datacenter, svcName, namespace, partition string - var port int32 - var destination pbmesh.Destination - - parts := strings.SplitN(rawUpstream, ":", 3) - - port, _ = PortValue(pod, strings.TrimSpace(parts[1])) - - // If Consul Namespaces or Admin Partitions are enabled, attempt to parse the - // destination for a namespace. - if enableNamespaces || enablePartitions { - pieces := strings.SplitN(parts[0], ".", 4) - switch len(pieces) { - case 4: - partition = strings.TrimSpace(pieces[3]) - fallthrough - case 3: - namespace = strings.TrimSpace(pieces[2]) - fallthrough - case 2: - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - default: - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - } else { - pieces := strings.SplitN(parts[0], ".", 2) - if len(pieces) < 2 { - return nil, fmt.Errorf("destination structured incorrectly: %s", rawUpstream) - } - svcName = strings.TrimSpace(pieces[1]) - portName = strings.TrimSpace(pieces[0]) - } - - // parse the optional datacenter - if len(parts) > 2 { - // TODO: uncomment and remove error when datacenters supported - //datacenter = strings.TrimSpace(parts[2]) - return nil, fmt.Errorf("destination currently does not support datacenters: %s", rawUpstream) - } - - if port > 0 { - destination = pbmesh.Destination{ - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(partition), - Namespace: constants.GetNormalizedConsulNamespace(namespace), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: svcName, - }, - DestinationPort: portName, - Datacenter: datacenter, - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(port), - Ip: ConsulNodeAddress, - }, - }, - } - } - return &destination, nil -} diff --git a/control-plane/connect-inject/common/annotation_processor_test.go b/control-plane/connect-inject/common/annotation_processor_test.go deleted file mode 100644 index 223067e6c5..0000000000 --- a/control-plane/connect-inject/common/annotation_processor_test.go +++ /dev/null @@ -1,1014 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package common - -import ( - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func TestProcessUpstreams(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - expected *pbmesh.Destinations - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool - consulNamespacesEnabled bool - consulPartitionsEnabled bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.dc1.dc:1234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") - return pod1 - }, - expErr: "destination currently does not support peers: myPort.port.upstream1.svc.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234") - return pod1 - }, - expErr: "destination currently does not support peers: myPort.port.upstream1.svc.ns1.ns.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.ap:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "part1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled annotated destination with svc, ns, and dc", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled multiple annotated destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.port.upstream2.svc:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "labeled multiple annotated destinations with dcs and peers", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234, myPort2.port.upstream2.svc:2234, myPort3.port.upstream3.svc.ns1.ns:3234, myPort4.port.upstream4.svc.ns1.ns.peer1.peer:4234") - return pod1 - }, - expErr: "destination currently does not support datacenters: myPort.port.upstream1.svc.ns1.ns.dc1.dc:1234", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "dc1", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream4", - // }, - // DestinationPort: "myPort4", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(4234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: invalid partition/dc/peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination with svc and peer, needs ns before peer if namespaces enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.peer:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid number of pieces in the address", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.peer1.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.peer1.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: invalid number of pieces in the address without namespaces and partitions", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.err:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.err:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: both peer and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.peer1.peer:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: both peer and dc provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.peer1.peer.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: both dc and partition provided", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.port.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc.myPort.port.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: wrong ordering for port and svc with namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc.myPort.port:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc.myPort.port:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled annotated destination error: incorrect key name namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.portage.upstream1.svc.ns1.ns.part1.partition.dc1.dc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: incorrect key name namespace partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.portage.upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: myPort.portage.upstream1.svc:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled missing port name", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error labeled missing port name namespace partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1.svc:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1.svc:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled and labeled multiple annotated destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.port.upstream1.svc.ns1.ns:1234, myPort2.upstream2:2234, myPort4.port.upstream4.svc.ns1.ns.ap1.ap:4234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream4", - }, - DestinationPort: "myPort4", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(4234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled single destination", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream.foo:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream.foo.bar:1234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "bar", - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple destinations", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2:2234") - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "myPort", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: ConsulNodeAddress, - }, - }, - }, - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream2", - }, - DestinationPort: "myPort2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(2234), - Ip: ConsulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled multiple destinations with consul namespaces, partitions and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo.baz:3234:dc2") - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "destination currently does not support datacenters: myPort3.upstream3.foo.baz:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "bar", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: "baz", - // Namespace: "foo", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "unlabeled multiple destinations with consul namespaces and datacenters", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "myPort.upstream1:1234, myPort2.upstream2.bar:2234, myPort3.upstream3.foo:3234:dc2") - return pod1 - }, - configEntry: func() api.ConfigEntry { - ce, _ := api.MakeConfigEntry(api.ProxyDefaults, "global") - pd := ce.(*api.ProxyConfigEntry) - pd.MeshGateway.Mode = "remote" - return pd - }, - expErr: "destination currently does not support datacenters: myPort3.upstream3.foo:3234:dc2", - // TODO: uncomment this and remove expErr when datacenters is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Upstreams: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: constants.GetNormalizedConsulNamespace(""), - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream1", - // }, - // DestinationPort: "myPort", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "bar", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream2", - // }, - // DestinationPort: "myPort2", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(2234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "foo", - // PeerName: constants.GetNormalizedConsulPeer(""), - // }, - // Name: "upstream3", - // }, - // DestinationPort: "myPort3", - // Datacenter: "dc2", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(3234), - // Ip: ConsulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - }, - { - name: "error unlabeled missing port name with namespace and partition disabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1:1234", - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "error unlabeled missing port name with namespace and partition enabled", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "upstream1:1234") - return pod1 - }, - expErr: "destination structured incorrectly: upstream1:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - destinations, err := ProcessPodDestinations(*tt.pod(), tt.consulNamespacesEnabled, tt.consulPartitionsEnabled) - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - require.Equal(t, tt.expected, destinations) - - if diff := cmp.Diff(tt.expected, destinations, protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - } - }) - } -} - -// createPod creates a multi-port pod as a base for tests. -func createPod(name string, annotation string) *corev1.Pod { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - pod.Annotations = map[string]string{ - constants.AnnotationMeshDestinations: annotation, - } - return pod -} diff --git a/control-plane/connect-inject/common/common.go b/control-plane/connect-inject/common/common.go index 99372f7aec..a99d9fd12e 100644 --- a/control-plane/connect-inject/common/common.go +++ b/control-plane/connect-inject/common/common.go @@ -8,17 +8,8 @@ import ( "strconv" "strings" - mapset "github.com/deckarep/golang-set" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + corev1 "k8s.io/api/core/v1" ) // DetermineAndValidatePort behaves as follows: @@ -99,111 +90,6 @@ func ShouldOverwriteProbes(pod corev1.Pod, globalOverwrite bool) (bool, error) { return globalOverwrite, nil } -// ShouldIgnore ignores namespaces where we don't mesh-inject. -func ShouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { - // Ignores system namespaces. - if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { - return true - } - - // Ignores deny list. - if denySet.Contains(namespace) { - return true - } - - // Ignores if not in allow list or allow list is not *. - if !allowSet.Contains("*") && !allowSet.Contains(namespace) { - return true - } - - return false -} - func ConsulNodeNameFromK8sNode(nodeName string) string { return fmt.Sprintf("%s-virtual", nodeName) } - -// ******************** -// V2 Exclusive Common Code -// ******************** - -// ToProtoAny is a convenience function for converting proto.Message values to anypb.Any without error handling. -// This should _only_ be used in cases where a nil or valid proto.Message value is _guaranteed_, else it will panic. -// If the type of m is *anypb.Any, that value will be returned unmodified. -func ToProtoAny(m proto.Message) *anypb.Any { - switch v := m.(type) { - case nil: - return nil - case *anypb.Any: - return v - } - a, err := anypb.New(m) - if err != nil { - panic(fmt.Errorf("unexpected error: failed to convert proto message to anypb.Any: %w", err)) - } - return a -} - -// GetPortProtocol matches the Kubernetes EndpointPort.AppProtocol or ServicePort.AppProtocol (*string) to a supported -// Consul catalog port protocol. If nil or unrecognized, the default of `PROTOCOL_UNSPECIFIED` is returned. -func GetPortProtocol(appProtocol *string) pbcatalog.Protocol { - if appProtocol == nil { - return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED - } - switch *appProtocol { - case "tcp": - return pbcatalog.Protocol_PROTOCOL_TCP - case "http": - return pbcatalog.Protocol_PROTOCOL_HTTP - case "http2": - return pbcatalog.Protocol_PROTOCOL_HTTP2 - case "grpc": - return pbcatalog.Protocol_PROTOCOL_GRPC - } - // If unrecognized or empty string, return default - return pbcatalog.Protocol_PROTOCOL_UNSPECIFIED -} - -// PortValueFromIntOrString returns the integer port value from the port that can be -// a named port, an integer string (e.g. "80"), or an integer. If the port is a named port, -// this function will attempt to find the value from the containers of the pod. -func PortValueFromIntOrString(pod corev1.Pod, port intstr.IntOrString) (uint32, error) { - if port.Type == intstr.Int { - return uint32(port.IntValue()), nil - } - - // Otherwise, find named port or try to parse the string as an int. - portVal, err := PortValue(pod, port.StrVal) - if err != nil { - return 0, err - } - return uint32(portVal), nil -} - -// HasBeenMeshInjected checks the value of the status annotation and returns true if the Pod has been injected. -// Does not apply to V1 pods, which use a different key (`constants.KeyInjectStatus`). -func HasBeenMeshInjected(pod corev1.Pod) bool { - if pod.Annotations == nil { - return false - } - if anno, ok := pod.Annotations[constants.KeyMeshInjectStatus]; ok && anno == constants.Injected { - return true - } - return false -} - -// ConsulNamespaceIsNotFound checks the gRPC error code and message to determine -// if a namespace does not exist. If the namespace exists this function returns false, true otherwise. -func ConsulNamespaceIsNotFound(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - if !ok { - return false - } - if codes.InvalidArgument == s.Code() && strings.Contains(s.Message(), "namespace not found") { - return true - } - return false -} diff --git a/control-plane/connect-inject/common/common_test.go b/control-plane/connect-inject/common/common_test.go index f7fff948a6..79a9294fe2 100644 --- a/control-plane/connect-inject/common/common_test.go +++ b/control-plane/connect-inject/common/common_test.go @@ -4,28 +4,13 @@ package common import ( - "context" - "fmt" "testing" - mapset "github.com/deckarep/golang-set" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/sdk/testutil" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestCommonDetermineAndValidatePort(t *testing.T) { @@ -274,305 +259,3 @@ func minimal() *corev1.Pod { }, } } - -func TestShouldIgnore(t *testing.T) { - t.Parallel() - cases := []struct { - name string - namespace string - denySet mapset.Set - allowSet mapset.Set - expected bool - }{ - { - name: "system namespace", - namespace: "kube-system", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "other system namespace", - namespace: "local-path-storage", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "any namespace allowed", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("*"), - expected: false, - }, - { - name: "in deny list", - namespace: "foo", - denySet: mapset.NewSetWith("foo"), - allowSet: mapset.NewSetWith("*"), - expected: true, - }, - { - name: "not in allow list", - namespace: "foo", - denySet: mapset.NewSetWith(), - allowSet: mapset.NewSetWith("bar"), - expected: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := ShouldIgnore(tt.namespace, tt.denySet, tt.allowSet) - require.Equal(t, tt.expected, actual) - }) - } -} - -func TestToProtoAny(t *testing.T) { - t.Parallel() - - t.Run("nil gets nil", func(t *testing.T) { - require.Nil(t, ToProtoAny(nil)) - }) - - t.Run("anypb.Any gets same value", func(t *testing.T) { - testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} - testAny, err := anypb.New(testMsg) - require.NoError(t, err) - - require.Equal(t, testAny, ToProtoAny(testAny)) - }) - - t.Run("valid proto is successfully serialized", func(t *testing.T) { - testMsg := &pbresource.Resource{Id: &pbresource.ID{Name: "foo"}} - testAny, err := anypb.New(testMsg) - require.NoError(t, err) - - if diff := cmp.Diff(testAny, ToProtoAny(testMsg), protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - }) -} - -func TestGetPortProtocol(t *testing.T) { - t.Parallel() - toStringPtr := func(s string) *string { - return &s - } - cases := []struct { - name string - input *string - expected pbcatalog.Protocol - }{ - { - name: "nil gets UNSPECIFIED", - input: nil, - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - name: "tcp gets TCP", - input: toStringPtr("tcp"), - expected: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - name: "http gets HTTP", - input: toStringPtr("http"), - expected: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - name: "http2 gets HTTP2", - input: toStringPtr("http2"), - expected: pbcatalog.Protocol_PROTOCOL_HTTP2, - }, - { - name: "grpc gets GRPC", - input: toStringPtr("grpc"), - expected: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - name: "case sensitive", - input: toStringPtr("gRPC"), - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - name: "unknown gets UNSPECIFIED", - input: toStringPtr("foo"), - expected: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := GetPortProtocol(tt.input) - require.Equal(t, tt.expected, actual) - }) - } -} - -func TestHasBeenMeshInjected(t *testing.T) { - t.Parallel() - cases := []struct { - name string - pod corev1.Pod - expected bool - }{ - { - name: "Pod with injected annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: constants.Injected, - }, - }, - }, - expected: true, - }, - { - name: "Pod without injected annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - "consul.hashicorp.com/foo": "bar", - }, - }, - }, - expected: false, - }, - { - name: "Pod with injected annotation but wrong value", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: "hiya", - }, - }, - }, - expected: false, - }, - { - name: "Pod with nil annotations", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{}, - }, - }, - expected: false, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := HasBeenMeshInjected(tt.pod) - require.Equal(t, tt.expected, actual) - }) - } -} - -func Test_ConsulNamespaceIsNotFound(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - input error - expectMissingNamespace bool - }{ - { - name: "nil error", - expectMissingNamespace: false, - }, - { - name: "random error", - input: fmt.Errorf("namespace resource not found"), - expectMissingNamespace: false, - }, - { - name: "grpc code is not InvalidArgument", - input: status.Error(codes.NotFound, "namespace resource not found"), - expectMissingNamespace: false, - }, - { - name: "grpc code is InvalidArgument, but the message is not for namespaces", - input: status.Error(codes.InvalidArgument, "blurg resource not found"), - expectMissingNamespace: false, - }, - { - name: "namespace is missing", - input: status.Error(codes.InvalidArgument, "namespace not found"), - expectMissingNamespace: true, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - actual := ConsulNamespaceIsNotFound(tt.input) - require.Equal(t, tt.expectMissingNamespace, actual) - }) - } -} - -// Test_ConsulNamespaceIsNotFound_ErrorMsg is an integration test that verifies the error message -// associated with a missing namespace while creating a resource doesn't drift. -func Test_ConsulNamespaceIsNotFound_ErrorMsg(t *testing.T) { - t.Parallel() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - id := &pbresource.ID{ - Name: "foo", - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.DefaultConsulPartition, - Namespace: "i-dont-exist-but-its-ok-we-will-meet-again-someday", - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "banana", - Identity: "foo", - } - - data := ToProtoAny(workload) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - } - - _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{Resource: resource}) - require.Error(t, err) - - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.InvalidArgument, s.Code()) - require.Contains(t, s.Message(), "namespace not found") - - require.True(t, ConsulNamespaceIsNotFound(err)) -} diff --git a/control-plane/connect-inject/constants/annotations_and_labels.go b/control-plane/connect-inject/constants/annotations_and_labels.go index 823776e577..4efcc24c74 100644 --- a/control-plane/connect-inject/constants/annotations_and_labels.go +++ b/control-plane/connect-inject/constants/annotations_and_labels.go @@ -75,7 +75,7 @@ const ( // AnnotationUpstreams is a list of upstreams to register with the // proxy in the format of `:,...`. The - // service name should map to a Consul service name and the local port + // service name should map to a Consul service namd and the local port // is the local port in the pod that the listener will bind to. It can // be a named port. AnnotationUpstreams = "consul.hashicorp.com/connect-service-upstreams" @@ -181,12 +181,8 @@ const ( // to explicitly perform the peering operation again. AnnotationPeeringVersion = "consul.hashicorp.com/peering-version" - // LegacyAnnotationConsulK8sVersion is the current version of this binary. - // TODO: remove this annotation in a future release. - LegacyAnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" - // AnnotationConsulK8sVersion is the current version of this binary. - AnnotationConsulK8sVersion = "consul.hashicorp.com/consul-k8s-version" + AnnotationConsulK8sVersion = "consul.hashicorp.com/connect-k8s-version" // LabelServiceIgnore is a label that can be added to a service to prevent it from being // registered with Consul. @@ -203,50 +199,9 @@ const ( Enabled = "enabled" // ManagedByValue is the value for keyManagedBy. - //TODO(zalimeni) rename this to ManagedByLegacyEndpointsValue. ManagedByValue = "consul-k8s-endpoints-controller" ) -// ******************** -// V2 Exclusive Annotations & Labels -// ******************** - -const ( - // AnnotationMeshInject is the key of the annotation that controls whether - // V2 mesh injection is explicitly enabled or disabled for a pod using. - // be set to a truthy or falsy value, as parseable by strconv.ParseBool. - AnnotationMeshInject = "consul.hashicorp.com/mesh-inject" - - // KeyMeshInjectStatus is the key of the annotation that is added to - // a pod after an injection is done. - KeyMeshInjectStatus = "consul.hashicorp.com/mesh-inject-status" - - // ManagedByEndpointsValue is used in Consul metadata to identify the manager - // of resources. The 'v2' suffix is used to differentiate from the legacy - // endpoints controller of the same name. - ManagedByEndpointsValue = "consul-k8s-endpoints-controller-v2" - - // ManagedByPodValue is used in Consul metadata to identify the manager - // of resources. - ManagedByPodValue = "consul-k8s-pod-controller" - - // ManagedByServiceAccountValue is used in Consul metadata to identify the manager - // of resources. - ManagedByServiceAccountValue = "consul-k8s-service-account-controller" - - // AnnotationMeshDestinations is a list of destinations to register with the - // proxy. The service name should map to a Consul service name and the local - // port is the local port in the pod that the listener will bind to. It can - // be a named port. - AnnotationMeshDestinations = "consul.hashicorp.com/mesh-service-destinations" - - // AnnotationMeshInjectMountVolumes is the key of the annotation that controls whether - // the data volume that mesh inject uses to store data including the Consul ACL token - // is mounted to other containers in the pod. It is a comma-separated list of container names - // to mount the volume on. It will be mounted at the path `/consul/mesh-inject`. - AnnotationMeshInjectMountVolumes = "consul.hashicorp.com/mesh-inject-mount-volume" -) - // Annotations used by Prometheus. const ( AnnotationPrometheusScrape = "prometheus.io/scrape" diff --git a/control-plane/connect-inject/constants/constants.go b/control-plane/connect-inject/constants/constants.go index 07bdaecba8..ca6fe23606 100644 --- a/control-plane/connect-inject/constants/constants.go +++ b/control-plane/connect-inject/constants/constants.go @@ -4,22 +4,8 @@ package constants const ( - // LegacyConsulCAFile is the location of the Consul CA file inside the injected pod. - // This is used with the V1 API. - LegacyConsulCAFile = "/consul/connect-inject/consul-ca.pem" - // ConsulCAFile is the location of the Consul CA file inside the injected pod. - // This is used with the V2 API. - ConsulCAFile = "/consul/mesh-inject/consul-ca.pem" - - // DefaultConsulNS is the default Consul namespace name. - DefaultConsulNS = "default" - - // DefaultConsulPartition is the default Consul partition name. - DefaultConsulPartition = "default" - - // DefaultConsulPeer is the name used to refer to resources that are in the same cluster. - DefaultConsulPeer = "local" + ConsulCAFile = "/consul/connect-inject/consul-ca.pem" // ProxyDefaultInboundPort is the default inbound port for the proxy. ProxyDefaultInboundPort = 20000 @@ -27,9 +13,6 @@ const ( // ProxyDefaultHealthPort is the default HTTP health check port for the proxy. ProxyDefaultHealthPort = 21000 - // MetaKeyManagedBy is the meta key name for indicating which Kubernetes controller manages a Consul resource. - MetaKeyManagedBy = "managed-by" - // MetaKeyKubeNS is the meta key name for Kubernetes namespace used for the Consul services. MetaKeyKubeNS = "k8s-namespace" @@ -42,10 +25,6 @@ const ( // MetaKeyKubeServiceName is the meta key name for Kubernetes service name used for the Consul services. MetaKeyKubeServiceName = "k8s-service-name" - // MetaKeyKubeServiceAccountName is the meta key name for Kubernetes service account name used for the Consul - // v2 workload identity. - MetaKeyKubeServiceAccountName = "k8s-service-account-name" - // MetaKeyPodName is the meta key name for Kubernetes pod name used for the Consul services. MetaKeyPodName = "pod-name" @@ -54,42 +33,4 @@ const ( // DefaultGracefulShutdownPath is the default path that consul-dataplane uses for graceful shutdown. DefaultGracefulShutdownPath = "/graceful_shutdown" - - // ConsulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. - ConsulKubernetesCheckType = "kubernetes-readiness" - - // ConsulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. - ConsulKubernetesCheckName = "Kubernetes Readiness Check" - - KubernetesSuccessReasonMsg = "Kubernetes health checks passing" ) - -// GetNormalizedConsulNamespace returns the default namespace if the passed namespace -// is empty, otherwise returns back the passed in namespace. -func GetNormalizedConsulNamespace(ns string) string { - if ns == "" { - ns = DefaultConsulNS - } - - return ns -} - -// GetNormalizedConsulPartition returns the default partition if the passed partition -// is empty, otherwise returns back the passed in partition. -func GetNormalizedConsulPartition(ap string) string { - if ap == "" { - ap = DefaultConsulPartition - } - - return ap -} - -// GetNormalizedConsulPeer returns the default peer if the passed peer -// is empty, otherwise returns back the passed in peer. -func GetNormalizedConsulPeer(peer string) string { - if peer == "" { - peer = DefaultConsulPeer - } - - return peer -} diff --git a/control-plane/connect-inject/constants/constants_test.go b/control-plane/connect-inject/constants/constants_test.go deleted file mode 100644 index 2637c3b7d3..0000000000 --- a/control-plane/connect-inject/constants/constants_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package constants - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetNormalizedConsulNamespace(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulNS, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulNamespace(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} - -func TestGetNormalizedConsulPartition(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulPartition, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulPartition(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} - -func TestGetNormalizedConsulPeer(t *testing.T) { - tests := []struct { - name string - value string - expect string - }{ - { - name: "expect contant", - value: "", - expect: DefaultConsulPeer, - }, - { - name: "expect passed in value", - value: "some-value", - expect: "some-value", - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - actual := GetNormalizedConsulPeer(tc.value) - require.Equal(t, actual, tc.expect) - }) - } -} diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go index 6d78654989..c71ee9ba55 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks.go @@ -6,13 +6,12 @@ package endpoints import ( "fmt" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" ) const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" @@ -20,12 +19,7 @@ const minSupportedConsulDataplaneVersion = "v1.0.0-beta1" // isConsulDataplaneSupported returns true if the consul-k8s version on the pod supports // consul-dataplane architecture of Consul. func isConsulDataplaneSupported(pod corev1.Pod) bool { - anno, ok := pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] - if !ok { - anno, ok = pod.Annotations[constants.AnnotationConsulK8sVersion] - } - - if ok { + if anno, ok := pod.Annotations[constants.AnnotationConsulK8sVersion]; ok { consulK8sVersion, err := version.NewVersion(anno) if err != nil { // Only consul-k8s v1.0.0+ (including pre-release versions) have the version annotation. So it would be diff --git a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go index 5aa7448ef3..36ad222d68 100644 --- a/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go +++ b/control-plane/connect-inject/controllers/endpoints/consul_client_health_checks_test.go @@ -7,15 +7,14 @@ import ( "testing" logrtest "github.com/go-logr/logr/testr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestIsConsulDataplaneSupported(t *testing.T) { @@ -47,7 +46,7 @@ func TestIsConsulDataplaneSupported(t *testing.T) { }, } if version != "" { - pod.ObjectMeta.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version + pod.ObjectMeta.Annotations[constants.AnnotationConsulK8sVersion] = version } require.Equal(t, c.expIsConsulDataplaneSupported, isConsulDataplaneSupported(pod)) diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go index ea22628022..29d9f5b497 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller.go @@ -13,22 +13,22 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/parsetags" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( @@ -44,6 +44,7 @@ const ( terminatingGateway = "terminating-gateway" ingressGateway = "ingress-gateway" + kubernetesSuccessReasonMsg = "Kubernetes health checks passing" envoyPrometheusBindAddr = "envoy_prometheus_bind_addr" envoyTelemetryCollectorBindSocketDir = "envoy_telemetry_collector_bind_socket_dir" defaultNS = "default" @@ -56,6 +57,12 @@ const ( // This address does not need to be routable as this node is ephemeral, and we're only providing it because // Consul's API currently requires node address to be provided when registering a node. consulNodeAddress = "127.0.0.1" + + // consulKubernetesCheckType is the type of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckType = "kubernetes-readiness" + + // consulKubernetesCheckName is the name of health check in Consul for Kubernetes readiness status. + consulKubernetesCheckName = "Kubernetes Readiness Check" ) type Controller struct { @@ -135,7 +142,7 @@ func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu var serviceEndpoints corev1.Endpoints // Ignore the request if the namespace of the endpoint is not allowed. - if common.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { + if shouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { return ctrl.Result{}, nil } @@ -310,20 +317,6 @@ func (r *Controller) registerServicesAndHealthCheck(apiClient *api.Client, pod c return nil } -func parseLocality(node corev1.Node) *api.Locality { - region := node.Labels[corev1.LabelTopologyRegion] - zone := node.Labels[corev1.LabelTopologyZone] - - if region == "" { - return nil - } - - return &api.Locality{ - Region: region, - Zone: zone, - } -} - // registerGateway creates Consul registrations for the Connect Gateways and registers them with Consul. // It also upserts a Kubernetes health check for the service based on whether the endpoint address is ready. func (r *Controller) registerGateway(apiClient *api.Client, pod corev1.Pod, serviceEndpoints corev1.Endpoints, healthStatus string, endpointAddressMap map[string]bool) error { @@ -411,11 +404,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints } } - var node corev1.Node - // Ignore errors because we don't want failures to block running services. - _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) - locality := parseLocality(node) - // We only want that annotation to be present when explicitly overriding the consul svc name // Otherwise, the Consul service name should equal the Kubernetes Service name. // The service name in Consul defaults to the Endpoints object name, and is overridden by the pod @@ -451,7 +439,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Meta: meta, Namespace: consulNS, Tags: tags, - Locality: locality, } serviceRegistration := &api.CatalogRegistration{ Node: common.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), @@ -462,8 +449,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, svcID), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: svcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -528,8 +515,6 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Namespace: consulNS, Proxy: proxyConfig, Tags: tags, - // Sidecar locality (not proxied service locality) is used for locality-aware routing. - Locality: locality, } // A user can enable/disable tproxy for an entire namespace. @@ -657,8 +642,8 @@ func (r *Controller) createServiceRegistrations(pod corev1.Pod, serviceEndpoints Service: proxyService, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, proxySvcID), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: proxySvcID, Output: getHealthCheckStatusReason(healthStatus, pod.Name, pod.Namespace), @@ -800,8 +785,8 @@ func (r *Controller) createGatewayRegistrations(pod corev1.Pod, serviceEndpoints Service: service, Check: &api.AgentCheck{ CheckID: consulHealthCheckID(pod.Namespace, pod.Name), - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: healthStatus, ServiceID: pod.Name, Namespace: consulNS, @@ -893,7 +878,7 @@ func consulHealthCheckID(k8sNS string, serviceID string) string { // as well as pod name and namespace and returns the reason message. func getHealthCheckStatusReason(healthCheckStatus, podName, podNamespace string) string { if healthCheckStatus == api.HealthPassing { - return constants.KubernetesSuccessReasonMsg + return kubernetesSuccessReasonMsg } return fmt.Sprintf("Pod \"%s/%s\" is not ready", podNamespace, podName) @@ -1164,9 +1149,8 @@ func processPreparedQueryUpstream(pod corev1.Pod, rawUpstream string) api.Upstre // processUnlabeledUpstream processes an upstream in the format: // [service-name].[service-namespace].[service-partition]:[port]:[optional datacenter]. -// There is no unlabeled field for peering. func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string) (api.Upstream, error) { - var datacenter, svcName, namespace, partition string + var datacenter, svcName, namespace, partition, peer string var port int32 var upstream api.Upstream @@ -1200,7 +1184,7 @@ func (r *Controller) processUnlabeledUpstream(pod corev1.Pod, rawUpstream string upstream = api.Upstream{ DestinationType: api.UpstreamDestTypeService, DestinationPartition: partition, - DestinationPeer: "", + DestinationPeer: peer, DestinationNamespace: namespace, DestinationName: svcName, Datacenter: datacenter, @@ -1290,6 +1274,26 @@ func (r *Controller) processLabeledUpstream(pod corev1.Pod, rawUpstream string) return upstream, nil } +// shouldIgnore ignores namespaces where we don't connect-inject. +func shouldIgnore(namespace string, denySet, allowSet mapset.Set) bool { + // Ignores system namespaces. + if namespace == metav1.NamespaceSystem || namespace == metav1.NamespacePublic || namespace == "local-path-storage" { + return true + } + + // Ignores deny list. + if denySet.Contains(namespace) { + return true + } + + // Ignores if not in allow list or allow list is not *. + if !allowSet.Contains("*") && !allowSet.Contains(namespace) { + return true + } + + return false +} + // consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace // depending on Consul Namespaces being enabled and the value of namespace mirroring. func (r *Controller) consulNamespace(namespace string) string { diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go index 44e64acba1..52064cd519 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_ent_test.go @@ -14,6 +14,9 @@ import ( logrtest "github.com/go-logr/logr/testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -23,10 +26,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // TestReconcileCreateEndpoint tests the logic to create service instances in Consul from the addresses in the Endpoints @@ -172,40 +171,40 @@ func TestReconcileCreateEndpointWithNamespaces(t *testing.T) { CheckID: fmt.Sprintf("%s/pod1-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod1-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created", testCase.SourceKubeNS), ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, { CheckID: fmt.Sprintf("%s/pod2-service-created-sidecar-proxy", testCase.SourceKubeNS), ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ExpConsulNS, }, }, @@ -446,30 +445,30 @@ func TestReconcileCreateGatewayWithNamespaces(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: "default", }, { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ConsulNS, }, { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, Namespace: testCase.ConsulNS, }, }, @@ -1544,7 +1543,7 @@ func TestReconcileUpdateEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service.Service, svc.Service.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) token, _, err := consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -1855,7 +1854,7 @@ func TestReconcileDeleteEndpointWithNamespaces(t *testing.T) { } else { writeOpts.Namespace = ts.ExpConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix, false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], ts.ExpConsulNS, ts.Mirror, ts.MirrorPrefix) token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -2153,7 +2152,7 @@ func TestReconcileDeleteGatewayWithNamespaces(t *testing.T) { writeOpts.Namespace = ts.ConsulNS } - test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "", false) + test.SetupK8sAuthMethodWithNamespaces(t, consulClient, svc.Service, svc.Meta[constants.MetaKeyKubeNS], writeOpts.Namespace, false, "") token, _, err = consulClient.ACL().Login(&api.ACLLoginParams{ AuthMethod: test.AuthMethod, BearerToken: test.ServiceAccountJWTToken, @@ -2224,7 +2223,7 @@ func createPodWithNamespace(name, namespace, ip string, inject bool, managedByEn Namespace: namespace, Labels: map[string]string{}, Annotations: map[string]string{ - constants.LegacyAnnotationConsulK8sVersion: "1.0.0", + constants.AnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ diff --git a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go index cb0e6807c1..4c685f2bab 100644 --- a/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go +++ b/control-plane/connect-inject/controllers/endpoints/endpoints_controller_test.go @@ -13,6 +13,9 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" @@ -24,10 +27,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const ( @@ -35,6 +34,59 @@ const ( consulNodeName = "test-node-virtual" ) +func TestShouldIgnore(t *testing.T) { + t.Parallel() + cases := []struct { + name string + namespace string + denySet mapset.Set + allowSet mapset.Set + expected bool + }{ + { + name: "system namespace", + namespace: "kube-system", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "other system namespace", + namespace: "local-path-storage", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "any namespace allowed", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("*"), + expected: false, + }, + { + name: "in deny list", + namespace: "foo", + denySet: mapset.NewSetWith("foo"), + allowSet: mapset.NewSetWith("*"), + expected: true, + }, + { + name: "not in allow list", + namespace: "foo", + denySet: mapset.NewSetWith(), + allowSet: mapset.NewSetWith("bar"), + expected: true, + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + actual := shouldIgnore(tt.namespace, tt.denySet, tt.allowSet) + require.Equal(t, tt.expected, actual) + }) + } +} + func TestHasBeenInjected(t *testing.T) { t.Parallel() cases := []struct { @@ -787,37 +839,37 @@ func TestReconcileCreateEndpoint_MultiportService(t *testing.T) { CheckID: "default/pod1-web", ServiceName: "web", ServiceID: "pod1-web", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-sidecar-proxy", ServiceName: "web-sidecar-proxy", ServiceID: "pod1-web-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin", ServiceName: "web-admin", ServiceID: "pod1-web-admin", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-web-admin-sidecar-proxy", ServiceName: "web-admin-sidecar-proxy", ServiceID: "pod1-web-admin-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1045,19 +1097,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1130,10 +1182,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1202,10 +1254,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, metricsEnabled: true, @@ -1275,10 +1327,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/mesh-gateway", ServiceName: "mesh-gateway", ServiceID: "mesh-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, metricsEnabled: true, @@ -1338,10 +1390,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1404,10 +1456,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/terminating-gateway", ServiceName: "terminating-gateway", ServiceID: "terminating-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1504,10 +1556,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1606,10 +1658,10 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/ingress-gateway", ServiceName: "ingress-gateway", ServiceID: "ingress-gateway", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1707,37 +1759,37 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created", ServiceName: "service-created", ServiceID: "pod2-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -1848,28 +1900,28 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod2-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, expErr: "1 error occurred:\n\t* pods \"pod3\" not found\n\n", @@ -1889,17 +1941,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { pod1.Annotations[constants.AnnotationUpstreams] = "upstream1:1234" pod1.Annotations[constants.AnnotationEnableMetrics] = "true" pod1.Annotations[constants.AnnotationPrometheusScrapePort] = "12345" - pod1.Spec.NodeName = "my-node" - node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-node", - Namespace: "default", - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-created", @@ -1920,7 +1961,7 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, }, } - return []runtime.Object{pod1, node, endpoint} + return []runtime.Object{pod1, endpoint} }, expectedConsulSvcInstances: []*api.CatalogService{ { @@ -1940,10 +1981,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { }, ServiceTags: []string{"abc,123", "pod1"}, ServiceProxy: &api.AgentServiceConnectProxyConfig{}, - ServiceLocality: &api.Locality{ - Region: "us-west-1", - Zone: "us-west-1a", - }, }, }, expectedProxySvcInstances: []*api.CatalogService{ @@ -1969,10 +2006,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { "envoy_telemetry_collector_bind_socket_dir": "/consul/connect-inject", }, }, - ServiceLocality: &api.Locality{ - Region: "us-west-1", - Zone: "us-west-1a", - }, ServiceMeta: map[string]string{ "name": "abc", "version": "2", @@ -1991,19 +2024,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-different-consul-svc-name", ServiceName: "different-consul-svc-name", ServiceID: "pod1-different-consul-svc-name", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-different-consul-svc-name-sidecar-proxy", ServiceName: "different-consul-svc-name-sidecar-proxy", ServiceID: "pod1-different-consul-svc-name-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2082,19 +2115,19 @@ func TestReconcileCreateEndpoint(t *testing.T) { CheckID: "default/pod1-service-created", ServiceName: "service-created", ServiceID: "pod1-service-created", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-created-sidecar-proxy", ServiceName: "service-created-sidecar-proxy", ServiceID: "pod1-service-created-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2160,7 +2193,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedConsulSvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTags, instance.ServiceTags) - require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceLocality, instance.ServiceLocality) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceTaggedAddresses, instance.ServiceTaggedAddresses) require.Equal(t, tt.expectedConsulSvcInstances[i].ServiceProxy, instance.ServiceProxy) if tt.nodeMeta != nil { @@ -2177,7 +2209,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { require.Equal(t, tt.expectedProxySvcInstances[i].ServicePort, instance.ServicePort) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceMeta, instance.ServiceMeta) require.Equal(t, tt.expectedProxySvcInstances[i].ServiceTags, instance.ServiceTags) - require.Equal(t, tt.expectedProxySvcInstances[i].ServiceLocality, instance.ServiceLocality) if tt.nodeMeta != nil { require.Equal(t, tt.expectedProxySvcInstances[i].NodeMeta, instance.NodeMeta) } @@ -2208,36 +2239,6 @@ func TestReconcileCreateEndpoint(t *testing.T) { } } -func TestParseLocality(t *testing.T) { - t.Run("no labels", func(t *testing.T) { - n := corev1.Node{} - require.Nil(t, parseLocality(n)) - }) - - t.Run("zone only", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Nil(t, parseLocality(n)) - }) - - t.Run("everything", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Equal(t, &api.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n)) - }) -} - // Tests updating an Endpoints object. // - Tests updates via the register codepath: // - When an address in an Endpoint is updated, that the corresponding service instance in Consul is updated. @@ -2307,8 +2308,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated", ServiceName: "service-updated", @@ -2334,8 +2335,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthCritical, ServiceID: "pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", @@ -2359,19 +2360,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -2418,8 +2419,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated", ServiceID: "pod1-service-updated", @@ -2445,8 +2446,8 @@ func TestReconcileUpdateEndpoint(t *testing.T) { }, Check: &api.AgentCheck{ CheckID: "default/pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, - Type: constants.ConsulKubernetesCheckType, + Name: consulKubernetesCheckName, + Type: consulKubernetesCheckType, Status: api.HealthPassing, ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", @@ -2470,19 +2471,19 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: constants.ConsulKubernetesCheckType, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthCritical, Output: "Pod \"default/pod1\" is not ready", - Type: constants.ConsulKubernetesCheckType, + Type: consulKubernetesCheckType, }, }, }, @@ -2756,37 +2757,37 @@ func TestReconcileUpdateEndpoint(t *testing.T) { CheckID: "default/pod1-service-updated", ServiceName: "service-updated", ServiceID: "pod1-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod1-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod1-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated", ServiceName: "service-updated", ServiceID: "pod2-service-updated", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, { CheckID: "default/pod2-service-updated-sidecar-proxy", ServiceName: "service-updated-sidecar-proxy", ServiceID: "pod2-service-updated-sidecar-proxy", - Name: constants.ConsulKubernetesCheckName, + Name: consulKubernetesCheckName, Status: api.HealthPassing, - Output: constants.KubernetesSuccessReasonMsg, - Type: constants.ConsulKubernetesCheckType, + Output: kubernetesSuccessReasonMsg, + Type: consulKubernetesCheckType, }, }, }, @@ -3663,7 +3664,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -3728,7 +3729,7 @@ func TestReconcileUpdateEndpoint_LegacyService(t *testing.T) { k8sObjects: func() []runtime.Object { pod1 := createServicePod("pod1", "1.2.3.4", true, true) pod1.Status.HostIP = "127.0.0.1" - pod1.Annotations[constants.LegacyAnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. + pod1.Annotations[constants.AnnotationConsulK8sVersion] = "0.99.0" // We want a version less than 1.0.0. endpoint := &corev1.Endpoints{ ObjectMeta: metav1.ObjectMeta{ Name: "service-updated", @@ -6573,7 +6574,7 @@ func createServicePod(name, ip string, inject bool, managedByEndpointsController Namespace: "default", Labels: map[string]string{}, Annotations: map[string]string{ - constants.LegacyAnnotationConsulK8sVersion: "1.0.0", + constants.AnnotationConsulK8sVersion: "1.0.0", }, }, Status: corev1.PodStatus{ diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go deleted file mode 100644 index 82dd583dfc..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller.go +++ /dev/null @@ -1,649 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "context" - "crypto/sha256" - "fmt" - "net" - "sort" - "strings" - - "github.com/go-logr/logr" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - kindReplicaSet = "ReplicaSet" -) - -type Controller struct { - client.Client - // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - // WriteCache keeps track of records already written to Consul in order to enable debouncing of writes. - // This is useful in particular for this controller which will see potentially many more reconciles due to - // endpoint changes (e.g. pod health) than changes to service data written to Consul. - // It is intentionally simple and best-effort, and does not guarantee against all redundant writes. - // It is not persistent across restarts of the controller process. - WriteCache WriteCache - - Log logr.Logger - - Scheme *runtime.Scheme - context.Context -} - -func (r *Controller) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - if r.WriteCache == nil { - return fmt.Errorf("WriteCache was not configured for Controller") - } - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Endpoints{}). - Complete(r) -} - -// Reconcile reads the state of an Endpoints object for a Kubernetes Service and reconciles Consul services which -// correspond to the Kubernetes Service. These events are driven by changes to the Pods backing the Kube service. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var endpoints corev1.Endpoints - var service corev1.Service - - // Ignore the request if the namespace of the endpoint is not allowed. - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - // If the Endpoints object has been deleted (and we get an IsNotFound error), - // we need to deregister that service from Consul. - err = r.Client.Get(ctx, req.NamespacedName, &endpoints) - if k8serrors.IsNotFound(err) { - err = r.deregisterService(ctx, resourceClient, req) - return ctrl.Result{}, err - } else if err != nil { - r.Log.Error(err, "failed to get Endpoints", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved Endpoints", "name", req.Name, "ns", req.Namespace) - - // We expect this to succeed if the Endpoints fetch for the Service succeeded. - err = r.Client.Get(r.Context, types.NamespacedName{Name: endpoints.Name, Namespace: endpoints.Namespace}, &service) - if err != nil { - r.Log.Error(err, "failed to get Service", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved Service", "name", req.Name, "ns", req.Namespace) - - consulSvc, err := r.getConsulService(ctx, &ClientPodFetcher{client: r.Client}, service, endpoints) - if err != nil { - r.Log.Error(err, "failed to build Consul service resource", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - // If we don't have at least one mesh-injected pod selected by the service, don't register. - // Note that we only _delete_ services when they're deleted from K8s, not when endpoints or - // workload selectors are empty. This ensures that failover can occur normally when targeting - // the existing VIP (ClusterIP) assigned to the service. - if consulSvc.Workloads == nil { - return ctrl.Result{}, nil - } - - // Register the service in Consul. - id := getServiceID( - service.Name, // Consul and Kubernetes service name will always match - r.getConsulNamespace(service.Namespace), - r.getConsulPartition()) - meta := getServiceMeta(service) - k8sUid := string(service.UID) - if err = r.ensureService(ctx, &defaultResourceReadWriter{resourceClient}, k8sUid, id, meta, consulSvc); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "service", service.GetName(), "ns", req.Namespace, - "consul-ns", r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -func (r *Controller) getConsulService(ctx context.Context, pf PodFetcher, service corev1.Service, endpoints corev1.Endpoints) (*pbcatalog.Service, error) { - prefixedPods, exactNamePods, err := r.getWorkloadDataFromEndpoints(ctx, pf, endpoints) - if err != nil { - return nil, err - } - - // Create Consul Service resource to be registered. - return &pbcatalog.Service{ - Workloads: getWorkloadSelector(prefixedPods, exactNamePods), - Ports: getServicePorts(service, prefixedPods, exactNamePods), - VirtualIps: r.getServiceVIPs(service), - }, nil -} - -type podSetData struct { - podCount int - samplePod *corev1.Pod -} - -// selectorPodData represents data for each set of pods represented by a WorkloadSelector value. -// The data may be for several pods (prefix) or a single pod (exact name). -// This is used for choosing the ideal Consul service TargetPort value when the K8s service target port is numeric. -type selectorPodData map[string]*podSetData - -// getWorkloadDataFromEndpoints accumulates data to supply the Consul service WorkloadSelector and TargetPort from -// Endpoints based on pod names and owners. -func (r *Controller) getWorkloadDataFromEndpoints(ctx context.Context, pf PodFetcher, endpoints corev1.Endpoints) (selectorPodData, selectorPodData, error) { - var errs error - - // Determine the workload selector by fetching as many pods as needed to accumulate prefixes - // and exact pod name matches. - // - // If the K8s service target port is numeric, we also use this information to determine the - // appropriate Consul target port value. - prefixedPods := make(selectorPodData) - exactNamePods := make(selectorPodData) - ignoredPodPrefixes := make(map[string]any) - for address := range allAddresses(endpoints.Subsets) { - if address.TargetRef != nil && address.TargetRef.Kind == "Pod" { - podName := types.NamespacedName{Name: address.TargetRef.Name, Namespace: endpoints.Namespace} - - // Accumulate owner prefixes and exact pod names for Consul workload selector. - // If this pod is already covered by a known owner prefix, skip it. - // If not, fetch the owner. If the owner has a unique prefix, add it to known prefixes. - // If not, add the pod name to exact name matches. - maybePodOwnerPrefix := getOwnerPrefixFromPodName(podName.Name) - - // If prefix is ignored, skip pod. - if _, ok := ignoredPodPrefixes[maybePodOwnerPrefix]; ok { - continue - } - - if existingPodData, ok := prefixedPods[maybePodOwnerPrefix]; !ok { - // Fetch pod info from K8s. - pod, err := pf.GetPod(ctx, podName) - if err != nil { - r.Log.Error(err, "failed to get pod", "name", podName.Name, "ns", endpoints.Namespace) - errs = multierror.Append(errs, err) - continue - } - - // Store data corresponding to the new selector value, which may be an actual set or exact pod. - podData := podSetData{ - podCount: 1, - samplePod: pod, - } - - // Add pod to workload selector values as appropriate. - // Pods can appear more than once in Endpoints subsets, so we use a set for exact names as well. - if prefix := getOwnerPrefixFromPod(pod); prefix != "" { - if inject.HasBeenMeshInjected(*pod) { - // Add to the list of pods represented by this prefix. This list is used by - // `getEffectiveTargetPort` to determine the most-used target container port name if the - // k8s service target port is numeric. - prefixedPods[prefix] = &podData - } else { - // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. - // Remember its prefix to avoid fetching its siblings needlessly. - ignoredPodPrefixes[prefix] = true - } - } else { - if inject.HasBeenMeshInjected(*pod) { - exactNamePods[podName.Name] = &podData - } - // If the pod hasn't been mesh-injected, ignore it, as it won't be available as a workload. - // No need to remember ignored exact pod names since we don't expect to see them twice. - } - } else { - // We've seen this prefix before. - // Keep track of how many times so that we can choose a container port name if needed later. - existingPodData.podCount += 1 - } - } - } - - return prefixedPods, exactNamePods, errs -} - -// allAddresses combines all Endpoints subset addresses to a single set. Service registration by this controller -// operates independent of health, and an address can appear in multiple subsets if it has a mix of ready and not-ready -// ports, so we combine them here to simplify iteration. -func allAddresses(subsets []corev1.EndpointSubset) map[corev1.EndpointAddress]any { - m := make(map[corev1.EndpointAddress]any) - for _, sub := range subsets { - for _, readyAddress := range sub.Addresses { - m[readyAddress] = true - } - for _, notReadyAddress := range sub.NotReadyAddresses { - m[notReadyAddress] = true - } - } - return m -} - -// getOwnerPrefixFromPodName extracts the owner name prefix from a pod name. -func getOwnerPrefixFromPodName(podName string) string { - podNameParts := strings.Split(podName, "-") - return strings.Join(podNameParts[:len(podNameParts)-1], "-") -} - -// getOwnerPrefixFromPod returns the common name prefix of the pod, if the pod is a member of a set with a unique name -// prefix. Currently, this only applies to ReplicaSets. -// -// We have to fetch the owner and check its type because pod names cannot be disambiguated from pod owner names due to -// the `-` delimiter and unique ID parts also being valid name components. -// -// If the pod owner does not have a unique name, the empty string is returned. -func getOwnerPrefixFromPod(pod *corev1.Pod) string { - for _, ref := range pod.OwnerReferences { - if ref.Kind == "ReplicaSet" { - return ref.Name - } - } - return "" -} - -// ensureService upserts a Consul service resource if an identical write has not already been made to Consul since this -// controller was started. If the check for a previous write fails, the resource is written anyway. -func (r *Controller) ensureService(ctx context.Context, rw resourceReadWriter, k8sUid string, id *pbresource.ID, meta map[string]string, consulSvc *pbcatalog.Service) error { - // Use Marshal w/ Deterministic option to ensure write hash generated from Data is consistent. - data := new(anypb.Any) - if err := anypb.MarshalFrom(data, consulSvc, proto.MarshalOptions{Deterministic: true}); err != nil { - return err - } - - // Use the locally-created Resource and ID (without Uid and Version) when writing so that it - // behaves as an upsert rather than CAS. - consulSvcResource := &pbresource.Resource{ - Id: id, - Data: data, - Metadata: meta, - } - - writeHash, err := getWriteHash(consulSvcResource) - if err != nil { - r.Log.Error(err, "failed to get write hash for service; assuming it is out of sync", - getLogFieldsForResource(id)...) - } - key := getWriteCacheKey(types.NamespacedName{Name: id.Name, Namespace: id.Tenancy.Namespace}) - generationFetchFn := func() string { - // Check for whether a matching service already exists in Consul. - // Gracefully fail on error. This allows us to make a best-effort write attempt in - // case of a persistent read error or permissions issue that does not impact writing. - resp, err := rw.Read(ctx, &pbresource.ReadRequest{Id: id}) - if s, ok := status.FromError(err); !ok || (s.Code() != codes.OK && s.Code() != codes.NotFound) { - r.Log.Error(err, "failed to read existing service resource from Consul; assuming it is out of sync", - append(getLogFieldsForResource(id), "code", s.Code(), "message", s.Message())...) - return "" - } - return resp.GetResource().GetGeneration() - } - if r.WriteCache.hasMatch(key, writeHash, generationFetchFn, k8sUid) { - r.Log.V(1).Info("skipping service write due to matching write hash") - return nil - } - - r.Log.Info("writing service to Consul", getLogFieldsForResource(consulSvcResource.Id)...) - resp, err := rw.Write(ctx, &pbresource.WriteRequest{Resource: consulSvcResource}) - if err != nil { - r.Log.Error(err, fmt.Sprintf("failed to write service: %+v", consulSvc), - getLogFieldsForResource(consulSvcResource.Id)...) - return err - } - - generation := resp.GetResource().GetGeneration() - r.Log.Info("caching service write to Consul", "hash", writeHash, "generation", generation, - "k8sUid", k8sUid) - r.WriteCache.update(key, writeHash, generation, k8sUid) - - return nil -} - -// resourceReadWriter wraps pbresource.ResourceServiceClient for testing purposes. -// The default implementation is a passthrough used outside of tests. -type resourceReadWriter interface { - Read(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - Write(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) -} - -type defaultResourceReadWriter struct { - client pbresource.ResourceServiceClient -} - -func (c *defaultResourceReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return c.client.Read(ctx, req) -} - -func (c *defaultResourceReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return c.client.Write(ctx, req) -} - -func getServiceID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getServicePorts converts Kubernetes Service ports data into Consul service ports. -func getServicePorts(service corev1.Service, prefixedPods selectorPodData, exactNamePods selectorPodData) []*pbcatalog.ServicePort { - ports := make([]*pbcatalog.ServicePort, 0, len(service.Spec.Ports)+1) - - for _, p := range service.Spec.Ports { - // Service mesh only supports TCP as the L4 Protocol (not to be confused w/ L7 AppProtocol). - // - // This check is necessary to deduplicate VirtualPort values when multiple declared ServicePort values exist - // for the same port, which is possible in K8s when e.g. multiplexing TCP and UDP traffic over a single port. - // - // If we otherwise see repeat port values in a K8s service, we pass along and allow Consul to fail validation. - if p.Protocol == corev1.ProtocolTCP { - //TODO(NET-5705): Error check reserved "mesh" target port - ports = append(ports, &pbcatalog.ServicePort{ - VirtualPort: uint32(p.Port), - TargetPort: getEffectiveTargetPort(p.TargetPort, prefixedPods, exactNamePods), - Protocol: inject.GetPortProtocol(p.AppProtocol), - }) - } - } - - // Sort for comparison stability during write deduplication. - sort.Slice(ports, func(i, j int) bool { - return ports[i].VirtualPort < ports[j].VirtualPort - }) - - // Append Consul service mesh port in addition to discovered ports. - ports = append(ports, &pbcatalog.ServicePort{ - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }) - - return ports -} - -func getEffectiveTargetPort(targetPort intstr.IntOrString, prefixedPods selectorPodData, exactNamePods selectorPodData) string { - // The Kubernetes service is targeting a port name; use it directly. - // The expected behavior of Kubernetes is that all included Endpoints conform and have a matching named port. - // This is the simplest path and preferred over services targeting by port number. - if targetPort.Type == intstr.String { - return targetPort.String() - } - - // The Kubernetes service is targeting a numeric port. This is more complicated for mapping to Consul: - // - Endpoints will contain _all_ selected pods, not just those with a matching declared port number. - // - Consul Workload ports always have a name, so we must determine the best name to match on. - // - There may be more than one option among the pods with named ports, including no name at all. - // - // Our best-effort approach is to find the most prevalent port name among selected pods that _do_ declare the target - // port explicitly in container ports. We'll assume that for each set of pods, the first pod is "representative" - - // i.e. we expect a ReplicaSet to be homogenous. In the vast majority of cases, this means we'll be looking for the - // largest selected ReplicaSet and using the first pod from it. - // - // The goal is to make this determination without fetching all pods belonging to the service, as that would be a - // very expensive operation to repeat every time endpoints change, and we don't expect the target port to change - // often if ever across pod/deployment lifecycles. - // - //TODO(NET-5706) in GA, we intend to change port selection to allow for Consul TargetPort to be numeric. If we - // retain the port selection model used here beyond GA, we should consider updating it to also consider pod health, - // s.t. when the selected port name changes between deployments of a ReplicaSet, we route traffic to ports - // belonging to the set most able to serve traffic, rather than simply the largest one. - targetPortInt := int32(targetPort.IntValue()) - var mostPrevalentContainerPort *corev1.ContainerPort - maxCount := 0 - effectiveNameForPort := func(port *corev1.ContainerPort) string { - if port.Name != "" { - return port.Name - } - return targetPort.String() - } - for _, podData := range prefixedPods { - containerPort := getTargetContainerPort(targetPortInt, podData.samplePod) - - // Ignore pods without a declared port matching the service targetPort. - if containerPort == nil { - continue - } - - // If this is the most prevalent container port by pod set size, update result. - if maxCount < podData.podCount { - mostPrevalentContainerPort = containerPort - maxCount = podData.podCount - } - } - - if mostPrevalentContainerPort != nil { - return effectiveNameForPort(mostPrevalentContainerPort) - } - - // If no pod sets have the expected target port, fall back to the most common name among exact-name pods. - // An assumption here is that exact name pods mixed with pod sets will be rare, and sets should be preferred. - if len(exactNamePods) > 0 { - nameCount := make(map[string]int) - for _, podData := range exactNamePods { - if containerPort := getTargetContainerPort(targetPortInt, podData.samplePod); containerPort != nil { - nameCount[effectiveNameForPort(containerPort)] += 1 - } - } - if len(nameCount) > 0 { - maxNameCount := 0 - mostPrevalentContainerPortName := "" - for name, count := range nameCount { - if maxNameCount < count { - mostPrevalentContainerPortName = name - maxNameCount = count - } - } - return mostPrevalentContainerPortName - } - } - - // If still no match for the target port, fall back to string-ifying the target port name, which - // is what the PodController will do when converting unnamed ContainerPorts to Workload ports. - return targetPort.String() -} - -// getTargetContainerPort returns the pod ContainerPort matching the given numeric port value, or nil if none is found. -func getTargetContainerPort(targetPort int32, pod *corev1.Pod) *corev1.ContainerPort { - for _, c := range pod.Spec.Containers { - if len(c.Ports) == 0 { - continue - } - for _, p := range c.Ports { - if p.ContainerPort == targetPort && p.Protocol == corev1.ProtocolTCP { - return &p - } - } - } - return nil -} - -// getServiceVIPs returns the VIPs to associate with the registered Consul service. This will contain the Kubernetes -// Service ClusterIP if it exists. -// -// Note that we always provide this data regardless of whether TProxy is enabled, deferring to individual proxy configs -// to decide whether it's used. -func (r *Controller) getServiceVIPs(service corev1.Service) []string { - if parsedIP := net.ParseIP(service.Spec.ClusterIP); parsedIP == nil { - r.Log.Info("skipping service registration virtual IP assignment due to invalid or unset ClusterIP", "name", service.Name, "ns", service.Namespace, "ip", service.Spec.ClusterIP) - return nil - } - - // Note: This slice needs to be sorted for stable comparison during write deduplication. - // If additional values are added in the future, the output order should be consistent. - return []string{service.Spec.ClusterIP} -} - -func getServiceMeta(service corev1.Service) map[string]string { - meta := map[string]string{ - constants.MetaKeyKubeNS: service.Namespace, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - } - return meta -} - -// getWorkloadSelector returns the WorkloadSelector for the given pod name prefixes and exact names. -// It returns nil if the provided name sets are empty. -func getWorkloadSelector(prefixedPods selectorPodData, exactNamePods selectorPodData) *pbcatalog.WorkloadSelector { - // If we don't have any values, return nil - if len(prefixedPods) == 0 && len(exactNamePods) == 0 { - return nil - } - - // Create the WorkloadSelector - workloads := &pbcatalog.WorkloadSelector{} - for v := range prefixedPods { - workloads.Prefixes = append(workloads.Prefixes, v) - } - for v := range exactNamePods { - workloads.Names = append(workloads.Names, v) - } - - // Sort for comparison stability during write deduplication - sort.Strings(workloads.Prefixes) - sort.Strings(workloads.Names) - - return workloads -} - -// deregisterService deletes the service resource corresponding to the given name and namespace from Consul. -// This operation is idempotent and can be executed for non-existent services. -func (r *Controller) deregisterService(ctx context.Context, resourceClient pbresource.ResourceServiceClient, req ctrl.Request) error { - // Regardless of whether we get an error on delete, remove the resource from the cache as we intend for it - // to be deleted and the record is no longer valid for preventing writes. - r.WriteCache.remove(getWriteCacheKey(req.NamespacedName)) - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ - Id: getServiceID(req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()), - }) - return err -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO(NET-5652): remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -// getWriteCacheKey gets a key to track syncronization of a K8s service to deduplicate writes to Consul. -// See also WriteCache.hasMatch. -func getWriteCacheKey(serviceName types.NamespacedName) string { - return serviceName.String() -} - -// getWriteHash gets a hash of the given resource to deduplicate writes to Consul. -// -// This hash is not intended to be cryptographically secure, only deterministic and collision-resistent -// for tens of thousands of values. -// -// If an error occurs marshalling the resource for the hash, returns a nil hash value and the error. -// error will be returned. -func getWriteHash(r *pbresource.Resource) ([]byte, error) { - // We Marshal the entire resource (not just its own Data, which is already serialized) - // in order to take advantage of the deterministic marshal offered by proto and include - // fields like Meta, which are not part of the resource Data. - data, err := proto.MarshalOptions{Deterministic: true}.Marshal(r) - if err != nil { - return nil, err - } - h := sha256.Sum256(data) - return h[:], nil -} - -func getLogFieldsForResource(id *pbresource.ID) []any { - return []any{ - "name", id.Name, - "ns", id.Tenancy.Namespace, - "partition", id.Tenancy.Partition, - } -} - -// PodFetcher fetches pods by NamespacedName. This interface primarily exists for testing. -type PodFetcher interface { - GetPod(context.Context, types.NamespacedName) (*corev1.Pod, error) -} - -// ClientPodFetcher wraps a Kubernetes client to implement PodFetcher. This is the only implementation outside of tests. -type ClientPodFetcher struct { - client client.Client -} - -func (c *ClientPodFetcher) GetPod(ctx context.Context, name types.NamespacedName) (*corev1.Pod, error) { - var pod corev1.Pod - err := c.client.Get(ctx, name, &pod) - if err != nil { - return nil, err - } - return &pod, nil -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go deleted file mode 100644 index 636a1ab923..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_ent_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package endpointsv2 - -import ( - "testing" -) - -// TODO: ConsulDestinationNamespace and EnableNSMirroring +/- prefix - -// TODO(zalimeni) -// Tests new Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_CreateService_WithNamespaces(t *testing.T) { - -} - -// TODO(zalimeni) -// Tests updating Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_UpdateService_WithNamespaces(t *testing.T) { - -} - -// TODO(zalimeni) -// Tests removing Service registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_DeleteService_WithNamespaces(t *testing.T) { - -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go b/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go deleted file mode 100644 index cfbd7d7cc2..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/endpoints_controller_test.go +++ /dev/null @@ -1,2374 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "context" - "fmt" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - kindDaemonSet = "DaemonSet" -) - -var ( - appProtocolHttp = "http" - appProtocolHttp2 = "http2" - appProtocolGrpc = "grpc" -) - -type reconcileCase struct { - name string - svcName string - k8sObjects func() []runtime.Object - existingResource *pbresource.Resource - expectedResource *pbresource.Resource - targetConsulNs string - targetConsulPartition string - expErr string - caseFn func(*testing.T, *reconcileCase, *Controller, pbresource.ResourceServiceClient) -} - -// TODO(NET-5716): Allow/deny namespaces for reconcile tests -// TODO(NET-5932): Add tests for consistently sorting repeated output fields (getConsulService, getServicePorts) - -func TestReconcile_CreateService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Empty endpoints do not get registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{}, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{endpoints, service} - }, - }, - { - name: "Endpoints without injected pods do not get registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - removeMeshInjectStatus(t, pod1) - removeMeshInjectStatus(t, pod2) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - }, - { - name: "Basic endpoints", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Unhealthy endpoints should be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - // Split addresses between ready and not-ready - Addresses: addressesForPods(pod1), - NotReadyAddresses: addressesForPods(pod2), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Both replicasets (ready and not ready) should be present - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Pods with only some service ports should be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - // Two separate endpoint subsets w/ each of 2 ports served by a different replicaset - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - { - Addresses: addressesForPods(pod2), - Ports: []corev1.EndpointPort{ - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Both replicasets should be present even though neither serves both ports - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Named container port gets the pod port name", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", - // Named port with container port value matching service target port - containerWithPort("named-port", 2345), - // Unnamed port with container port value matching service target port - containerWithPort("", 6789)) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromInt(6789), // Numeric target port - AppProtocol: &appProtocolGrpc, - }, - { - Name: "unmatched-port", - Port: 10010, - Protocol: "TCP", - TargetPort: intstr.FromInt(10010), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "6789", // Matches service target number - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10010, - TargetPort: "10010", // Matches service target number (unmatched by container ports) - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Container port mix gets the name from largest matching pod set", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - // Unnamed port matching service target port. - // Also has second named port, and is not the most prevalent set for that port. - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde", - containerWithPort("", 2345), - containerWithPort("api-port", 6789)) - - // Named port with different name from most prevalent pods. - // Also has second unnamed port, and _is_ the most prevalent set for that port. - pod2a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", - containerWithPort("another-port-name", 2345), - containerWithPort("", 6789)) - pod2b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij", - containerWithPort("another-port-name", 2345), - containerWithPort("", 6789)) - - // Named port with container port value matching service target port. - // The most common "set" of pods, so should become the port name for service target port. - pod3a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - pod3b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - pod3c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno", - containerWithPort("named-port", 2345)) - - // Named port that does not match service target port. - // More common "set" of pods selected by the service, but does not have a target port (value) match. - pod4a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - pod4d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-pqrst", - containerWithPort("non-matching-named-port", 5432)) - - // Named port from non-injected pods. - // More common "set" of pods selected by the service, but should be filtered out. - pod5a := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5b := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5c := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - pod5d := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-uvwxy", - containerWithPort("ignored-named-port", 2345)) - for _, p := range []*corev1.Pod{pod5a, pod5b, pod5c, pod5d} { - removeMeshInjectStatus(t, p) - } - - // Named port with container port value matching service target port. - // Single pod from non-ReplicaSet owner. Should not take precedence over set pods. - pod6a := createServicePod(kindDaemonSet, "service-created-ds", "12345", - containerWithPort("another-port-name", 2345)) - - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods( - pod1, - pod2a, pod2b, - pod3a, pod3b, pod3c, - pod4a, pod4b, pod4c, pod4d, - pod5a, pod5b, pod5c, pod5d, - pod6a), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromInt(6789), // Numeric target port - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{ - pod1, - pod2a, pod2b, - pod3a, pod3b, pod3c, - pod4a, pod4b, pod4c, pod4d, - pod5a, pod5b, pod5c, pod5d, - pod6a, - endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "6789", // Matches service target number due to unnamed being most common - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{ - "service-created-rs-abcde", - "service-created-rs-fghij", - "service-created-rs-klmno", - "service-created-rs-pqrst", - }, - Names: []string{ - "service-created-ds-12345", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Numeric service target port: Most used container port name from exact name pods used when no pod sets present", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - // Named port with different name from most prevalent pods. - pod1a := createServicePod(kindDaemonSet, "service-created-ds1", "12345", - containerWithPort("another-port-name", 2345)) - - // Named port with container port value matching service target port. - // The most common container port name, so should become the port name for service target port. - pod2a := createServicePod(kindDaemonSet, "service-created-ds2", "12345", - containerWithPort("named-port", 2345)) - pod2b := createServicePod(kindDaemonSet, "service-created-ds2", "23456", - containerWithPort("named-port", 2345)) - - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods( - pod1a, - pod2a, pod2b), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromInt(2345), // Numeric target port - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{ - pod1a, - pod2a, pod2b, - endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "named-port", // Matches container port name, not service target number - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{ - "service-created-ds1-12345", - "service-created-ds2-12345", - "service-created-ds2-23456", - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Only L4 TCP ports get a Consul Service port when L4 protocols are multiplexed", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public-tcp", - Port: 2345, - Protocol: "TCP", - }, - { - Name: "public-udp", - Port: 2345, - Protocol: "UDP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - // Two L4 protocols on one exposed port - { - Name: "public-tcp", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-svc-port"), - }, - { - Name: "public-udp", - Port: 8080, - Protocol: "UDP", - TargetPort: intstr.FromString("my-svc-port"), - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-svc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Services without mesh-injected pods should not be registered", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - removeMeshInjectStatus(t, pod1) - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, endpoints, service} - }, - // No expected resource - }, - { - name: "Services with mix of injected and non-injected pods registered with only injected selectors", - svcName: "service-created", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-fghij") - pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - pod4 := createServicePod(kindDaemonSet, "service-created-ds", "23456") - removeMeshInjectStatus(t, pod1) - removeMeshInjectStatus(t, pod3) - // Retain status of second pod - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2, pod3, pod4), - Ports: []corev1.EndpointPort{ - { - Name: "public", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-created", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Selector only contains values for injected pods - Prefixes: []string{"service-created-rs-fghij"}, - Names: []string{"service-created-ds-23456"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestReconcile_UpdateService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Pods changed", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-klmno") - pod3 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - pod4 := createServicePod(kindDaemonSet, "service-created-ds", "34567") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2, pod3, pod4), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, pod3, pod4, endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{ - "service-created-rs-abcde", // Retained - "service-created-rs-fghij", // Removed - }, - Names: []string{ - "service-created-ds-12345", // Retained - "service-created-ds-23456", // Removed - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-created", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - - Prefixes: []string{ - "service-created-rs-abcde", // Retained - "service-created-rs-klmno", // New - }, - Names: []string{ - "service-created-ds-12345", // Retained - "service-created-ds-34567", // New - }, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Service ports changed", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePod(kindDaemonSet, "service-created-ds", "12345") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "my-grpc-port", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("new-http-port"), - AppProtocol: &appProtocolHttp2, - }, - { - Name: "api", - Port: 9091, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "new-http-port", // Updated - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP2, // Updated - }, - { - VirtualPort: 9091, // Updated - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - // Port 10001 removed - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - { - name: "Redundant reconcile does not write to Consul unless resource was modified", - svcName: "service-updated", - k8sObjects: func() []runtime.Object { - pod1 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - pod2 := createServicePodOwnedBy(kindReplicaSet, "service-created-rs-abcde") - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(pod1, pod2), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - Port: 2345, - Protocol: "TCP", - AppProtocol: &appProtocolHttp, - }, - { - Name: "my-grpc-port", - Port: 6789, - Protocol: "TCP", - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - }, - }, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-updated", - Namespace: "default", - UID: types.UID(randomUid()), - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - { - Name: "api", - Port: 9090, - Protocol: "TCP", - TargetPort: intstr.FromString("my-grpc-port"), - AppProtocol: &appProtocolGrpc, - }, - { - Name: "other", - Port: 10001, - Protocol: "TCP", - TargetPort: intstr.FromString("10001"), - // no app protocol specified - }, - }, - }, - } - return []runtime.Object{pod1, pod2, endpoints, service} - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-updated", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - VirtualPort: 9090, - TargetPort: "my-grpc-port", - Protocol: pbcatalog.Protocol_PROTOCOL_GRPC, - }, - { - VirtualPort: 10001, - TargetPort: "10001", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - caseFn: func(t *testing.T, tc *reconcileCase, ep *Controller, resourceClient pbresource.ResourceServiceClient) { - runReconcile := func() { - r, err := ep.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcName, - Namespace: tc.targetConsulNs, - }}) - require.False(t, r.Requeue) - require.NoError(t, err) - } - - // Get resource before additional reconcile - beforeResource := getAndValidateResource(t, resourceClient, tc.expectedResource.Id) - - // Run several additional reconciles, expecting no writes to Consul - for i := 0; i < 5; i++ { - runReconcile() - require.Equal(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - - // Modify resource external to controller - modified := proto.Clone(beforeResource).(*pbresource.Resource) - modified.Metadata = map[string]string{"foo": "bar"} - modified.Version = "" - modified.Generation = "" - _, err := resourceClient.Write(context.Background(), &pbresource.WriteRequest{ - Resource: modified, - }) - require.NoError(t, err) - - // Get resource after additional reconcile, now expecting a new write to occur - runReconcile() - - require.NotEqual(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - - // Get resource before additional reconcile - beforeResource = getAndValidateResource(t, resourceClient, tc.expectedResource.Id) - - // Run several additional reconciles, expecting no writes to Consul - for i := 0; i < 5; i++ { - runReconcile() - require.Equal(t, beforeResource.GetGeneration(), - getAndValidateResource(t, resourceClient, tc.expectedResource.Id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestEnsureService(t *testing.T) { - t.Parallel() - - type args struct { - k8sUid string - meta map[string]string - consulSvc *pbcatalog.Service - } - - uuid1 := randomUid() - uuid2 := randomUid() - meta1 := getServiceMeta(corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }}) - meta2 := getServiceMeta(corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - }}) - meta2["some-other-key"] = "value" - - id := getServiceID( - "my-svc", - constants.DefaultConsulNS, - constants.DefaultConsulPartition) - - cases := []struct { - name string - beforeArgs args - afterArgs args - readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) - expectWrite bool - expectAlwaysWrite bool - expectErr string - caseFn func(t *testing.T, ep *Controller) - }{ - { - name: "Identical args writes once", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - // Identical to before - afterArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: false, - }, - { - name: "Changed service payload updates resource", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - afterArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - // Different workload selector - Prefixes: []string{"service-created-rs-fghij"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Changed service meta updates resource", - beforeArgs: args{ - k8sUid: uuid1, - meta: meta1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - afterArgs: args{ - k8sUid: uuid1, - meta: meta2, // Updated meta - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Changed k8s UID updates resource", - beforeArgs: args{ - k8sUid: uuid1, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - // Identical to before except K8s UID, indicating delete and rewrite of K8s service - afterArgs: args{ - k8sUid: uuid2, - consulSvc: &pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }, - }, - expectWrite: true, - }, - { - name: "Read not found fails open and writes update", - readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return nil, status.Error(codes.NotFound, "not found") - }, - expectWrite: true, - expectAlwaysWrite: true, - }, - { - name: "Read error fails open and writes update", - readFn: func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return nil, status.Error(codes.PermissionDenied, "not allowed") - }, - expectWrite: true, - expectAlwaysWrite: true, - }, - { - name: "Write error does not prevent future writes (cache not updated)", - writeFn: func(ctx context.Context, request *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return nil, status.Error(codes.Internal, "oops") - }, - expectErr: "rpc error: code = Internal desc = oops", - caseFn: func(t *testing.T, ep *Controller) { - require.Empty(t, ep.WriteCache.(*writeCache).data) - }, - }, - } - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - // Create the Endpoints controller. - ep := &Controller{ - Client: fake.NewClientBuilder().WithRuntimeObjects().Build(), // No k8s fetches should be needed - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - - // Set up test resourceReadWriter - rw := struct{ testReadWriter }{} - defaultRw := defaultResourceReadWriter{resourceClient} - rw.readFn = defaultRw.Read - rw.writeFn = defaultRw.Write - if tc.readFn != nil { - rw.readFn = tc.readFn - } - if tc.writeFn != nil { - rw.writeFn = tc.writeFn - } - - // Ensure caseFn runs if provided, regardless of whether error is expected - if tc.caseFn != nil { - defer tc.caseFn(t, ep) - } - - // Call first time - err := ep.ensureService(context.Background(), &rw, tc.beforeArgs.k8sUid, id, tc.beforeArgs.meta, tc.beforeArgs.consulSvc) - if tc.expectErr != "" { - require.Contains(t, err.Error(), tc.expectErr) - return - } - require.NoError(t, err) - - // Get written resource before additional calls - beforeResource := getAndValidateResource(t, resourceClient, id) - - // Call a second time - err = ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) - require.NoError(t, err) - - // Check for change on second call to ensureService - if tc.expectWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - - // Call several additional times - for i := 0; i < 5; i++ { - // Get written resource before each additional call - beforeResource = getAndValidateResource(t, resourceClient, id) - - err := ep.ensureService(context.Background(), &rw, tc.afterArgs.k8sUid, id, tc.afterArgs.meta, tc.afterArgs.consulSvc) - require.NoError(t, err) - - if tc.expectAlwaysWrite { - require.NotEqual(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), - "wanted different version for before and after resources following modification and reconcile") - } else { - require.Equal(t, beforeResource.GetGeneration(), getAndValidateResource(t, resourceClient, id).GetGeneration(), - "wanted same version for before and after resources following repeat reconcile") - } - } - }) - } -} - -type testReadWriter struct { - readFn func(context.Context, *pbresource.ReadRequest) (*pbresource.ReadResponse, error) - writeFn func(context.Context, *pbresource.WriteRequest) (*pbresource.WriteResponse, error) -} - -func (rw *testReadWriter) Read(ctx context.Context, req *pbresource.ReadRequest) (*pbresource.ReadResponse, error) { - return rw.readFn(ctx, req) -} - -func (rw *testReadWriter) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) { - return rw.writeFn(ctx, req) -} - -func TestReconcile_DeleteService(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Basic Endpoints not found (service deleted) deregisters service", - svcName: "service-deleted", - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - caseFn: func(t *testing.T, _ *reconcileCase, ep *Controller, _ pbresource.ResourceServiceClient) { - // Ensure cache was also cleared - require.Empty(t, ep.WriteCache.(*writeCache).data) - }, - }, - { - name: "Empty endpoints does not cause deregistration of existing service", - svcName: "service-deleted", - k8sObjects: func() []runtime.Object { - endpoints := &corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-deleted", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: []corev1.EndpointAddress{}, - }, - }, - } - service := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "service-deleted", - Namespace: "default", - }, - Spec: corev1.ServiceSpec{ - ClusterIP: "172.18.0.1", - Ports: []corev1.ServicePort{ - { - Name: "public", - Port: 8080, - Protocol: "TCP", - TargetPort: intstr.FromString("my-http-port"), - AppProtocol: &appProtocolHttp, - }, - }, - }, - } - return []runtime.Object{endpoints, service} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "service-deleted", - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: inject.ToProtoAny(&pbcatalog.Service{ - Ports: []*pbcatalog.ServicePort{ - { - VirtualPort: 8080, - TargetPort: "my-http-port", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"service-created-rs-abcde"}, - Names: []string{"service-created-ds-12345"}, - }, - VirtualIps: []string{"172.18.0.1"}, - }), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByEndpointsValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func TestGetWorkloadSelectorFromEndpoints(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - type testCase struct { - name string - endpoints corev1.Endpoints - responses map[types.NamespacedName]*corev1.Pod - expected *pbcatalog.WorkloadSelector - mockFn func(*testing.T, *MockPodFetcher) - } - - rsPods := []*corev1.Pod{ - createServicePod(kindReplicaSet, "svc-rs-abcde", "12345"), - createServicePod(kindReplicaSet, "svc-rs-abcde", "23456"), - createServicePod(kindReplicaSet, "svc-rs-abcde", "34567"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "12345"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "23456"), - createServicePod(kindReplicaSet, "svc-rs-fghij", "34567"), - } - otherPods := []*corev1.Pod{ - createServicePod(kindDaemonSet, "svc-ds", "12345"), - createServicePod(kindDaemonSet, "svc-ds", "23456"), - createServicePod(kindDaemonSet, "svc-ds", "34567"), - createServicePod("StatefulSet", "svc-ss", "12345"), - createServicePod("StatefulSet", "svc-ss", "23456"), - createServicePod("StatefulSet", "svc-ss", "34567"), - } - ignoredPods := []*corev1.Pod{ - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "12345"), - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "23456"), - createServicePod(kindReplicaSet, "svc-rs-ignored-klmno", "34567"), - } - - podsByName := make(map[types.NamespacedName]*corev1.Pod) - for _, p := range rsPods { - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - for _, p := range otherPods { - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - for _, p := range ignoredPods { - removeMeshInjectStatus(t, p) - podsByName[types.NamespacedName{Name: p.Name, Namespace: p.Namespace}] = p - } - - cases := []testCase{ - { - name: "Pod is fetched once per ReplicaSet", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(rsPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: getWorkloadSelector( - // Selector should consist of prefixes only. - selectorPodData{ - "svc-rs-abcde": &podSetData{}, - "svc-rs-fghij": &podSetData{}, - }, - selectorPodData{}), - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once per set. - require.Equal(t, 2, len(pf.calls)) - }, - }, - { - name: "Pod is fetched once per other pod owner type", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(otherPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: getWorkloadSelector( - // Selector should consist of exact name matches only. - selectorPodData{}, - selectorPodData{ - "svc-ds-12345": &podSetData{}, - "svc-ds-23456": &podSetData{}, - "svc-ds-34567": &podSetData{}, - "svc-ss-12345": &podSetData{}, - "svc-ss-23456": &podSetData{}, - "svc-ss-34567": &podSetData{}, - }), - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once per pod. - require.Equal(t, len(otherPods), len(pf.calls)) - }, - }, - { - name: "Pod is ignored if not mesh-injected", - endpoints: corev1.Endpoints{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "default", - }, - Subsets: []corev1.EndpointSubset{ - { - Addresses: addressesForPods(ignoredPods...), - Ports: []corev1.EndpointPort{ - { - Name: "my-http-port", - AppProtocol: &appProtocolHttp, - Port: 2345, - }, - }, - }, - }, - }, - responses: podsByName, - expected: nil, - mockFn: func(t *testing.T, pf *MockPodFetcher) { - // Assert called once for single set. - require.Equal(t, 1, len(pf.calls)) - }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - // Create mock pod fetcher. - pf := MockPodFetcher{responses: tc.responses} - - // Create the Endpoints controller. - ep := &Controller{ - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - } - - prefixedPods, exactNamePods, err := ep.getWorkloadDataFromEndpoints(ctx, &pf, tc.endpoints) - require.NoError(t, err) - - ws := getWorkloadSelector(prefixedPods, exactNamePods) - if diff := cmp.Diff(tc.expected, ws, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - tc.mockFn(t, &pf) - }) - } -} - -type MockPodFetcher struct { - calls []types.NamespacedName - responses map[types.NamespacedName]*corev1.Pod -} - -func (m *MockPodFetcher) GetPod(_ context.Context, name types.NamespacedName) (*corev1.Pod, error) { - m.calls = append(m.calls, name) - if v, ok := m.responses[name]; !ok { - panic(fmt.Errorf("test is missing response for passed pod name: %v", name)) - } else { - return v, nil - } -} - -func runReconcileCase(t *testing.T, tc reconcileCase) { - t.Helper() - - // Create fake k8s client - var k8sObjects []runtime.Object - if tc.k8sObjects != nil { - k8sObjects = tc.k8sObjects() - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the Endpoints controller. - ep := &Controller{ - Client: fakeClient, - WriteCache: NewWriteCache(logrtest.New(t)), - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - resourceClient, err := consul.NewResourceServiceClient(ep.ConsulServerConnMgr) - require.NoError(t, err) - - // Default ns and partition if not specified in test. - if tc.targetConsulNs == "" { - tc.targetConsulNs = constants.DefaultConsulNS - } - if tc.targetConsulPartition == "" { - tc.targetConsulPartition = constants.DefaultConsulPartition - } - - // If existing resource specified, create it and ensure it exists. - if tc.existingResource != nil { - writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err = resourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) - } - - // Run actual reconcile and verify results. - resp, err := ep.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcName, - Namespace: tc.targetConsulNs, - }, - }) - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedServiceMatches(t, resourceClient, tc.svcName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) - - if tc.caseFn != nil { - tc.caseFn(t, &tc, ep, resourceClient) - } -} - -func expectedServiceMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { - req := &pbresource.ReadRequest{Id: getServiceID(name, namespace, partition)} - - res, err := client.Read(context.Background(), req) - - if expectedResource == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - require.NotNil(t, res.GetResource().GetData()) - - expectedService := &pbcatalog.Service{} - err = anypb.UnmarshalTo(expectedResource.Data, expectedService, proto.UnmarshalOptions{}) - require.NoError(t, err) - - actualService := &pbcatalog.Service{} - err = res.GetResource().GetData().UnmarshalTo(actualService) - require.NoError(t, err) - - if diff := cmp.Diff(expectedService, actualService, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } -} - -func createServicePodOwnedBy(ownerKind, ownerName string, containers ...corev1.Container) *corev1.Pod { - return createServicePod(ownerKind, ownerName, randomKubernetesId(), containers...) -} - -func createServicePod(ownerKind, ownerName, podId string, containers ...corev1.Container) *corev1.Pod { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", ownerName, podId), - Namespace: "default", - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.3.0", - constants.KeyMeshInjectStatus: constants.Injected, - }, - OwnerReferences: []metav1.OwnerReference{ - { - Name: ownerName, - Kind: ownerKind, - }, - }, - }, - Spec: corev1.PodSpec{ - Containers: containers, - }, - } - return pod -} - -func containerWithPort(name string, port int32) corev1.Container { - return corev1.Container{ - Ports: []corev1.ContainerPort{ - { - Name: name, - ContainerPort: port, - Protocol: "TCP", - }, - }, - } -} - -func addressesForPods(pods ...*corev1.Pod) []corev1.EndpointAddress { - var addresses []corev1.EndpointAddress - for i, p := range pods { - addresses = append(addresses, corev1.EndpointAddress{ - IP: fmt.Sprintf("1.2.3.%d", i), - TargetRef: &corev1.ObjectReference{ - Kind: "Pod", - Name: p.Name, - Namespace: p.Namespace, - }, - }) - } - return addresses -} - -func randomKubernetesId() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u[:5] -} - -func randomUid() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u -} - -func removeMeshInjectStatus(t *testing.T, pod *corev1.Pod) { - delete(pod.Annotations, constants.KeyMeshInjectStatus) - require.False(t, inject.HasBeenMeshInjected(*pod)) -} - -func getAndValidateResource(t *testing.T, resourceClient pbresource.ResourceServiceClient, id *pbresource.ID) *pbresource.Resource { - resp, err := resourceClient.Read(metadata.NewOutgoingContext( - context.Background(), - // Read with strong consistency to avoid race conditions - metadata.New(map[string]string{"x-consul-consistency-mode": "consistent"}), - ), &pbresource.ReadRequest{ - Id: id, - }) - require.NoError(t, err) - r := resp.GetResource() - require.NotNil(t, r) - require.NotEmpty(t, r.GetGeneration()) - return r -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache.go deleted file mode 100644 index 0baf537ef7..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/write_cache.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - "bytes" - "fmt" - "github.com/go-logr/logr" - "github.com/hashicorp/go-multierror" - "sync" -) - -// consulWriteRecord is a record of writing a resource to Consul for the sake of deduplicating writes. -// -// It is bounded in size and even a low-resource pod should be able to store 10Ks of them in-memory without worrying -// about eviction. On average, assuming a SHA256 hash, the total size of each record should be approximately 150 bytes. -type consulWriteRecord struct { - // inputHash is a detrministic hash of the payload written to Consul. - // It should be derived from the "source" data rather than the returned payload in order to be unaffected by added - // fields and defaulting behavior defined by Consul. - inputHash []byte - // generation is the generation of the written resource in Consul. This ensures that we write to Consul if a - // redundant reconcile occurs, but the actual Consul resource has been modified since the last write. - generation string - // k8sUid is the UID of the corresponding resource in K8s. This allows us to check for K8s service recreation in - // between successful reconciles even though deletion of a K8s resource does not expose the UID of the deleted - // resource (the reconcile request only contains the namespaced name). - k8sUid string -} - -// WriteCache is a simple, unbounded, thread-safe in-memory cache for tracking writes of Consul resources. -// It can be used to deduplicate identical writes client-side to "debounce" writes during repeat reconciles -// that do not impact data already written to Consul. -type WriteCache interface { - hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool - update(key string, hash []byte, generation string, k8sUid string) - remove(key string) -} - -type writeCache struct { - data map[string]consulWriteRecord - dataMutex sync.RWMutex - - log logr.Logger -} - -func NewWriteCache(log logr.Logger) WriteCache { - return &writeCache{ - data: make(map[string]consulWriteRecord), - log: log.WithName("writeCache"), - } -} - -// update upserts a record containing the given hash and generation to the cache at the given key. -func (c *writeCache) update(key string, hash []byte, generation string, k8sUid string) { - c.dataMutex.Lock() - defer c.dataMutex.Unlock() - - var err error - if key == "" { - err = multierror.Append(err, fmt.Errorf("key was empty")) - } - if len(hash) == 0 { - err = multierror.Append(err, fmt.Errorf("hash was empty")) - } - if generation == "" { - err = multierror.Append(err, fmt.Errorf("generation was empty")) - } - if k8sUid == "" { - err = multierror.Append(err, fmt.Errorf("k8sUid was empty")) - } - if err != nil { - c.log.Error(err, "writeCache could not be updated due to empty value(s) - redundant writes may be repeated") - return - } - - c.data[key] = consulWriteRecord{ - inputHash: hash, - generation: generation, - k8sUid: k8sUid, - } -} - -// remove removes a record from the cache at the given key. -func (c *writeCache) remove(key string) { - c.dataMutex.Lock() - defer c.dataMutex.Unlock() - - delete(c.data, key) -} - -// hasMatch returns true iff. there is an existing write record for the given key in the cache, and that record matches -// the provided non-empty hash, generation, and Kubernetes UID. -// -// The generation is fetched rather than provided directly s.t. a call to Consul can be skipped if a record is not found -// or other available fields do not match. -// -// While not strictly necessary assuming the controller is the sole writer of the resource, the generation check ensures -// that the resource is kept in sync even if externally modified. -// -// When checking for a match, ensures the UID of the K8s service also matches s.t. we don't skip updates on recreation -// of a K8s service, as the intent of the user may have been to force a sync, and a future solution that stores write -// fingerprints in K8s annotations would also have this behavior. -func (c *writeCache) hasMatch(key string, hash []byte, generationFetchFn func() string, k8sUid string) bool { - var lastHash []byte - lastGeneration := "" - lastK8sUid := "" - if s, ok := c.get(key); ok { - lastHash = s.inputHash - lastGeneration = s.generation - lastK8sUid = s.k8sUid - } - - if len(lastHash) == 0 || lastGeneration == "" || lastK8sUid == "" { - return false - } - - return bytes.Equal(lastHash, hash) && - lastK8sUid == k8sUid && - lastGeneration == generationFetchFn() // Fetch generation only if other fields match -} - -func (c *writeCache) get(key string) (consulWriteRecord, bool) { - c.dataMutex.RLock() - defer c.dataMutex.RUnlock() - - v, ok := c.data[key] - return v, ok -} diff --git a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go b/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go deleted file mode 100644 index 2b22c5707a..0000000000 --- a/control-plane/connect-inject/controllers/endpointsv2/write_cache_test.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package endpointsv2 - -import ( - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/go-uuid" - "testing" -) - -func Test_writeCache(t *testing.T) { - testHash := randomBytes() - testGeneration := randomString() - testK8sUid := randomString() - - type args struct { - key string - hash []byte - generationFetchFn func() string - k8sUid string - } - cases := []struct { - name string - args args - setupFn func(args args, cache WriteCache) - want bool - }{ - { - name: "No data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - want: false, - }, - { - name: "Non-matching key returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update("another-key", args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching hash returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching generation returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, randomString(), args.k8sUid) - }, - want: false, - }, - { - name: "Non-matching k8sUid returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), randomString()) - }, - want: false, - }, - { - name: "Matching data returns true", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: true, - }, - { - name: "Removed data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update("another-key", randomBytes(), randomString(), randomString()) - cache.remove(args.key) - }, - want: false, - }, - { - name: "Replaced data returns false", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, randomBytes(), args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - { - name: "Invalid hash does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, []byte{}, args.generationFetchFn(), args.k8sUid) - }, - want: true, - }, - { - name: "Invalid generation does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, args.hash, "", args.k8sUid) - }, - want: true, - }, - { - name: "Invalid k8sUid does not update cache", - args: args{ - "foo", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update(args.key, args.hash, args.generationFetchFn(), args.k8sUid) - cache.update(args.key, args.hash, args.generationFetchFn(), "") - }, - want: true, - }, - { - name: "Invalid key is ignored", - args: args{ - "", - testHash, - func() string { - return testGeneration - }, - testK8sUid, - }, - setupFn: func(args args, cache WriteCache) { - cache.update("", args.hash, args.generationFetchFn(), args.k8sUid) - }, - want: false, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - c := NewWriteCache(logrtest.New(t)) - if tc.setupFn != nil { - tc.setupFn(tc.args, c) - } - if got := c.hasMatch(tc.args.key, tc.args.hash, tc.args.generationFetchFn, tc.args.k8sUid); got != tc.want { - t.Errorf("hasMatch() = %v, want %v", got, tc.want) - } - }) - } -} - -func randomBytes() []byte { - b, err := uuid.GenerateRandomBytes(32) - if err != nil { - panic(err) - } - return b -} - -func randomString() string { - u, err := uuid.GenerateUUID() - if err != nil { - panic(err) - } - return u -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller.go b/control-plane/connect-inject/controllers/pod/pod_controller.go deleted file mode 100644 index 3864f0e213..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller.go +++ /dev/null @@ -1,744 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package pod - -import ( - "context" - "encoding/json" - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/go-logr/logr" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/go-multierror" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/proto" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - DefaultTelemetryBindSocketDir = "/consul/mesh-inject" - consulNodeAddress = "127.0.0.1" - tokenMetaPodNameKey = "pod" -) - -// Controller watches Pod events and converts them to V2 Workloads and HealthStatus. -// The translation from Pod to Workload is 1:1 and the HealthStatus object is a representation -// of the Pod's Status field. Controller is also responsible for generating V2 Upstreams resources -// when not in transparent proxy mode. ProxyConfiguration is also optionally created. -type Controller struct { - client.Client - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - // TODO: EnableWANFederation - - // EnableTransparentProxy controls whether transparent proxy should be enabled - // for all proxy service registrations. - EnableTransparentProxy bool - // TProxyOverwriteProbes controls whether the pods controller should expose pod's HTTP probes - // via Envoy proxy. - TProxyOverwriteProbes bool - - // AuthMethod is the name of the Kubernetes Auth Method that - // was used to login with Consul. The pods controller - // will delete any tokens associated with this auth method - // whenever service instances are deregistered. - AuthMethod string - - // EnableTelemetryCollector controls whether the proxy service should be registered - // with config to enable telemetry forwarding. - EnableTelemetryCollector bool - - MetricsConfig metrics.Config - Log logr.Logger - - // ResourceClient is a gRPC client for the resource service. It is public for testing purposes - ResourceClient pbresource.ResourceServiceClient -} - -// TODO: logs, logs, logs - -// Reconcile reads the state of a Kubernetes Pod and reconciles Consul workloads that are 1:1 mapped. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var errs error - var pod corev1.Pod - - // Ignore the request if the namespace of the pod is not allowed. - // Strictly speaking, this is not required because the mesh webhook also knows valid namespaces - // for injection, but it will somewhat reduce the amount of unnecessary deletions for non-injected - // pods - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - rc, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.ResourceClient = rc - - apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul API client", "name", req.Name) - return ctrl.Result{}, err - } - - if r.ConsulClientConfig.APIClientConfig.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", r.ConsulClientConfig.APIClientConfig.Token) - } - - err = r.Client.Get(ctx, req.NamespacedName, &pod) - - // If the pod object has been deleted (and we get an IsNotFound error), - // we need to remove the Workload from Consul. - if k8serrors.IsNotFound(err) { - - // Consul should also clean up the orphaned HealthStatus - if err := r.deleteWorkload(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - // Delete destinations, if any exist - if err := r.deleteDestinations(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - if err := r.deleteProxyConfiguration(ctx, req.NamespacedName); err != nil { - errs = multierror.Append(errs, err) - } - - if r.AuthMethod != "" { - r.Log.Info("deleting ACL tokens for pod", "name", req.Name, "ns", req.Namespace) - err := r.deleteACLTokensForPod(apiClient, req.NamespacedName) - if err != nil { - r.Log.Error(err, "failed to delete ACL tokens for pod", "name", req.Name, "ns", req.Namespace) - errs = multierror.Append(errs, err) - } - } - - return ctrl.Result{}, errs - } else if err != nil { - r.Log.Error(err, "failed to get Pod", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - r.Log.Info("retrieved", "name", pod.Name, "ns", pod.Namespace) - - if inject.HasBeenMeshInjected(pod) { - if err := r.writeProxyConfiguration(ctx, pod); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - if err := r.writeWorkload(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - // Create explicit destinations (if any exist) - if err := r.writeDestinations(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - - if err := r.writeHealthStatus(ctx, pod); err != nil { - // Technically this is not needed, but keeping in case this gets refactored in - // a different order - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "pod", req.Name, "ns", req.Namespace, "consul-ns", - r.getConsulNamespace(req.Namespace), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - errs = multierror.Append(errs, err) - } - } - - return ctrl.Result{}, errs -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Pod{}). - Complete(r) -} - -func (r *Controller) deleteWorkload(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getWorkloadID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -func (r *Controller) deleteProxyConfiguration(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getProxyConfigurationID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -// deleteACLTokensForPod finds the ACL tokens that belongs to the pod and delete them from Consul. -// It will only check for ACL tokens that have been created with the auth method this controller -// has been configured with and will only delete tokens for the provided pod Name. -func (r *Controller) deleteACLTokensForPod(apiClient *api.Client, pod types.NamespacedName) error { - // Skip if name is empty. - if pod.Name == "" { - return nil - } - - // Use the V1 logic for getting a compatible namespace - consulNamespace := namespaces.ConsulNamespace( - pod.Namespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, r.EnableNSMirroring, r.NSMirroringPrefix, - ) - - // TODO: create an index for the workloadidentity in Consul, which will also require - // the identity to be attached to the token for templated-policies. - tokens, _, err := apiClient.ACL().TokenListFiltered( - api.ACLTokenFilterOptions{ - AuthMethod: r.AuthMethod, - }, - &api.QueryOptions{ - Namespace: consulNamespace, - }) - if err != nil { - return fmt.Errorf("failed to get a list of tokens from Consul: %s", err) - } - - // We iterate through each token in the auth method, which is terribly inefficient. - // See discussion above about optimizing the token list query. - for _, token := range tokens { - tokenMeta, err := getTokenMetaFromDescription(token.Description) - if err != nil { - return fmt.Errorf("failed to parse token metadata: %s", err) - } - - tokenPodName := strings.TrimPrefix(tokenMeta[tokenMetaPodNameKey], pod.Namespace+"/") - - // If we can't find token's pod, delete it. - if tokenPodName == pod.Name { - r.Log.Info("deleting ACL token", "name", pod.Name, "namespace", pod.Namespace, "ID", token.AccessorID) - if _, err := apiClient.ACL().TokenDelete(token.AccessorID, &api.WriteOptions{Namespace: consulNamespace}); err != nil { - return fmt.Errorf("failed to delete token from Consul: %s", err) - } - } - } - return nil -} - -// getTokenMetaFromDescription parses JSON metadata from token's description. -func getTokenMetaFromDescription(description string) (map[string]string, error) { - re := regexp.MustCompile(`.*({.+})`) - - matches := re.FindStringSubmatch(description) - if len(matches) != 2 { - return nil, fmt.Errorf("failed to extract token metadata from description: %s", description) - } - tokenMetaJSON := matches[1] - - var tokenMeta map[string]string - err := json.Unmarshal([]byte(tokenMetaJSON), &tokenMeta) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal token metadata '%s': %s", tokenMetaJSON, err) - } - - return tokenMeta, nil -} - -func (r *Controller) writeWorkload(ctx context.Context, pod corev1.Pod) error { - - // TODO: we should add some validation on the required fields here - // e.g. what if token automount is disabled and there is not SA. The API call - // will fail with no indication to the user other than controller logs - ports, workloadPorts := getWorkloadPorts(pod) - - var node corev1.Node - // Ignore errors because we don't want failures to block running services. - _ = r.Client.Get(context.Background(), types.NamespacedName{Name: pod.Spec.NodeName, Namespace: pod.Namespace}, &node) - locality := parseLocality(node) - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: pod.Status.PodIP, Ports: ports}, - }, - Identity: pod.Spec.ServiceAccountName, - Locality: locality, - // Adding a node does not currently work because the node doesn't exist so its health status will always be - // unhealthy, causing any endpoints on that node to also be unhealthy. - // TODO: (v2/nitya) Bring this back when node controller is built. - //NodeName: inject.ConsulNodeNameFromK8sNode(pod.Spec.NodeName), - Ports: workloadPorts, - } - data := inject.ToProtoAny(workload) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err := r.ResourceClient.Write(ctx, req) - return err -} - -func (r *Controller) writeProxyConfiguration(ctx context.Context, pod corev1.Pod) error { - mode, err := r.getTproxyMode(ctx, pod) - if err != nil { - return fmt.Errorf("failed to get transparent proxy mode: %w", err) - } - - exposeConfig, err := r.getExposeConfig(pod) - if err != nil { - return fmt.Errorf("failed to get expose config: %w", err) - } - - bootstrapConfig, err := r.getBootstrapConfig(pod) - if err != nil { - return fmt.Errorf("failed to get bootstrap config: %w", err) - } - - if exposeConfig == nil && - bootstrapConfig == nil && - mode == pbmesh.ProxyMode_PROXY_MODE_DEFAULT { - // It's possible to remove interesting annotations and need to clear any existing config, - // but for now we treat pods as immutable configs owned by other managers. - return nil - } - - pc := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{pod.GetName()}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: mode, - ExposeConfig: exposeConfig, - }, - BootstrapConfig: bootstrapConfig, - } - data := inject.ToProtoAny(pc) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getProxyConfigurationID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err = r.ResourceClient.Write(ctx, req) - return err -} - -func (r *Controller) getTproxyMode(ctx context.Context, pod corev1.Pod) (pbmesh.ProxyMode, error) { - // A user can enable/disable tproxy for an entire namespace. - var ns corev1.Namespace - err := r.Client.Get(ctx, types.NamespacedName{Name: pod.GetNamespace()}, &ns) - if err != nil { - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not get namespace info for %s: %w", pod.GetNamespace(), err) - } - - tproxyEnabled, err := inject.TransparentProxyEnabled(ns, pod, r.EnableTransparentProxy) - if err != nil { - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, fmt.Errorf("could not determine if transparent proxy is enabled: %w", err) - } - - if tproxyEnabled { - return pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, nil - } - return pbmesh.ProxyMode_PROXY_MODE_DEFAULT, nil -} - -func (r *Controller) getExposeConfig(pod corev1.Pod) (*pbmesh.ExposeConfig, error) { - // Expose k8s probes as Envoy listeners if needed. - overwriteProbes, err := inject.ShouldOverwriteProbes(pod, r.TProxyOverwriteProbes) - if err != nil { - return nil, fmt.Errorf("could not determine if probes should be overwritten: %w", err) - } - - if !overwriteProbes { - return nil, nil - } - - var originalPod corev1.Pod - err = json.Unmarshal([]byte(pod.Annotations[constants.AnnotationOriginalPod]), &originalPod) - if err != nil { - return nil, fmt.Errorf("failed to get original pod spec: %w", err) - } - - exposeConfig := &pbmesh.ExposeConfig{} - for _, mutatedContainer := range pod.Spec.Containers { - for _, originalContainer := range originalPod.Spec.Containers { - if originalContainer.Name == mutatedContainer.Name { - paths, err := getContainerExposePaths(originalPod, originalContainer, mutatedContainer) - if err != nil { - return nil, fmt.Errorf("error getting container expose path for %s: %w", originalContainer.Name, err) - } - - exposeConfig.ExposePaths = append(exposeConfig.ExposePaths, paths...) - } - } - } - - if len(exposeConfig.ExposePaths) == 0 { - return nil, nil - } - return exposeConfig, nil -} - -func getContainerExposePaths(originalPod corev1.Pod, originalContainer, mutatedContainer corev1.Container) ([]*pbmesh.ExposePath, error) { - var paths []*pbmesh.ExposePath - if mutatedContainer.LivenessProbe != nil && mutatedContainer.LivenessProbe.HTTPGet != nil { - originalLivenessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.LivenessProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.LivenessProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalLivenessPort, - Path: mutatedContainer.LivenessProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - if mutatedContainer.ReadinessProbe != nil && mutatedContainer.ReadinessProbe.HTTPGet != nil { - originalReadinessPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.ReadinessProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.ReadinessProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalReadinessPort, - Path: mutatedContainer.ReadinessProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - if mutatedContainer.StartupProbe != nil && mutatedContainer.StartupProbe.HTTPGet != nil { - originalStartupPort, err := inject.PortValueFromIntOrString(originalPod, originalContainer.StartupProbe.HTTPGet.Port) - if err != nil { - return nil, err - } - - newPath := &pbmesh.ExposePath{ - ListenerPort: uint32(mutatedContainer.StartupProbe.HTTPGet.Port.IntValue()), - LocalPathPort: originalStartupPort, - Path: mutatedContainer.StartupProbe.HTTPGet.Path, - } - paths = append(paths, newPath) - } - return paths, nil -} - -func (r *Controller) getBootstrapConfig(pod corev1.Pod) (*pbmesh.BootstrapConfig, error) { - bootstrap := &pbmesh.BootstrapConfig{} - - // If metrics are enabled, the BootstrapConfig should set envoy_prometheus_bind_addr to a listener on 0.0.0.0 on - // the PrometheusScrapePort that points to a metrics backend. The backend for this listener will be determined by - // the envoy bootstrapping command (consul connect envoy) or the consul-dataplane GetBoostrapParams rpc. - // If there is a merged metrics server, the backend would be that server. - // If we are not running the merged metrics server, the backend should just be the Envoy metrics endpoint. - enableMetrics, err := r.MetricsConfig.EnableMetrics(pod) - if err != nil { - return nil, fmt.Errorf("error determining if metrics are enabled: %w", err) - } - if enableMetrics { - prometheusScrapePort, err := r.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return nil, err - } - prometheusScrapeListener := fmt.Sprintf("0.0.0.0:%s", prometheusScrapePort) - bootstrap.PrometheusBindAddr = prometheusScrapeListener - } - - if r.EnableTelemetryCollector { - bootstrap.TelemetryCollectorBindSocketDir = DefaultTelemetryBindSocketDir - } - - if proto.Equal(bootstrap, &pbmesh.BootstrapConfig{}) { - return nil, nil - } - return bootstrap, nil -} - -func (r *Controller) writeHealthStatus(ctx context.Context, pod corev1.Pod) error { - status := getHealthStatusFromPod(pod) - - hs := &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: status, - Description: constants.ConsulKubernetesCheckName, - Output: getHealthStatusReason(status, pod), - } - data := inject.ToProtoAny(hs) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getHealthStatusID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Owner: getWorkloadID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err := r.ResourceClient.Write(ctx, req) - return err -} - -// TODO: delete ACL token for workload -// deleteACLTokensForServiceInstance finds the ACL tokens that belongs to the service instance and deletes it from Consul. -// It will only check for ACL tokens that have been created with the auth method this controller -// has been configured with and will only delete tokens for the provided podName. -// func (r *Controller) deleteACLTokensForWorkload(apiClient *api.Client, svc *api.AgentService, k8sNS, podName string) error { - -// writeDestinations will write explicit destinations if pod annotations exist. -func (r *Controller) writeDestinations(ctx context.Context, pod corev1.Pod) error { - uss, err := inject.ProcessPodDestinations(pod, r.EnableConsulPartitions, r.EnableConsulNamespaces) - if err != nil { - return fmt.Errorf("error processing destination annotations: %s", err.Error()) - } - if uss == nil { - return nil - } - - data := inject.ToProtoAny(uss) - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: getDestinationsID(pod.GetName(), r.getConsulNamespace(pod.Namespace), r.getPartition()), - Metadata: metaFromPod(pod), - Data: data, - }, - } - _, err = r.ResourceClient.Write(ctx, req) - - return err -} - -func (r *Controller) deleteDestinations(ctx context.Context, pod types.NamespacedName) error { - req := &pbresource.DeleteRequest{ - Id: getDestinationsID(pod.Name, r.getConsulNamespace(pod.Namespace), r.getPartition()), - } - - _, err := r.ResourceClient.Delete(ctx, req) - return err -} - -// consulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources change. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func getWorkloadPorts(pod corev1.Pod) ([]string, map[string]*pbcatalog.WorkloadPort) { - ports := make([]string, 0) - workloadPorts := map[string]*pbcatalog.WorkloadPort{} - - for _, container := range pod.Spec.Containers { - for _, port := range container.Ports { - name := port.Name - if name == "" { - name = strconv.Itoa(int(port.ContainerPort)) - } - - // TODO: error check reserved "mesh" keyword and 20000 - - if port.Protocol != corev1.ProtocolTCP { - // TODO: also throw an error here - continue - } - - ports = append(ports, name) - workloadPorts[name] = &pbcatalog.WorkloadPort{ - Port: uint32(port.ContainerPort), - - // We leave the protocol unspecified so that it can be inherited from the Service appProtocol - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - } - } - } - - ports = append(ports, "mesh") - workloadPorts["mesh"] = &pbcatalog.WorkloadPort{ - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - } - - return ports, workloadPorts -} - -func parseLocality(node corev1.Node) *pbcatalog.Locality { - region := node.Labels[corev1.LabelTopologyRegion] - zone := node.Labels[corev1.LabelTopologyZone] - - if region == "" { - return nil - } - - return &pbcatalog.Locality{ - Region: region, - Zone: zone, - } -} - -func metaFromPod(pod corev1.Pod) map[string]string { - // TODO: allow custom workload metadata - return map[string]string{ - constants.MetaKeyKubeNS: pod.GetNamespace(), - constants.MetaKeyManagedBy: constants.ManagedByPodValue, - } -} - -// getHealthStatusFromPod checks the Pod for a "Ready" condition that is true. -// This is true when all the containers are ready, vs. "Running" on the PodPhase, -// which is true if any container is running. -func getHealthStatusFromPod(pod corev1.Pod) pbcatalog.Health { - if pod.Status.Conditions == nil { - return pbcatalog.Health_HEALTH_CRITICAL - } - - for _, condition := range pod.Status.Conditions { - if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue { - return pbcatalog.Health_HEALTH_PASSING - } - } - - return pbcatalog.Health_HEALTH_CRITICAL -} - -// getHealthStatusReason takes Consul's health check status (either passing or critical) -// and the pod to return a descriptive output for the HealthStatus Output. -func getHealthStatusReason(state pbcatalog.Health, pod corev1.Pod) string { - if state == pbcatalog.Health_HEALTH_PASSING { - return constants.KubernetesSuccessReasonMsg - } - - return fmt.Sprintf("Pod \"%s/%s\" is not ready", pod.GetNamespace(), pod.GetName()) -} - -func getWorkloadID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getHealthStatusID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.HealthStatusType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -func getDestinationsID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.DestinationsType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go deleted file mode 100644 index 1c074dc940..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller_ent_test.go +++ /dev/null @@ -1,775 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package pod - -import ( - "context" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - capi "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - testPodName = "foo" - testPartition = "my-partition" -) - -type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - podNamespace string // Defaults to metav1.NamespaceDefault if empty. - partition string - - k8sObjects func() []runtime.Object // testing node is injected separately - - // Pod Controller Settings - acls bool - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - namespaceMirroring bool - namespaceDestination string - namespacePrefix string - - // Initial Consul state. - existingConsulNamespace string // This namespace will be populated before the test is executed. - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - // Expected Consul state. - expectedConsulNamespace string // This namespace will be used to query Consul for the results - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - // Reconcile loop outputs - expErr string - expRequeue bool // The response from the reconcile function -} - -// TestReconcileCreatePodWithMirrorNamespaces creates a Pod object in a non-default NS and Partition -// with namespaces set to mirroring -func TestReconcileCreatePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "kitchen sink new pod, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "kitchen sink new pod, non-default ns and partition", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "new pod with namespace prefix", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - - expectedConsulNamespace: "foo-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "namespace mirroring overrides destination namespace", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespaceDestination: "supernova", - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "new pod with explicit destinations, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - tproxy: false, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - { - name: "namespace in Consul does not exist", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - - // The equivalent namespace in Consul does not exist, so requeue for backoff. - expRequeue: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileUpdatePodWithMirrorNamespaces updates a Pod object in a non-default NS and Partition -// with namespaces set to mirroring. -func TestReconcileUpdatePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "update pod health", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, false) // failing - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "foo-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), - }, - { - name: "duplicated pod event", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceMirroring: true, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileDeletePodWithMirrorNamespaces deletes a Pod object in a non-default NS and Partition -// with namespaces set to mirroring. -func TestReconcileDeletePodWithMirrorNamespaces(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "delete kitchen sink pod", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "bar", - }, - { - name: "delete pod w/ explicit destinations", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - - expectedConsulNamespace: "bar", - }, - { - name: "delete pod with namespace prefix", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceMirroring: true, - namespacePrefix: "foo-", - - existingConsulNamespace: "foo-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "foo-bar", - }, - { - name: "resources are already gone in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceMirroring: true, - - existingConsulNamespace: "bar", - - expectedConsulNamespace: "bar", - }, - { - name: "namespace is already missing in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceMirroring: true, - - expectedConsulNamespace: "bar", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileCreatePodWithDestinationNamespace creates a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileCreatePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "kitchen sink new pod, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: constants.DefaultConsulNS, - - existingConsulNamespace: constants.DefaultConsulNS, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "new pod with explicit destinations, ns and partition", - podName: testPodName, - partition: constants.DefaultConsulPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, metav1.NamespaceDefault, true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: constants.DefaultConsulNS, - - existingConsulNamespace: constants.DefaultConsulNS, - - expectedConsulNamespace: constants.DefaultConsulNS, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - { - name: "kitchen sink new pod, non-default ns and partition", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "namespace in Consul does not exist", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - // The equivalent namespace in Consul does not exist, so requeue for backoff. - expRequeue: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileUpdatePodWithDestinationNamespace updates a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileUpdatePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "update pod health", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, false) // failing - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus(testPodName, "bar"), - }, - { - name: "duplicated pod event", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - k8sObjects: func() []runtime.Object { - pod := createPod(testPodName, "bar", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -// TestReconcileDeletePodWithDestinationNamespace deletes a Pod object in a non-default NS and Partition -// with namespaces set to a destination. -func TestReconcileDeletePodWithDestinationNamespace(t *testing.T) { - t.Parallel() - - testCases := []testCase{ - { - name: "delete kitchen sink pod", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "delete pod with explicit destinations", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration(testPodName, pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "resources are already gone in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - existingConsulNamespace: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - { - name: "namespace is already missing in Consul", - podName: testPodName, - podNamespace: "bar", - partition: testPartition, - - namespaceDestination: "a-penguin-walks-into-a-bar", - - expectedConsulNamespace: "a-penguin-walks-into-a-bar", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runControllerTest(t, tc) - }) - } -} - -func runControllerTest(t *testing.T, tc testCase) { - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - nsBar := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - k8sObjects := []runtime.Object{ - &ns, - &nsBar, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - adminToken := "123e4567-e89b-12d3-a456-426614174000" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - if tc.acls { - c.ACL.Enabled = tc.acls - c.ACL.Tokens.InitialManagement = adminToken - } - }) - - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the partition in Consul. - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - // Create the namespace in Consul if specified. - if tc.existingConsulNamespace != "" { - namespace := &capi.Namespace{ - Name: tc.existingConsulNamespace, - Partition: tc.partition, - } - - _, _, err := testClient.APIClient.Namespaces().Create(namespace, nil) - require.NoError(t, err) - } - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - EnableConsulNamespaces: true, - NSMirroringPrefix: tc.namespacePrefix, - EnableNSMirroring: tc.namespaceMirroring, - ConsulDestinationNamespace: tc.namespaceDestination, - EnableConsulPartitions: true, - ConsulPartition: tc.partition, - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - if tc.acls { - pc.AuthMethod = test.AuthMethod - } - - podNamespace := tc.podNamespace - if podNamespace == "" { - podNamespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, tc.expectedConsulNamespace, tc.partition), tc.existingDestinations, nil) - - namespacedName := types.NamespacedName{ - Namespace: podNamespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, tc.expRequeue, resp.Requeue) - - wID := getWorkloadID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, tc.expectedConsulNamespace, tc.partition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) -} diff --git a/control-plane/connect-inject/controllers/pod/pod_controller_test.go b/control-plane/connect-inject/controllers/pod/pod_controller_test.go deleted file mode 100644 index b6fb1d4d44..0000000000 --- a/control-plane/connect-inject/controllers/pod/pod_controller_test.go +++ /dev/null @@ -1,2194 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package pod - -import ( - "context" - "encoding/json" - "fmt" - "testing" - "time" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul/api" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - // TODO: (v2/nitya) Bring back consulLocalityNodeName once node controller is implemented and assertions for - // workloads need node names again. - nodeName = "test-node" - localityNodeName = "test-node-w-locality" - consulNodeName = "test-node-virtual" -) - -func TestParseLocality(t *testing.T) { - t.Run("no labels", func(t *testing.T) { - n := corev1.Node{} - require.Nil(t, parseLocality(n)) - }) - - t.Run("zone only", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.Nil(t, parseLocality(n)) - }) - - t.Run("everything", func(t *testing.T) { - n := corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-west-1", - corev1.LabelTopologyZone: "us-west-1a", - }, - }, - } - require.True(t, proto.Equal(&pbcatalog.Locality{Region: "us-west-1", Zone: "us-west-1a"}, parseLocality(n))) - }) -} - -func TestWorkloadWrite(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - localityNode := corev1.Node{ObjectMeta: metav1.ObjectMeta{ - Name: localityNodeName, - Namespace: metav1.NamespaceDefault, - Labels: map[string]string{ - corev1.LabelTopologyRegion: "us-east1", - corev1.LabelTopologyZone: "us-east1-b", - }, - }} - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedWorkload *pbcatalog.Workload - } - - run := func(t *testing.T, tc testCase) { - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - k8sObjects := []runtime.Object{ - &ns, - &node, - &localityNode, - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - } - - err = pc.writeWorkload(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := resourceClient.Read(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualWorkload := &pbcatalog.Workload{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualWorkload) - require.NoError(t, err) - - require.True(t, proto.Equal(actualWorkload, tc.expectedWorkload)) - } - - testCases := []testCase{ - { - name: "multi-port single-container", - pod: createPod("foo", "", true, true), - expectedWorkload: createWorkload(), - }, - { - name: "multi-port multi-container", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - container := corev1.Container{ - Name: "logger", - Ports: []corev1.ContainerPort{ - { - Name: "agent", - Protocol: corev1.ProtocolTCP, - ContainerPort: 6666, - }, - }, - } - pod.Spec.Containers = append(pod.Spec.Containers, container) - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "agent", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "agent": { - Port: 6666, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - { - name: "pod with locality", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.NodeName = localityNodeName - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Locality: &pbcatalog.Locality{ - Region: "us-east1", - Zone: "us-east1-b", - }, - Identity: "foo", - }, - }, - { - name: "pod with unnamed ports", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.Containers[0].Ports[0].Name = "" - pod.Spec.Containers[0].Ports[1].Name = "" - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"80", "8080", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "80": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "8080": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - { - name: "pod with no ports", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Spec.Containers[0].Ports = nil - }, - expectedWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestWorkloadDelete(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - existingWorkload *pbcatalog.Workload - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - } - - workload, err := anypb.New(tc.existingWorkload) - require.NoError(t, err) - - workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: workloadID, - Data: workload, - }, - } - - _, err = resourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, workloadID) - - reconcileReq := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: tc.pod.GetName(), - } - err = pc.deleteWorkload(context.Background(), reconcileReq) - require.NoError(t, err) - - readReq := &pbresource.ReadRequest{ - Id: getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - _, err = resourceClient.Read(context.Background(), readReq) - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - } - - testCases := []testCase{ - { - name: "basic pod delete", - pod: createPod("foo", "", true, true), - existingWorkload: createWorkload(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestHealthStatusWrite(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedHealthStatus *pbcatalog.HealthStatus - } - - run := func(t *testing.T, tc testCase) { - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - } - - // The owner of a resource is validated, so create a dummy workload for the HealthStatus - workloadData, err := anypb.New(createWorkload()) - require.NoError(t, err) - - workloadID := getWorkloadID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: workloadID, - Data: workloadData, - }, - } - _, err = resourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - - // Test writing the pod to a HealthStatus - err = pc.writeHealthStatus(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getHealthStatusID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := resourceClient.Read(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualHealthStatus := &pbcatalog.HealthStatus{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualHealthStatus) - require.NoError(t, err) - - require.True(t, proto.Equal(actualHealthStatus, tc.expectedHealthStatus)) - } - - testCases := []testCase{ - { - name: "ready pod", - pod: createPod("foo", "", true, true), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "not ready pod", - pod: createPod("foo", "", true, false), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "pod with no condition", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Status.Conditions = []corev1.PodCondition{} - }, - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestProxyConfigurationWrite(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - podModifier func(pod *corev1.Pod) - expectedProxyConfiguration *pbmesh.ProxyConfiguration - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - } - - run := func(t *testing.T, tc testCase) { - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - - nsTproxy := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: "tproxy-party", - Labels: map[string]string{ - constants.KeyTransparentProxy: "true", - }, - }} - - if tc.podModifier != nil { - tc.podModifier(tc.pod) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(&ns, &nsTproxy).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - EnableTransparentProxy: tc.tproxy, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTelemetryCollector: tc.telemetry, - ResourceClient: resourceClient, - } - - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "5678", - } - } - - // Test writing the pod to a HealthStatus - err = pc.writeProxyConfiguration(context.Background(), *tc.pod) - require.NoError(t, err) - - req := &pbresource.ReadRequest{ - Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - actualRes, err := resourceClient.Read(context.Background(), req) - - if tc.expectedProxyConfiguration == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, actualRes) - - requireEqualID(t, actualRes, tc.pod.GetName(), constants.DefaultConsulNS, constants.DefaultConsulPartition) - require.NotNil(t, actualRes.GetResource().GetData()) - - actualProxyConfiguration := &pbmesh.ProxyConfiguration{} - err = actualRes.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) - require.NoError(t, err) - - diff := cmp.Diff(actualProxyConfiguration, tc.expectedProxyConfiguration, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff) - } - - testCases := []testCase{ - { - name: "no tproxy, no telemetry, no metrics, no probe overwrite", - pod: createPod("foo", "", true, true), - expectedProxyConfiguration: nil, - }, - { - name: "kitchen sink - globally enabled", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - addProbesAndOriginalPodAnnotation(pod) - }, - tproxy: true, - overwriteProbes: true, - metrics: true, - telemetry: true, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:5678", - TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, - }, - }, - }, - { - name: "tproxy, metrics, and probe overwrite enabled on pod", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Annotations[constants.KeyTransparentProxy] = "true" - pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" - pod.Annotations[constants.AnnotationEnableMetrics] = "true" - pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" - - addProbesAndOriginalPodAnnotation(pod) - }, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:21234", - }, - }, - }, - { - name: "tproxy enabled on namespace", - pod: createPod("foo", "", true, true), - podModifier: func(pod *corev1.Pod) { - pod.Namespace = "tproxy-party" - }, - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func requireEqualID(t *testing.T, res *pbresource.ReadResponse, name string, ns string, partition string) { - require.Equal(t, name, res.GetResource().GetId().GetName()) - require.Equal(t, ns, res.GetResource().GetId().GetTenancy().GetNamespace()) - require.Equal(t, partition, res.GetResource().GetId().GetTenancy().GetPartition()) -} - -func TestProxyConfigurationDelete(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - pod *corev1.Pod - existingProxyConfiguration *pbmesh.ProxyConfiguration - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - } - - // Create the existing ProxyConfiguration - pcData, err := anypb.New(tc.existingProxyConfiguration) - require.NoError(t, err) - - pcID := getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition) - writeReq := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: pcID, - Data: pcData, - }, - } - - _, err = resourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, pcID) - - reconcileReq := types.NamespacedName{ - Namespace: metav1.NamespaceDefault, - Name: tc.pod.GetName(), - } - err = pc.deleteProxyConfiguration(context.Background(), reconcileReq) - require.NoError(t, err) - - readReq := &pbresource.ReadRequest{ - Id: getProxyConfigurationID(tc.pod.GetName(), metav1.NamespaceDefault, constants.DefaultConsulPartition), - } - _, err = resourceClient.Read(context.Background(), readReq) - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - } - - testCases := []testCase{ - { - name: "proxy configuration delete", - pod: createPod("foo", "", true, true), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// TestDestinationsWrite does a subsampling of tests covered in TestProcessUpstreams to make sure things are hooked up -// correctly. For the sake of test speed, more exhaustive testing is performed in TestProcessUpstreams. -func TestDestinationsWrite(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - expected *pbmesh.Destinations - expErr string - consulNamespacesEnabled bool - consulPartitionsEnabled bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.peer1.peer:1234" - return pod1 - }, - expErr: "error processing destination annotations: destination currently does not support peers: destination.port.upstream1.svc.ns1.ns.peer1.peer:1234", - // TODO: uncomment this and remove expErr when peers is supported - //expected: &pbmesh.Destinations{ - // Workloads: &pbcatalog.WorkloadSelector{ - // Names: []string{podName}, - // }, - // Destinations: []*pbmesh.Destination{ - // { - // DestinationRef: &pbresource.Reference{ - // Type: pbcatalog.ServiceType, - // Tenancy: &pbresource.Tenancy{ - // Partition: constants.GetNormalizedConsulPartition(""), - // Namespace: "ns1", - // PeerName: "peer1", - // }, - // Name: "upstream1", - // }, - // DestinationPort: "destination", - // Datacenter: "", - // ListenAddr: &pbmesh.Destination_IpPort{ - // IpPort: &pbmesh.IPPortAddress{ - // Port: uint32(1234), - // Ip: consulNodeAddress, - // }, - // }, - // }, - // }, - //}, - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "labeled annotated destination with svc, ns, and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.ap:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "part1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - { - name: "error labeled annotated destination error: invalid partition/dc/peer", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc.ns1.ns.part1.err:1234" - return pod1 - }, - expErr: "error processing destination annotations: destination structured incorrectly: destination.port.upstream1.svc.ns1.ns.part1.err:1234", - consulNamespacesEnabled: true, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: false, - consulPartitionsEnabled: false, - }, - { - name: "unlabeled single destination with namespace and partition", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.upstream.foo.bar:1234" - return pod1 - }, - expected: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "bar", - Namespace: "foo", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - consulNamespacesEnabled: true, - consulPartitionsEnabled: true, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - // Create test consulServer client. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ConsulTenancyConfig: common.ConsulTenancyConfig{ - EnableConsulNamespaces: tt.consulNamespacesEnabled, - EnableConsulPartitions: tt.consulPartitionsEnabled, - }, - ResourceClient: resourceClient, - } - - err = pc.writeDestinations(context.Background(), *tt.pod()) - - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.expected) - } - }) - } -} - -func TestDestinationsDelete(t *testing.T) { - t.Parallel() - - const podName = "pod1" - - cases := []struct { - name string - pod func() *corev1.Pod - existingDestinations *pbmesh.Destinations - expErr string - configEntry func() api.ConfigEntry - consulUnavailable bool - }{ - { - name: "labeled annotated destination with svc only", - pod: func() *corev1.Pod { - pod1 := createPod(podName, "", true, true) - pod1.Annotations[constants.AnnotationMeshDestinations] = "destination.port.upstream1.svc:1234" - return pod1 - }, - existingDestinations: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "upstream1", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - }, - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - } - - // Load in the upstream for us to delete and check that it's there - loadResource(t, context.Background(), resourceClient, getDestinationsID(tt.pod().Name, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.existingDestinations, nil) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tt.existingDestinations) - - // Delete the upstream - nn := types.NamespacedName{Name: tt.pod().Name} - err = pc.deleteDestinations(context.Background(), nn) - - // Verify the upstream has been deleted or that an expected error has been returned - if tt.expErr != "" { - require.EqualError(t, err, tt.expErr) - } else { - require.NoError(t, err) - uID := getDestinationsID(tt.pod().Name, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, nil) - } - }) - } -} - -func TestDeleteACLTokens(t *testing.T) { - t.Parallel() - - podName := "foo-123" - serviceName := "foo" - - // Create test consulServer server. - masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.ACL.Enabled = true - c.ACL.Tokens.InitialManagement = masterToken - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - // Wait for the ACL system to be bootstraped - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Wait for the default partition to be created - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - test.SetupK8sAuthMethodV2(t, testClient.APIClient, serviceName, metav1.NamespaceDefault) - token, _, err := testClient.APIClient.ACL().Login(&api.ACLLoginParams{ - AuthMethod: test.AuthMethod, - BearerToken: test.ServiceAccountJWTToken, - Meta: map[string]string{ - "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, podName), - "component": "connect-injector", - }, - }, nil) - require.NoError(t, err) - - pc := &Controller{ - Log: logrtest.New(t), - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - ResourceClient: resourceClient, - AuthMethod: test.AuthMethod, - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - } - - // Delete the ACL Token - pod := types.NamespacedName{Name: podName, Namespace: metav1.NamespaceDefault} - err = pc.deleteACLTokensForPod(testClient.APIClient, pod) - require.NoError(t, err) - - // Verify the token has been deleted. - _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") -} - -// TestReconcileCreatePod ensures that a new pod reconciliation fans out to create -// the appropriate Consul resources. Translation details from pod to Consul workload are -// tested at the relevant private functions. Any error states that are also tested here. -func TestReconcileCreatePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) - } - - testCases := []testCase{ - { - name: "vanilla new pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - tproxy: true, - telemetry: true, - metrics: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "pod in ignored namespace", - podName: "foo", - namespace: metav1.NamespaceSystem, - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.ObjectMeta.Namespace = metav1.NamespaceSystem - return []runtime.Object{pod} - }, - }, - { - name: "unhealthy new pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "return error - pod has no original pod annotation", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - tproxy: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - expErr: "1 error occurred:\n\t* failed to get expose config: failed to get original pod spec: unexpected end of JSON input\n\n", - }, - { - name: "pod has not been injected", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", false, true) - return []runtime.Object{pod} - }, - }, - { - name: "pod with annotations", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - addProbesAndOriginalPodAnnotation(pod) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - tproxy: false, - telemetry: true, - metrics: true, - overwriteProbes: true, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - expectedDestinations: createDestinations(), - }, - // TODO: make sure multi-error accumulates errors - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// TestReconcileUpdatePod test updating a Pod object when there is already matching resources in Consul. -// Updates are unlikely because of the immutable behaviors of pods as members of deployment/statefulset, -// but theoretically it is possible to update annotations and labels in-place. Most likely this will be -// from a change in health status. -func TestReconcileUpdatePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - tproxy bool - overwriteProbes bool - metrics bool - telemetry bool - - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - TProxyOverwriteProbes: tc.overwriteProbes, - EnableTransparentProxy: tc.tproxy, - EnableTelemetryCollector: tc.telemetry, - } - if tc.metrics { - pc.MetricsConfig = metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "1234", - } - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, context.Background(), resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, context.Background(), resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, context.Background(), resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, context.Background(), resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, context.Background(), resourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, context.Background(), resourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, context.Background(), resourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, context.Background(), resourceClient, uID, tc.expectedDestinations) - } - - testCases := []testCase{ - { - name: "pod update ports", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - return []runtime.Object{pod} - }, - existingHealthStatus: createPassingHealthStatus(), - existingWorkload: &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: consulNodeName, - Identity: "foo", - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - }, - { - name: "pod healthy to unhealthy", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, false) - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - expectedWorkload: createWorkload(), - expectedHealthStatus: createCriticalHealthStatus("foo", "default"), - }, - { - name: "add metrics, tproxy and probe overwrite to pod", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.Annotations[constants.KeyTransparentProxy] = "true" - pod.Annotations[constants.AnnotationTransparentProxyOverwriteProbes] = "true" - pod.Annotations[constants.AnnotationEnableMetrics] = "true" - pod.Annotations[constants.AnnotationPrometheusScrapePort] = "21234" - addProbesAndOriginalPodAnnotation(pod) - - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedProxyConfiguration: &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - TransparentProxy: &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:21234", - }, - }, - }, - { - name: "pod update explicit destination", - podName: "foo", - k8sObjects: func() []runtime.Object { - pod := createPod("foo", "", true, true) - pod.Annotations[constants.AnnotationMeshDestinations] = "destination.port.mySVC.svc:24601" - return []runtime.Object{pod} - }, - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingDestinations: &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: "ap1", - Namespace: "ns1", - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "mySVC3", - }, - DestinationPort: "destination2", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(1234), - Ip: consulNodeAddress, - }, - }, - }, - }, - }, - expectedWorkload: createWorkload(), - expectedHealthStatus: createPassingHealthStatus(), - expectedDestinations: createDestinations(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// Tests deleting a Pod object, with and without matching Consul resources. -func TestReconcileDeletePod(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - Namespace: metav1.NamespaceDefault, - }} - node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: nodeName}} - - type testCase struct { - name string - podName string // This needs to be aligned with the pod created in `k8sObjects` - namespace string // Defaults to metav1.NamespaceDefault if empty. Should be aligned with the ns in the pod - - k8sObjects func() []runtime.Object // testing node is injected separately - - existingWorkload *pbcatalog.Workload - existingHealthStatus *pbcatalog.HealthStatus - existingProxyConfiguration *pbmesh.ProxyConfiguration - existingDestinations *pbmesh.Destinations - - expectedWorkload *pbcatalog.Workload - expectedHealthStatus *pbcatalog.HealthStatus - expectedProxyConfiguration *pbmesh.ProxyConfiguration - expectedDestinations *pbmesh.Destinations - - aclsEnabled bool - - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &node, - } - if tc.k8sObjects != nil { - k8sObjects = append(k8sObjects, tc.k8sObjects()...) - } - - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - masterToken := "b78d37c7-0ca7-5f4d-99ee-6d9975ce4586" - - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - if tc.aclsEnabled { - c.ACL.Enabled = true - c.ACL.Tokens.InitialManagement = masterToken - } - c.Experiments = []string{"resource-apis"} - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - if tc.aclsEnabled { - // Wait for the ACL system to be bootstraped - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - } - - ctx := context.Background() - if tc.aclsEnabled { - ctx = metadata.AppendToOutgoingContext(context.Background(), "x-consul-token", masterToken) - } - - // Wait for the default partition to be created - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(ctx, constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Create the pod controller. - pc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - if tc.aclsEnabled { - pc.AuthMethod = test.AuthMethod - } - - namespace := tc.namespace - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - workloadID := getWorkloadID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition) - loadResource(t, ctx, resourceClient, workloadID, tc.existingWorkload, nil) - loadResource(t, ctx, resourceClient, getHealthStatusID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingHealthStatus, workloadID) - loadResource(t, ctx, resourceClient, getProxyConfigurationID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingProxyConfiguration, nil) - loadResource(t, ctx, resourceClient, getDestinationsID(tc.podName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tc.existingDestinations, nil) - - var token *api.ACLToken - if tc.aclsEnabled { - test.SetupK8sAuthMethodV2(t, testClient.APIClient, tc.podName, metav1.NamespaceDefault) //podName is a standin for the service name - token, _, err = testClient.APIClient.ACL().Login(&api.ACLLoginParams{ - AuthMethod: test.AuthMethod, - BearerToken: test.ServiceAccountJWTToken, - Meta: map[string]string{ - "pod": fmt.Sprintf("%s/%s", metav1.NamespaceDefault, tc.podName), - "component": "connect-injector", - }, - }, nil) - require.NoError(t, err) - } - - namespacedName := types.NamespacedName{ - Namespace: namespace, - Name: tc.podName, - } - - resp, err := pc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - wID := getWorkloadID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedWorkloadMatches(t, ctx, resourceClient, wID, tc.expectedWorkload) - - hsID := getHealthStatusID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedHealthStatusMatches(t, ctx, resourceClient, hsID, tc.expectedHealthStatus) - - pcID := getProxyConfigurationID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedProxyConfigurationMatches(t, ctx, resourceClient, pcID, tc.expectedProxyConfiguration) - - uID := getDestinationsID(tc.podName, metav1.NamespaceDefault, constants.DefaultConsulPartition) - expectedDestinationMatches(t, ctx, resourceClient, uID, tc.expectedDestinations) - - if tc.aclsEnabled { - _, _, err = testClient.APIClient.ACL().TokenRead(token.AccessorID, nil) - require.Contains(t, err.Error(), "ACL not found") - } - - } - - testCases := []testCase{ - { - name: "vanilla delete pod", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - }, - { - name: "annotated delete pod", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_DEFAULT), - existingDestinations: createDestinations(), - }, - { - name: "delete pod w/ acls", - podName: "foo", - existingWorkload: createWorkload(), - existingHealthStatus: createPassingHealthStatus(), - existingProxyConfiguration: createProxyConfiguration("foo", pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT), - aclsEnabled: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// createPod creates a multi-port pod as a base for tests. If `namespace` is empty, -// the default Kube namespace will be used. -func createPod(name, namespace string, inject, ready bool) *corev1.Pod { - if namespace == "" { - namespace = metav1.NamespaceDefault - } - - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.AnnotationConsulK8sVersion: "1.3.0", - }, - }, - Status: corev1.PodStatus{ - PodIP: "10.0.0.1", - HostIP: consulNodeAddress, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "public", - Protocol: corev1.ProtocolTCP, - ContainerPort: 80, - }, - { - Name: "admin", - Protocol: corev1.ProtocolTCP, - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/readyz", - Port: intstr.FromInt(2000), - }, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/livez", - Port: intstr.FromInt(2001), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/startupz", - Port: intstr.FromInt(2002), - }, - }, - }, - }, - }, - NodeName: nodeName, - ServiceAccountName: name, - }, - } - if ready { - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: corev1.ConditionTrue, - }, - } - } else { - pod.Status.Conditions = []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: corev1.ConditionFalse, - }, - } - } - - if inject { - pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected - pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected - } - return pod -} - -// createWorkload creates a workload that matches the pod from createPod. -func createWorkload() *pbcatalog.Workload { - return &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - Identity: "foo", - } -} - -// createPassingHealthStatus creates a passing HealthStatus that matches the pod from createPod. -func createPassingHealthStatus() *pbcatalog.HealthStatus { - return &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: pbcatalog.Health_HEALTH_PASSING, - Output: constants.KubernetesSuccessReasonMsg, - Description: constants.ConsulKubernetesCheckName, - } -} - -// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createCriticalHealthStatus(name string, namespace string) *pbcatalog.HealthStatus { - return &pbcatalog.HealthStatus{ - Type: constants.ConsulKubernetesCheckType, - Status: pbcatalog.Health_HEALTH_CRITICAL, - Output: fmt.Sprintf("Pod \"%s/%s\" is not ready", namespace, name), - Description: constants.ConsulKubernetesCheckName, - } -} - -// createProxyConfiguration creates a proxyConfiguration that matches the pod from createPod, -// assuming that metrics, telemetry, and overwrite probes are enabled separately. -func createProxyConfiguration(podName string, mode pbmesh.ProxyMode) *pbmesh.ProxyConfiguration { - mesh := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{podName}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: mode, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - PrometheusBindAddr: "0.0.0.0:1234", - TelemetryCollectorBindSocketDir: DefaultTelemetryBindSocketDir, - }, - } - - if mode == pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT { - mesh.DynamicConfig.TransparentProxy = &pbmesh.TransparentProxy{ - OutboundListenerPort: 15001, - } - } - - return mesh -} - -// createCriticalHealthStatus creates a failing HealthStatus that matches the pod from createPod. -func createDestinations() *pbmesh.Destinations { - return &pbmesh.Destinations{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - Destinations: []*pbmesh.Destination{ - { - DestinationRef: &pbresource.Reference{ - Type: pbcatalog.ServiceType, - Tenancy: &pbresource.Tenancy{ - Partition: constants.GetNormalizedConsulPartition(""), - Namespace: constants.GetNormalizedConsulNamespace(""), - PeerName: constants.GetNormalizedConsulPeer(""), - }, - Name: "mySVC", - }, - DestinationPort: "destination", - Datacenter: "", - ListenAddr: &pbmesh.Destination_IpPort{ - IpPort: &pbmesh.IPPortAddress{ - Port: uint32(24601), - Ip: consulNodeAddress, - }, - }, - }, - }, - } -} - -func expectedWorkloadMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedWorkload *pbcatalog.Workload) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedWorkload == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualWorkload := &pbcatalog.Workload{} - err = res.GetResource().GetData().UnmarshalTo(actualWorkload) - require.NoError(t, err) - - diff := cmp.Diff(expectedWorkload, actualWorkload, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "Workloads do not match") -} - -func expectedHealthStatusMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedHealthStatus *pbcatalog.HealthStatus) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedHealthStatus == nil { - // Because HealthStatus is asynchronously garbage-collected, we can retry to make sure it gets cleaned up. - require.Eventually(t, func() bool { - _, err := client.Read(ctx, req) - s, ok := status.FromError(err) - return ok && codes.NotFound == s.Code() - }, 3*time.Second, 500*time.Millisecond) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualHealthStatus := &pbcatalog.HealthStatus{} - err = res.GetResource().GetData().UnmarshalTo(actualHealthStatus) - require.NoError(t, err) - - diff := cmp.Diff(expectedHealthStatus, actualHealthStatus, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "HealthStatuses do not match") -} - -func expectedProxyConfigurationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedProxyConfiguration *pbmesh.ProxyConfiguration) { - req := &pbresource.ReadRequest{Id: id} - - res, err := client.Read(ctx, req) - - if expectedProxyConfiguration == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualProxyConfiguration := &pbmesh.ProxyConfiguration{} - err = res.GetResource().GetData().UnmarshalTo(actualProxyConfiguration) - require.NoError(t, err) - - diff := cmp.Diff(expectedProxyConfiguration, actualProxyConfiguration, test.CmpProtoIgnoreOrder()...) - require.Equal(t, "", diff, "ProxyConfigurations do not match") -} - -func expectedDestinationMatches(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, expectedUpstreams *pbmesh.Destinations) { - req := &pbresource.ReadRequest{Id: id} - res, err := client.Read(ctx, req) - - if expectedUpstreams == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - - requireEqualResourceID(t, id, res.GetResource().GetId()) - - require.NotNil(t, res.GetResource().GetData()) - - actualUpstreams := &pbmesh.Destinations{} - err = res.GetResource().GetData().UnmarshalTo(actualUpstreams) - require.NoError(t, err) - - require.True(t, proto.Equal(actualUpstreams, expectedUpstreams)) -} - -func loadResource(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { - if id == nil || !proto.ProtoReflect().IsValid() { - return - } - - data, err := anypb.New(proto) - require.NoError(t, err) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - Owner: owner, - } - - req := &pbresource.WriteRequest{Resource: resource} - _, err = client.Write(ctx, req) - require.NoError(t, err) - test.ResourceHasPersisted(t, ctx, client, id) -} - -func addProbesAndOriginalPodAnnotation(pod *corev1.Pod) { - podBytes, _ := json.Marshal(pod) - pod.Annotations[constants.AnnotationOriginalPod] = string(podBytes) - - // Fake the probe changes that would be added by the mesh webhook - pod.Spec.Containers[0].ReadinessProbe.HTTPGet.Port = intstr.FromInt(20300) - pod.Spec.Containers[0].LivenessProbe.HTTPGet.Port = intstr.FromInt(20400) - pod.Spec.Containers[0].StartupProbe.HTTPGet.Port = intstr.FromInt(20500) -} - -func requireEqualResourceID(t *testing.T, expected, actual *pbresource.ID) { - opts := []cmp.Option{ - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - } - opts = append(opts, test.CmpProtoIgnoreOrder()...) - diff := cmp.Diff(expected, actual, opts...) - require.Equal(t, "", diff, "resource IDs do not match") -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go deleted file mode 100644 index 8027995ac9..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serviceaccount - -import ( - "context" - - "github.com/go-logr/logr" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "google.golang.org/grpc/metadata" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const ( - defaultServiceAccountName = "default" -) - -type Controller struct { - client.Client - // ConsulServerConnMgr is the watcher for the Consul server addresses used to create Consul API v2 clients. - ConsulServerConnMgr consul.ServerConnectionManager - // K8sNamespaceConfig manages allow/deny Kubernetes namespaces. - common.K8sNamespaceConfig - // ConsulTenancyConfig manages settings related to Consul namespaces and partitions. - common.ConsulTenancyConfig - - Log logr.Logger - - Scheme *runtime.Scheme - context.Context -} - -func (r *Controller) Logger(name types.NamespacedName) logr.Logger { - return r.Log.WithValues("request", name) -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.ServiceAccount{}). - Complete(r) -} - -// Reconcile reads the state of a ServiceAccount object for a Kubernetes namespace and reconciles the corresponding -// Consul WorkloadIdentity. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var serviceAccount corev1.ServiceAccount - - // Ignore the request if the namespace of the service account is not allowed. - if inject.ShouldIgnore(req.Namespace, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - // Create Consul resource service client for this reconcile. - resourceClient, err := consul.NewResourceServiceClient(r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul resource client", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - - state, err := r.ConsulServerConnMgr.State() - if err != nil { - r.Log.Error(err, "failed to query Consul client state", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - if state.Token != "" { - ctx = metadata.AppendToOutgoingContext(ctx, "x-consul-token", state.Token) - } - - // We don't allow the default service account synced to prevent unintended TrafficPermissions - if req.Name == defaultServiceAccountName { - r.Log.Info("Not syncing default Kubernetes service account", "namespace", req.Namespace) - return ctrl.Result{}, nil - } - - // If the ServiceAccount object has been deleted (and we get an IsNotFound error), - // we need to deregister that WorkloadIdentity from Consul. - err = r.Client.Get(ctx, req.NamespacedName, &serviceAccount) - if k8serrors.IsNotFound(err) { - err = r.deregisterWorkloadIdentity(ctx, resourceClient, req.Name, r.getConsulNamespace(req.Namespace), r.getConsulPartition()) - return ctrl.Result{}, err - } else if err != nil { - r.Log.Error(err, "failed to get ServiceAccount", "name", req.Name, "ns", req.Namespace) - return ctrl.Result{}, err - } - r.Log.Info("retrieved ServiceAccount", "name", req.Name, "ns", req.Namespace) - - // Ensure the WorkloadIdentity exists. - workloadIdentityResource := r.getWorkloadIdentityResource( - serviceAccount.Name, // Consul and Kubernetes service account name will always match - r.getConsulNamespace(serviceAccount.Namespace), - r.getConsulPartition(), - map[string]string{ - constants.MetaKeyKubeNS: serviceAccount.Namespace, - constants.MetaKeyKubeServiceAccountName: serviceAccount.Name, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - ) - - r.Log.Info("registering workload identity with Consul", getLogFieldsForResource(workloadIdentityResource.Id)...) - // We currently blindly write these records as changes to service accounts and resulting reconciles should be rare, - // and there's no data to conflict with in the payload. - if _, err := resourceClient.Write(ctx, &pbresource.WriteRequest{Resource: workloadIdentityResource}); err != nil { - // We could be racing with the namespace controller. - // Requeue (which includes backoff) to try again. - if inject.ConsulNamespaceIsNotFound(err) { - r.Log.Info("Consul namespace not found; re-queueing request", - "service-account", serviceAccount.Name, "ns", serviceAccount.Namespace, - "consul-ns", workloadIdentityResource.GetId().GetTenancy().GetNamespace(), "err", err.Error()) - return ctrl.Result{Requeue: true}, nil - } - - r.Log.Error(err, "failed to register workload identity", getLogFieldsForResource(workloadIdentityResource.Id)...) - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -// deregisterWorkloadIdentity deletes the WorkloadIdentity resource corresponding to the given name and namespace from -// Consul. This operation is idempotent and can be executed for non-existent service accounts. -func (r *Controller) deregisterWorkloadIdentity(ctx context.Context, resourceClient pbresource.ResourceServiceClient, name, namespace, partition string) error { - _, err := resourceClient.Delete(ctx, &pbresource.DeleteRequest{ - Id: getWorkloadIdentityID(name, namespace, partition), - }) - return err -} - -// getWorkloadIdentityResource converts the given Consul WorkloadIdentity and metadata to a Consul resource API record. -func (r *Controller) getWorkloadIdentityResource(name, namespace, partition string, meta map[string]string) *pbresource.Resource { - return &pbresource.Resource{ - Id: getWorkloadIdentityID(name, namespace, partition), - // WorkloadIdentity is currently an empty message. - Data: inject.ToProtoAny(&pbauth.WorkloadIdentity{}), - Metadata: meta, - } -} - -func getWorkloadIdentityID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - r.EnableConsulNamespaces, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources is no longer required to be set explicitly. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} - -func (r *Controller) getConsulPartition() string { - if !r.EnableConsulPartitions || r.ConsulPartition == "" { - return constants.DefaultConsulPartition - } - return r.ConsulPartition -} - -func getLogFieldsForResource(id *pbresource.ID) []any { - return []any{ - "name", id.Name, - "ns", id.Tenancy.Namespace, - "partition", id.Tenancy.Partition, - } -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go deleted file mode 100644 index d90791d093..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_ent_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package serviceaccount - -import ( - "testing" -) - -// TODO(NET-5719): ConsulDestinationNamespace and EnableNSMirroring +/- prefix - -// TODO(NET-5719) -// Tests new WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_CreateWorkloadIdentity_WithNamespaces(t *testing.T) { - //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff -} - -// TODO(NET-5719) -// Tests removing WorkloadIdentity registration in a non-default NS and Partition with namespaces set to mirroring -func TestReconcile_DeleteWorkloadIdentity_WithNamespaces(t *testing.T) { - //TODO(NET-5719): Add test case to cover Consul namespace missing and check for backoff -} diff --git a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go b/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go deleted file mode 100644 index d2ea94c22d..0000000000 --- a/control-plane/connect-inject/controllers/serviceaccount/serviceaccount_controller_test.go +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package serviceaccount - -import ( - "context" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "google.golang.org/protobuf/proto" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - inject "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -type reconcileCase struct { - name string - svcAccountName string - k8sObjects func() []runtime.Object - existingResource *pbresource.Resource - expectedResource *pbresource.Resource - targetConsulNs string - targetConsulPartition string - expErr string -} - -// TODO(NET-5719): Allow/deny namespaces for reconcile tests - -// TestReconcile_CreateWorkloadIdentity ensures that a new ServiceAccount is reconciled -// to a Consul WorkloadIdentity. -func TestReconcile_CreateWorkloadIdentity(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Default ServiceAccount not synced", - svcAccountName: "default", - k8sObjects: func() []runtime.Object { - return []runtime.Object{createServiceAccount("default", "default")} - }, - }, - { - name: "Custom ServiceAccount", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("my-svc-account", "default"), - } - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Already exists", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("my-svc-account", "default"), - } - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - expectedResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -// Tests deleting a WorkloadIdentity object, with and without matching Consul resources. -func TestReconcile_DeleteWorkloadIdentity(t *testing.T) { - t.Parallel() - cases := []reconcileCase{ - { - name: "Basic ServiceAccount not found (deleted)", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Only default exists (always exists). - return []runtime.Object{createServiceAccount("default", "default")} - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Other ServiceAccount exists", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Default and other ServiceAccount exist - return []runtime.Object{ - createServiceAccount("default", "default"), - createServiceAccount("other-svc-account", "default"), - } - }, - existingResource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "my-svc-account", - Type: pbauth.WorkloadIdentityType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - }, - }, - Data: getWorkloadIdentityData(), - Metadata: map[string]string{ - constants.MetaKeyKubeNS: constants.DefaultConsulNS, - constants.MetaKeyManagedBy: constants.ManagedByServiceAccountValue, - }, - }, - }, - { - name: "Already deleted", - svcAccountName: "my-svc-account", - k8sObjects: func() []runtime.Object { - // Only default exists (always exists). - return []runtime.Object{createServiceAccount("default", "default")} - }, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - runReconcileCase(t, tc) - }) - } -} - -func runReconcileCase(t *testing.T, tc reconcileCase) { - t.Helper() - - // Create fake k8s client - var k8sObjects []runtime.Object - if tc.k8sObjects != nil { - k8sObjects = tc.k8sObjects() - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test Consul server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - // Create the ServiceAccount controller. - sa := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulServerConnMgr: testClient.Watcher, - K8sNamespaceConfig: common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - }, - } - resourceClient, err := consul.NewResourceServiceClient(sa.ConsulServerConnMgr) - require.NoError(t, err) - - require.Eventually(t, func() bool { - _, _, err := testClient.APIClient.Partitions().Read(context.Background(), constants.DefaultConsulPartition, nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - - // Default ns and partition if not specified in test. - if tc.targetConsulNs == "" { - tc.targetConsulNs = constants.DefaultConsulNS - } - if tc.targetConsulPartition == "" { - tc.targetConsulPartition = constants.DefaultConsulPartition - } - - // If existing resource specified, create it and ensure it exists. - if tc.existingResource != nil { - writeReq := &pbresource.WriteRequest{Resource: tc.existingResource} - _, err = resourceClient.Write(context.Background(), writeReq) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), resourceClient, tc.existingResource.Id) - } - - // Run actual reconcile and verify results. - resp, err := sa.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: types.NamespacedName{ - Name: tc.svcAccountName, - Namespace: tc.targetConsulNs, - }, - }) - if tc.expErr != "" { - require.ErrorContains(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedWorkloadIdentityMatches(t, resourceClient, tc.svcAccountName, tc.targetConsulNs, tc.targetConsulPartition, tc.expectedResource) -} - -func expectedWorkloadIdentityMatches(t *testing.T, client pbresource.ResourceServiceClient, name, namespace, partition string, expectedResource *pbresource.Resource) { - req := &pbresource.ReadRequest{Id: getWorkloadIdentityID(name, namespace, partition)} - - res, err := client.Read(context.Background(), req) - - if expectedResource == nil { - require.Error(t, err) - s, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, codes.NotFound, s.Code()) - return - } - - require.NoError(t, err) - require.NotNil(t, res) - require.NotNil(t, res.GetResource().GetData()) - - // This equality check isn't technically necessary because WorkloadIdentity is an empty message, - // but this supports the addition of fields in the future. - expectedWorkloadIdentity := &pbauth.WorkloadIdentity{} - err = anypb.UnmarshalTo(expectedResource.Data, expectedWorkloadIdentity, proto.UnmarshalOptions{}) - require.NoError(t, err) - - actualWorkloadIdentity := &pbauth.WorkloadIdentity{} - err = res.GetResource().GetData().UnmarshalTo(actualWorkloadIdentity) - require.NoError(t, err) - - if diff := cmp.Diff(expectedWorkloadIdentity, actualWorkloadIdentity, test.CmpProtoIgnoreOrder()...); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } -} - -// getWorkloadIdentityData returns a WorkloadIdentity resource payload. -// This function takes no arguments because WorkloadIdentity is currently an empty proto message. -func getWorkloadIdentityData() *anypb.Any { - return inject.ToProtoAny(&pbauth.WorkloadIdentity{}) -} - -func createServiceAccount(name, namespace string) *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - // Other fields exist, but we ignore them in this controller. - } -} diff --git a/control-plane/connect-inject/metrics/metrics_configuration.go b/control-plane/connect-inject/metrics/metrics_configuration.go index 2f217f233d..6f9c29c85b 100644 --- a/control-plane/connect-inject/metrics/metrics_configuration.go +++ b/control-plane/connect-inject/metrics/metrics_configuration.go @@ -8,10 +8,9 @@ import ( "fmt" "strconv" - corev1 "k8s.io/api/core/v1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + corev1 "k8s.io/api/core/v1" ) // Config represents configuration common to connect-inject components related to metrics. diff --git a/control-plane/connect-inject/namespace/namespace_controller.go b/control-plane/connect-inject/namespace/namespace_controller.go deleted file mode 100644 index 86035bc69f..0000000000 --- a/control-plane/connect-inject/namespace/namespace_controller.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package namespace - -import ( - "context" - "fmt" - - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -type Controller struct { - client.Client - // ConsulClientConfig is the config for the Consul API client. - ConsulClientConfig *consul.Config - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - // AllowK8sNamespacesSet determines kube namespace that are reconciled. - AllowK8sNamespacesSet mapset.Set - // DenyK8sNamespacesSet determines kube namespace that are ignored. - DenyK8sNamespacesSet mapset.Set - - // Partition is not required. It should already be set in the API ClientConfig - - // ConsulDestinationNamespace is the name of the Consul namespace to create - // all config entries in. If EnableNSMirroring is true this is ignored. - ConsulDestinationNamespace string - // EnableNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any config entry custom resource. Config entries will - // be created in the matching Consul namespace. - EnableNSMirroring bool - // NSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - NSMirroringPrefix string - - // CrossNamespaceACLPolicy is the name of the ACL policy to attach to - // any created Consul namespaces to allow cross namespace service discovery. - // Only necessary if ACLs are enabled. - CrossNamespaceACLPolicy string - - Log logr.Logger -} - -// Reconcile reads a Kubernetes Namespace and reconciles the mapped namespace in Consul. -// TODO: Move the creation of a destination namespace to a dedicated, single-flight goroutine. -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - var namespace corev1.Namespace - - // Ignore the request if the namespace is not allowed. - if common.ShouldIgnore(req.Name, r.DenyK8sNamespacesSet, r.AllowK8sNamespacesSet) { - return ctrl.Result{}, nil - } - - apiClient, err := consul.NewClientFromConnMgr(r.ConsulClientConfig, r.ConsulServerConnMgr) - if err != nil { - r.Log.Error(err, "failed to create Consul API client", "name", req.Name) - return ctrl.Result{}, err - } - - err = r.Client.Get(ctx, req.NamespacedName, &namespace) - - // If the namespace object has been deleted (and we get an IsNotFound error), - // we need to remove the Namespace from Consul. - if k8serrors.IsNotFound(err) { - - // if we are using a destination namespace, NEVER delete it. - if !r.EnableNSMirroring { - return ctrl.Result{}, nil - } - - if err := namespaces.EnsureDeleted(apiClient, r.getConsulNamespace(req.Name)); err != nil { - r.Log.Error(err, "error deleting namespace", - "namespace", r.getConsulNamespace(req.Name)) - return ctrl.Result{}, fmt.Errorf("error deleting namespace: %w", err) - } - - return ctrl.Result{}, nil - } else if err != nil { - r.Log.Error(err, "failed to get namespace", "name", req.Name) - return ctrl.Result{}, err - } - - r.Log.Info("retrieved", "namespace", namespace.GetName()) - - // TODO: eventually we will want to replace the V1 namespace APIs with the native V2 resource creation for tenancy - if _, err := namespaces.EnsureExists(apiClient, r.getConsulNamespace(namespace.GetName()), r.CrossNamespaceACLPolicy); err != nil { - r.Log.Error(err, "error checking or creating namespace", - "namespace", r.getConsulNamespace(namespace.GetName())) - return ctrl.Result{}, fmt.Errorf("error checking or creating namespace: %w", err) - } - - return ctrl.Result{}, nil -} - -// SetupWithManager registers this controller with the manager. -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Namespace{}). - Complete(r) -} - -// getConsulNamespace returns the Consul destination namespace for a provided Kubernetes namespace -// depending on Consul Namespaces being enabled and the value of namespace mirroring. -func (r *Controller) getConsulNamespace(kubeNamespace string) string { - ns := namespaces.ConsulNamespace( - kubeNamespace, - true, - r.ConsulDestinationNamespace, - r.EnableNSMirroring, - r.NSMirroringPrefix, - ) - - // TODO: remove this if and when the default namespace of resources change. - if ns == "" { - ns = constants.DefaultConsulNS - } - return ns -} diff --git a/control-plane/connect-inject/namespace/namespace_controller_ent_test.go b/control-plane/connect-inject/namespace/namespace_controller_ent_test.go deleted file mode 100644 index 1b63161976..0000000000 --- a/control-plane/connect-inject/namespace/namespace_controller_ent_test.go +++ /dev/null @@ -1,413 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package namespace - -import ( - "context" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - capi "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -const ( - testNamespaceName = "foo" - testCrossACLPolicy = "cross-namespace-policy" -) - -// TestReconcileCreateNamespace ensures that a new namespace is reconciled to a -// Consul namespace. The actual namespace in Consul depends on if the controller -// is configured with a destination namespace or mirroring enabled. -func TestReconcileCreateNamespace(t *testing.T) { - t.Parallel() - - ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: testNamespaceName, - }} - nsDefault := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ - Name: metav1.NamespaceDefault, - }} - - type testCase struct { - name string - kubeNamespaceName string // this will default to "foo" - partition string - - consulDestinationNamespace string - enableNSMirroring bool - nsMirrorPrefix string - - expectedConsulNamespaceName string - expectedConsulNamespace *capi.Namespace - - acls bool - expErr string - } - - run := func(t *testing.T, tc testCase) { - k8sObjects := []runtime.Object{ - &ns, - &nsDefault, - } - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(k8sObjects...).Build() - - // Create test consulServer server. - adminToken := "123e4567-e89b-12d3-a456-426614174000" - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - if tc.acls { - c.ACL.Enabled = tc.acls - c.ACL.Tokens.InitialManagement = adminToken - } - }) - - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - // Create the namespace controller. - nc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - EnableNSMirroring: tc.enableNSMirroring, - NSMirroringPrefix: tc.nsMirrorPrefix, - ConsulDestinationNamespace: tc.consulDestinationNamespace, - } - if tc.acls { - nc.CrossNamespaceACLPolicy = testCrossACLPolicy - - policy := &capi.ACLPolicy{Name: testCrossACLPolicy} - _, _, err := testClient.APIClient.ACL().PolicyCreate(policy, nil) - require.NoError(t, err) - } - - if tc.kubeNamespaceName == "" { - tc.kubeNamespaceName = testNamespaceName - } - - namespacedName := types.NamespacedName{ - Name: tc.kubeNamespaceName, - } - - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - if tc.expErr != "" { - require.EqualError(t, err, tc.expErr) - } else { - require.NoError(t, err) - } - require.False(t, resp.Requeue) - - expectedNamespaceMatches(t, testClient.APIClient, tc.expectedConsulNamespaceName, tc.partition, tc.expectedConsulNamespace) - } - - testCases := []testCase{ - { - // This also tests that we don't overwrite anything about the default Consul namespace, - // because the original description is maintained. - name: "destination namespace default", - expectedConsulNamespaceName: constants.DefaultConsulNS, - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - }, - { - name: "destination namespace, non-default", - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", "", false), - }, - { - name: "destination namespace, non-default with ACLs enabled", - consulDestinationNamespace: "bar", - acls: true, - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "destination namespace, non-default, non-default partition", - partition: "baz", - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", - expectedConsulNamespace: getNamespace("bar", "baz", false), - }, - { - name: "mirrored namespaces", - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespaces, non-default partition", - partition: "baz", - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "baz", false), - }, - { - name: "mirrored namespaces with acls", - acls: true, - enableNSMirroring: true, - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "mirrored namespaces with prefix", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", "", false), - }, - { - name: "mirrored namespaces with prefix, non-default partition", - nsMirrorPrefix: "k8s-", - partition: "baz", - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", "baz", false), - }, - { - name: "mirrored namespaces with prefix and acls", - nsMirrorPrefix: "k8s-", - acls: true, - enableNSMirroring: true, - expectedConsulNamespaceName: "k8s-foo", - expectedConsulNamespace: getNamespace("k8s-foo", constants.DefaultConsulPartition, true), // For some reason, we the partition is returned by Consul in this case, even though it is default - }, - { - name: "mirrored namespaces overrides destination namespace", - enableNSMirroring: true, - consulDestinationNamespace: "baz", - expectedConsulNamespaceName: testNamespaceName, - expectedConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "ignore kube-system", - kubeNamespaceName: metav1.NamespaceSystem, - consulDestinationNamespace: "bar", - expectedConsulNamespaceName: "bar", // we make sure that this doesn't get created from the kube-system space by not providing the actual struct - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// Tests deleting a Namespace object, with and without matching Consul resources. -func TestReconcileDeleteNamespace(t *testing.T) { - t.Parallel() - - type testCase struct { - name string - kubeNamespaceName string // this will default to "foo" - partition string - - destinationNamespace string - enableNSMirroring bool - nsMirrorPrefix string - - existingConsulNamespace *capi.Namespace - - expectedConsulNamespace *capi.Namespace - } - - run := func(t *testing.T, tc testCase) { - fakeClient := fake.NewClientBuilder().WithRuntimeObjects().Build() - - // Create test consulServer server. - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - }) - - if tc.partition != "" { - testClient.Cfg.APIClientConfig.Partition = tc.partition - - partition := &capi.Partition{ - Name: tc.partition, - } - _, _, err := testClient.APIClient.Partitions().Create(context.Background(), partition, nil) - require.NoError(t, err) - } - - if tc.existingConsulNamespace != nil { - _, _, err := testClient.APIClient.Namespaces().Create(tc.existingConsulNamespace, nil) - require.NoError(t, err) - } - - // Create the namespace controller. - nc := &Controller{ - Client: fakeClient, - Log: logrtest.New(t), - ConsulClientConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSetWith(), - EnableNSMirroring: tc.enableNSMirroring, - NSMirroringPrefix: tc.nsMirrorPrefix, - ConsulDestinationNamespace: tc.destinationNamespace, - } - - if tc.kubeNamespaceName == "" { - tc.kubeNamespaceName = testNamespaceName - } - - namespacedName := types.NamespacedName{ - Name: tc.kubeNamespaceName, - } - - resp, err := nc.Reconcile(context.Background(), ctrl.Request{ - NamespacedName: namespacedName, - }) - require.NoError(t, err) - require.False(t, resp.Requeue) - - if tc.existingConsulNamespace != nil { - expectedNamespaceMatches(t, testClient.APIClient, tc.existingConsulNamespace.Name, tc.partition, tc.expectedConsulNamespace) - } else { - expectedNamespaceMatches(t, testClient.APIClient, testNamespaceName, tc.partition, tc.expectedConsulNamespace) - } - } - - testCases := []testCase{ - { - name: "destination namespace with default is not cleaned up", - existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - }, - { - name: "destination namespace with non-default is not cleaned up", - destinationNamespace: "bar", - existingConsulNamespace: getNamespace("bar", "", false), - expectedConsulNamespace: getNamespace("bar", "", false), - }, - { - name: "destination namespace with non-default is not cleaned up, non-default partition", - destinationNamespace: "bar", - partition: "baz", - existingConsulNamespace: getNamespace("bar", "baz", false), - expectedConsulNamespace: getNamespace("bar", "baz", false), - }, - { - name: "mirrored namespaces", - enableNSMirroring: true, - existingConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespaces but it's the default namespace", - kubeNamespaceName: metav1.NamespaceDefault, - enableNSMirroring: true, - existingConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), - expectedConsulNamespace: getNamespace(constants.DefaultConsulNS, "", false), // Don't ever delete the Consul default NS - }, - { - name: "mirrored namespaces, non-default partition", - partition: "baz", - enableNSMirroring: true, - existingConsulNamespace: getNamespace(testNamespaceName, "baz", false), - }, - { - name: "mirrored namespaces with prefix", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - existingConsulNamespace: getNamespace("k8s-foo", "", false), - }, - { - name: "mirrored namespaces with prefix, non-default partition", - partition: "baz", - nsMirrorPrefix: "k8s-", - enableNSMirroring: true, - existingConsulNamespace: getNamespace("k8s-foo", "baz", false), - }, - { - name: "mirrored namespaces overrides destination namespace", - enableNSMirroring: true, - destinationNamespace: "baz", - existingConsulNamespace: getNamespace(testNamespaceName, "", false), - }, - { - name: "mirrored namespace, but the namespace is already removed from Consul", - enableNSMirroring: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -// getNamespace return a basic Consul V1 namespace for testing setup and comparison -func getNamespace(name string, partition string, acls bool) *capi.Namespace { - ns := &capi.Namespace{ - Name: name, - Partition: partition, - } - - if name != constants.DefaultConsulNS { - ns.Description = "Auto-generated by consul-k8s" - ns.Meta = map[string]string{"external-source": "kubernetes"} - ns.ACLs = &capi.NamespaceACLConfig{} - } else { - ns.Description = "Builtin Default Namespace" - } - - if acls && name != constants.DefaultConsulNS { - // Create the ACLs config for the cross-Consul-namespace - // default policy that needs to be attached - ns.ACLs = &capi.NamespaceACLConfig{ - PolicyDefaults: []capi.ACLLink{ - {Name: testCrossACLPolicy}, - }, - } - } - - return ns -} - -func expectedNamespaceMatches(t *testing.T, client *capi.Client, name string, partition string, expectedNamespace *capi.Namespace) { - namespaceInfo, _, err := client.Namespaces().Read(name, &capi.QueryOptions{Partition: partition}) - - require.NoError(t, err) - - if expectedNamespace == nil { - require.True(t, namespaceInfo == nil || namespaceInfo.DeletedAt != nil) - return - } - - require.NotNil(t, namespaceInfo) - // Zero out the Raft Index, in this case it is irrelevant. - namespaceInfo.CreateIndex = 0 - namespaceInfo.ModifyIndex = 0 - if namespaceInfo.ACLs != nil && len(namespaceInfo.ACLs.PolicyDefaults) > 0 { - namespaceInfo.ACLs.PolicyDefaults[0].ID = "" // Zero out the ID for ACLs enabled to facilitate testing. - } - require.Equal(t, *expectedNamespace, *namespaceInfo) -} diff --git a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go index 05a67c3736..ad2e07fffa 100644 --- a/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go +++ b/control-plane/connect-inject/webhook/consul_dataplane_sidecar.go @@ -10,13 +10,12 @@ import ( "strings" "github.com/google/shlex" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" ) const ( @@ -256,7 +255,7 @@ func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, mpi mu args = append(args, "-tls-server-name="+w.ConsulTLSServerName) } if w.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.LegacyConsulCAFile) + args = append(args, "-ca-certs="+constants.ConsulCAFile) } } else { args = append(args, "-tls-disabled") diff --git a/control-plane/connect-inject/webhook/mesh_webhook.go b/control-plane/connect-inject/webhook/mesh_webhook.go index 24c26d4f42..d97bca6646 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook.go +++ b/control-plane/connect-inject/webhook/mesh_webhook.go @@ -15,6 +15,13 @@ import ( mapset "github.com/deckarep/golang-set" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" "gomodules.xyz/jsonpatch/v2" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -23,14 +30,6 @@ import ( "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" ) const ( @@ -295,10 +294,7 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi // port. annotatedSvcNames := w.annotatedServiceNames(pod) multiPort := len(annotatedSvcNames) > 1 - lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) - if ok != nil { - w.Log.Error(err, "unable to get lifecycle enabled status") - } + // For single port pods, add the single init container and envoy sidecar. if !multiPort { // Add the init container that registers the service and sets up the Envoy configuration. @@ -315,14 +311,8 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - //Append the Envoy sidecar before the application container only if lifecycle enabled. - - if lifecycleEnabled && ok == nil { - pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - } - + // TODO: invert to start the Envoy sidecar before the application container + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } else { // For multi port pods, check for unsupported cases, mount all relevant service account tokens, and mount an init // container and envoy sidecar per port. Tproxy, metrics, and metrics merging are not supported for multi port pods. @@ -337,10 +327,6 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "checking unsupported cases for multi port pods") return admission.Errored(http.StatusInternalServerError, err) } - - //List of sidecar containers for each service. Build as a list to preserve correct ordering in relation - //to services. - sidecarContainers := []corev1.Container{} for i, svc := range annotatedSvcNames { w.Log.Info(fmt.Sprintf("service: %s", svc)) if w.AuthMethod != "" { @@ -396,20 +382,9 @@ func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admissi w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) } - // If Lifecycle is enabled, add to the list of sidecar containers to be added - // to pod containers at the end in order to preserve relative ordering. - if lifecycleEnabled { - sidecarContainers = append(sidecarContainers, envoySidecar) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - - } - - } - - //Add sidecar containers first if lifecycle enabled. - if lifecycleEnabled { - pod.Spec.Containers = append(sidecarContainers, pod.Spec.Containers...) + // TODO: invert to start the Envoy sidecar container before the + // application container + pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) } } @@ -534,24 +509,20 @@ func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) erro } if tproxyEnabled && overwriteProbes { - // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, - // which is performed before the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { + for i, container := range pod.Spec.Containers { // skip the "envoy-sidecar" container from having it's probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) + container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + i) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) + container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + i) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) + container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + i) } - idx++ } } return nil @@ -620,7 +591,6 @@ func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error } } pod.Annotations[constants.AnnotationOriginalPod] = podJson - pod.Annotations[constants.LegacyAnnotationConsulK8sVersion] = version.GetHumanVersion() pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() return nil diff --git a/control-plane/connect-inject/webhook/mesh_webhook_test.go b/control-plane/connect-inject/webhook/mesh_webhook_test.go index 2b71c08500..946933f7d7 100644 --- a/control-plane/connect-inject/webhook/mesh_webhook_test.go +++ b/control-plane/connect-inject/webhook/mesh_webhook_test.go @@ -6,13 +6,16 @@ package webhook import ( "context" "encoding/json" - "strconv" "strings" "testing" mapset "github.com/deckarep/golang-set" logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" + "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/stretchr/testify/require" "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" @@ -24,13 +27,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" ) func TestHandlerHandle(t *testing.T) { @@ -142,73 +138,6 @@ func TestHandlerHandle(t *testing.T) { }, }, }, - { - "empty pod basic with lifecycle", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - }, - }, { "pod with upstreams specified", @@ -246,10 +175,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -343,10 +268,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -402,10 +323,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -480,10 +397,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -544,10 +457,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -633,10 +542,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -774,10 +679,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -841,10 +742,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -904,99 +801,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "multiport pod kube < 1.24 with AuthMethod, serviceaccount has secret ref, lifecycle enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: testClientWithServiceAccountAndSecretRefs(), - AuthMethod: "k8s", - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "web,web-admin", - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/containers/2", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1067,10 +871,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1143,10 +943,6 @@ func TestHandlerHandle(t *testing.T) { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, { Operation: "add", Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), @@ -1180,224 +976,6 @@ func TestHandlerHandle(t *testing.T) { } } -// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() -// Because they happen at different points in the injection, the port numbers can get out of sync. -func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { - t.Parallel() - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/tcpSocket", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", - }, - { - Operation: "remove", - Path: "/spec/containers/0/readinessProbe/httpGet", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "remove", - Path: "/spec/containers/0/startupProbe", - }, - { - Operation: "remove", - Path: "/spec/containers/0/livenessProbe", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.LegacyAnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - var iptablesCfg iptables.Config - var overwritePorts []string - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - - // We want to grab the iptables configuration from the connect-init container's - // environment. - if actual[i].Path == "/spec/initContainers" { - value := actual[i].Value.([]any) - valueMap := value[0].(map[string]any) - envs := valueMap["env"].([]any) - redirectEnv := envs[8].(map[string]any) - require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") - iptablesJson := redirectEnv["value"].(string) - - err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) - require.NoError(t, err) - } - - // We want to accumulate the httpGet Probes from the application container to - // compare them to the iptables rules. This is now the second container in the spec - if strings.Contains(actual[i].Path, "/spec/containers/1") { - valueMap, ok := actual[i].Value.(map[string]any) - require.True(t, ok) - - for k, v := range valueMap { - if strings.Contains(k, "Probe") { - probe := v.(map[string]any) - httpProbe := probe["httpGet"] - httpProbeMap := httpProbe.(map[string]any) - port := httpProbeMap["port"] - portNum := port.(float64) - - overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) - } - } - } - - // nil out all the patch values to just compare the keys changing. - actual[i].Value = nil - } - } - // Make sure the iptables excluded ports match the ports on the container - require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - func TestHandlerDefaultAnnotations(t *testing.T) { cases := []struct { Name string @@ -1409,9 +987,8 @@ func TestHandlerDefaultAnnotations(t *testing.T) { "empty", &corev1.Pod{}, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1431,9 +1008,8 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1459,10 +1035,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - "consul.hashicorp.com/connect-service": "foo", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + "consul.hashicorp.com/connect-service": "foo", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null,\"annotations\":{\"consul.hashicorp.com/connect-service\":\"foo\"}},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", @@ -1489,10 +1064,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "http", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "http", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, @@ -1517,10 +1091,9 @@ func TestHandlerDefaultAnnotations(t *testing.T) { }, }, map[string]string{ - constants.AnnotationPort: "8080", - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.LegacyAnnotationConsulK8sVersion: version.GetHumanVersion(), - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), + constants.AnnotationPort: "8080", + constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", + constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), }, "", }, diff --git a/control-plane/connect-inject/webhook/redirect_traffic.go b/control-plane/connect-inject/webhook/redirect_traffic.go index f928df4afd..b0cbefeeaa 100644 --- a/control-plane/connect-inject/webhook/redirect_traffic.go +++ b/control-plane/connect-inject/webhook/redirect_traffic.go @@ -8,11 +8,10 @@ import ( "fmt" "strconv" - "github.com/hashicorp/consul/sdk/iptables" - corev1 "k8s.io/api/core/v1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul/sdk/iptables" + corev1 "k8s.io/api/core/v1" ) // addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. @@ -63,24 +62,20 @@ func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (s } if overwriteProbes { - // We don't use the loop index because this needs to line up w.overwriteProbes(), - // which is performed after the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "consul-dataplane" container from having its probes overridden + for i, container := range pod.Spec.Containers { + // skip the "envoy-sidecar" container from having its probes overridden if container.Name == sidecarContainer { continue } if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+i)) } if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+i)) } if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) + cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+i)) } - idx++ } } diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go deleted file mode 100644 index ed0983a1c8..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar.go +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - "strings" - - "github.com/google/shlex" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - consulDataplaneDNSBindHost = "127.0.0.1" - consulDataplaneDNSBindPort = 8600 -) - -func (w *MeshWebhook) consulDataplaneSidecar(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { - resources, err := w.sidecarResources(pod) - if err != nil { - return corev1.Container{}, err - } - - // Extract the service account token's volume mount. - var bearerTokenFile string - var saTokenVolumeMount corev1.VolumeMount - if w.AuthMethod != "" { - saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) - if err != nil { - return corev1.Container{}, err - } - } - - args, err := w.getContainerSidecarArgs(namespace, bearerTokenFile, pod) - if err != nil { - return corev1.Container{}, err - } - - containerName := sidecarContainer - - var probe *corev1.Probe - if useProxyHealthCheck(pod) { - // If using the proxy health check for a service, configure an HTTP handler - // that queries the '/ready' endpoint of the proxy. - probe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(constants.ProxyDefaultHealthPort), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - } else { - probe = &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(constants.ProxyDefaultInboundPort), - }, - }, - InitialDelaySeconds: 1, - } - } - - container := corev1.Container{ - Name: containerName, - Image: w.ImageConsulDataplane, - Resources: resources, - // We need to set tmp dir to an ephemeral volume that we're mounting so that - // consul-dataplane can write files to it. Otherwise, it wouldn't be able to - // because we set file system to be read-only. - Env: []corev1.EnvVar{ - { - Name: "TMPDIR", - Value: "/consul/mesh-inject", - }, - { - Name: "NODE_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - FieldPath: "spec.nodeName", - }, - }, - }, - // The pod name isn't known currently, so we must rely on the environment variable to fill it in rather than using args. - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: "DP_PROXY_ID", - Value: "$(POD_NAME)", - }, - { - Name: "DP_CREDENTIAL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - }, - Args: args, - } - - container.ReadinessProbe = probe - - if w.AuthMethod != "" { - container.VolumeMounts = append(container.VolumeMounts, saTokenVolumeMount) - } - - if useProxyHealthCheck(pod) { - // Configure the Readiness Address for the proxy's health check to be the Pod IP. - container.Env = append(container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - // Configure the port on which the readiness probe will query the proxy for its health. - container.Ports = append(container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: int32(constants.ProxyDefaultHealthPort), - }) - } - - // Add any extra VolumeMounts. - if userVolMount, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolumeMount]; ok { - var volumeMounts []corev1.VolumeMount - err := json.Unmarshal([]byte(userVolMount), &volumeMounts) - if err != nil { - return corev1.Container{}, err - } - container.VolumeMounts = append(container.VolumeMounts, volumeMounts...) - } - - tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) - if err != nil { - return corev1.Container{}, err - } - - // If not running in transparent proxy mode and in an OpenShift environment, - // skip setting the security context and let OpenShift set it for us. - // When transparent proxy is enabled, then consul-dataplane needs to run as our specific user - // so that traffic redirection will work. - if tproxyEnabled || !w.EnableOpenShift { - if pod.Spec.SecurityContext != nil { - // User container and consul-dataplane container cannot have the same UID. - if pod.Spec.SecurityContext.RunAsUser != nil && *pod.Spec.SecurityContext.RunAsUser == sidecarUserAndGroupID { - return corev1.Container{}, fmt.Errorf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID) - } - } - // Ensure that none of the user's containers have the same UID as consul-dataplane. At this point in injection the meshWebhook - // has only injected init containers so all containers defined in pod.Spec.Containers are from the user. - for _, c := range pod.Spec.Containers { - // User container and consul-dataplane container cannot have the same UID. - if c.SecurityContext != nil && c.SecurityContext.RunAsUser != nil && *c.SecurityContext.RunAsUser == sidecarUserAndGroupID && c.Image != w.ImageConsulDataplane { - return corev1.Container{}, fmt.Errorf("container %q has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", c.Name, sidecarUserAndGroupID) - } - } - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - } - } - - return container, nil -} - -func (w *MeshWebhook) getContainerSidecarArgs(namespace corev1.Namespace, bearerTokenFile string, pod corev1.Pod) ([]string, error) { - envoyConcurrency := w.DefaultEnvoyProxyConcurrency - - // Check to see if the user has overriden concurrency via an annotation. - if envoyConcurrencyAnnotation, ok := pod.Annotations[constants.AnnotationEnvoyProxyConcurrency]; ok { - val, err := strconv.ParseUint(envoyConcurrencyAnnotation, 10, 64) - if err != nil { - return nil, fmt.Errorf("unable to parse annotation %q: %w", constants.AnnotationEnvoyProxyConcurrency, err) - } - envoyConcurrency = int(val) - } - - args := []string{ - "-addresses", w.ConsulAddress, - "-grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort), - "-log-level=" + w.LogLevel, - "-log-json=" + strconv.FormatBool(w.LogJSON), - "-envoy-concurrency=" + strconv.Itoa(envoyConcurrency), - } - - if w.SkipServerWatch { - args = append(args, "-server-watch-disabled=true") - } - - if w.AuthMethod != "" { - args = append(args, - "-credential-type=login", - "-login-auth-method="+w.AuthMethod, - "-login-bearer-token-path="+bearerTokenFile, - // We don't know the pod name at this time, so we must use environment variables to populate the login-meta instead. - ) - if w.EnableNamespaces { - if w.EnableK8SNSMirroring { - args = append(args, "-login-namespace=default") - } else { - args = append(args, "-login-namespace="+w.consulNamespace(namespace.Name)) - } - } - if w.ConsulPartition != "" { - args = append(args, "-login-partition="+w.ConsulPartition) - } - } - if w.EnableNamespaces { - args = append(args, "-proxy-namespace="+w.consulNamespace(namespace.Name)) - } - if w.ConsulPartition != "" { - args = append(args, "-proxy-partition="+w.ConsulPartition) - } - if w.TLSEnabled { - if w.ConsulTLSServerName != "" { - args = append(args, "-tls-server-name="+w.ConsulTLSServerName) - } - if w.ConsulCACert != "" { - args = append(args, "-ca-certs="+constants.ConsulCAFile) - } - } else { - args = append(args, "-tls-disabled") - } - - // Configure the readiness port on the dataplane sidecar if proxy health checks are enabled. - if useProxyHealthCheck(pod) { - args = append(args, fmt.Sprintf("%s=%d", "-envoy-ready-bind-port", constants.ProxyDefaultHealthPort)) - } - - // The consul-dataplane HTTP listener always starts for graceful shutdown. To avoid port conflicts, the - // graceful port always needs to be set - gracefulPort, err := w.LifecycleConfig.GracefulPort(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle graceful port: %w", err) - } - - args = append(args, fmt.Sprintf("-graceful-port=%d", gracefulPort)) - - enableProxyLifecycle, err := w.LifecycleConfig.EnableProxyLifecycle(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if proxy lifecycle management is enabled: %w", err) - } - if enableProxyLifecycle { - shutdownDrainListeners, err := w.LifecycleConfig.EnableShutdownDrainListeners(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if proxy lifecycle shutdown listener draining is enabled: %w", err) - } - if shutdownDrainListeners { - args = append(args, "-shutdown-drain-listeners") - } - - shutdownGracePeriodSeconds, err := w.LifecycleConfig.ShutdownGracePeriodSeconds(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle shutdown grace period: %w", err) - } - args = append(args, fmt.Sprintf("-shutdown-grace-period-seconds=%d", shutdownGracePeriodSeconds)) - - gracefulShutdownPath := w.LifecycleConfig.GracefulShutdownPath(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine proxy lifecycle graceful shutdown path: %w", err) - } - args = append(args, fmt.Sprintf("-graceful-shutdown-path=%s", gracefulShutdownPath)) - } - - // Set a default scrape path that can be overwritten by the annotation. - prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(pod) - args = append(args, "-telemetry-prom-scrape-path="+prometheusScrapePath) - - metricsServer, err := w.MetricsConfig.ShouldRunMergedMetricsServer(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if merged metrics is enabled: %w", err) - } - if metricsServer { - - // (TODO) Figure out what port will be used for merged metrics and setup merged metrics - - mergedMetricsPort, err := w.MetricsConfig.MergedMetricsPort(pod) - if err != nil { - return nil, fmt.Errorf("unable to determine if merged metrics port: %w", err) - } - args = append(args, "-telemetry-prom-merge-port="+mergedMetricsPort) - - // Pull the TLS config from the relevant annotations. - var prometheusCAFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAFile]; ok && raw != "" { - prometheusCAFile = raw - } - - var prometheusCAPath string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCAPath]; ok && raw != "" { - prometheusCAPath = raw - } - - var prometheusCertFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusCertFile]; ok && raw != "" { - prometheusCertFile = raw - } - - var prometheusKeyFile string - if raw, ok := pod.Annotations[constants.AnnotationPrometheusKeyFile]; ok && raw != "" { - prometheusKeyFile = raw - } - - // Validate required Prometheus TLS config is present if set. - if prometheusCAFile != "" || prometheusCAPath != "" || prometheusCertFile != "" || prometheusKeyFile != "" { - if prometheusCAFile == "" && prometheusCAPath == "" { - return nil, fmt.Errorf("must set one of %q or %q when providing prometheus TLS config", constants.AnnotationPrometheusCAFile, constants.AnnotationPrometheusCAPath) - } - if prometheusCertFile == "" { - return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusCertFile) - } - if prometheusKeyFile == "" { - return nil, fmt.Errorf("must set %q when providing prometheus TLS config", constants.AnnotationPrometheusKeyFile) - } - // TLS config has been validated, add them to the consul-dataplane cmd args - args = append(args, "-telemetry-prom-ca-certs-file="+prometheusCAFile, - "-telemetry-prom-ca-certs-path="+prometheusCAPath, - "-telemetry-prom-cert-file="+prometheusCertFile, - "-telemetry-prom-key-file="+prometheusKeyFile) - } - } - - // If Consul DNS is enabled, we want to configure consul-dataplane to be the DNS proxy - // for Consul DNS in the pod. - dnsEnabled, err := consulDNSEnabled(namespace, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - return nil, err - } - if dnsEnabled { - args = append(args, "-consul-dns-bind-port="+strconv.Itoa(consulDataplaneDNSBindPort)) - } - - var envoyExtraArgs []string - extraArgs, annotationSet := pod.Annotations[constants.AnnotationEnvoyExtraArgs] - - if annotationSet || w.EnvoyExtraArgs != "" { - extraArgsToUse := w.EnvoyExtraArgs - - // Prefer args set by pod annotation over the flag to the consul-k8s binary (h.EnvoyExtraArgs). - if annotationSet { - extraArgsToUse = extraArgs - } - - // Split string into tokens. - // e.g. "--foo bar --boo baz" --> ["--foo", "bar", "--boo", "baz"] - tokens, err := shlex.Split(extraArgsToUse) - if err != nil { - return []string{}, err - } - for _, t := range tokens { - if strings.Contains(t, " ") { - t = strconv.Quote(t) - } - envoyExtraArgs = append(envoyExtraArgs, t) - } - } - if envoyExtraArgs != nil { - args = append(args, "--") - args = append(args, envoyExtraArgs...) - } - return args, nil -} - -func (w *MeshWebhook) sidecarResources(pod corev1.Pod) (corev1.ResourceRequirements, error) { - resources := corev1.ResourceRequirements{ - Limits: corev1.ResourceList{}, - Requests: corev1.ResourceList{}, - } - // zeroQuantity is used for comparison to see if a quantity was explicitly - // set. - var zeroQuantity resource.Quantity - - // NOTE: We only want to set the limit/request if the default or annotation - // was explicitly set. If it's not explicitly set, it will be the zero value - // which would show up in the pod spec as being explicitly set to zero if we - // set that key, e.g. "cpu" to zero. - // We want it to not show up in the pod spec at all if it's not explicitly - // set so that users aren't wondering why it's set to 0 when they didn't specify - // a request/limit. If they have explicitly set it to 0 then it will be set - // to 0 in the pod spec because we're doing a comparison to the zero-valued - // struct. - - // CPU Limit. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPULimit]; ok { - cpuLimit, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPULimit, anno, err) - } - resources.Limits[corev1.ResourceCPU] = cpuLimit - } else if w.DefaultProxyCPULimit != zeroQuantity { - resources.Limits[corev1.ResourceCPU] = w.DefaultProxyCPULimit - } - - // CPU Request. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyCPURequest]; ok { - cpuRequest, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyCPURequest, anno, err) - } - resources.Requests[corev1.ResourceCPU] = cpuRequest - } else if w.DefaultProxyCPURequest != zeroQuantity { - resources.Requests[corev1.ResourceCPU] = w.DefaultProxyCPURequest - } - - // Memory Limit. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryLimit]; ok { - memoryLimit, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryLimit, anno, err) - } - resources.Limits[corev1.ResourceMemory] = memoryLimit - } else if w.DefaultProxyMemoryLimit != zeroQuantity { - resources.Limits[corev1.ResourceMemory] = w.DefaultProxyMemoryLimit - } - - // Memory Request. - if anno, ok := pod.Annotations[constants.AnnotationSidecarProxyMemoryRequest]; ok { - memoryRequest, err := resource.ParseQuantity(anno) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("parsing annotation %s:%q: %s", constants.AnnotationSidecarProxyMemoryRequest, anno, err) - } - resources.Requests[corev1.ResourceMemory] = memoryRequest - } else if w.DefaultProxyMemoryRequest != zeroQuantity { - resources.Requests[corev1.ResourceMemory] = w.DefaultProxyMemoryRequest - } - - return resources, nil -} - -// useProxyHealthCheck returns true if the pod has the annotation 'consul.hashicorp.com/use-proxy-health-check' -// set to truthy values. -func useProxyHealthCheck(pod corev1.Pod) bool { - if v, ok := pod.Annotations[constants.AnnotationUseProxyHealthCheck]; ok { - useProxyHealthCheck, err := strconv.ParseBool(v) - if err != nil { - return false - } - return useProxyHealthCheck - } - return false -} diff --git a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go b/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go deleted file mode 100644 index fecef1bb5d..0000000000 --- a/control-plane/connect-inject/webhookv2/consul_dataplane_sidecar_test.go +++ /dev/null @@ -1,1101 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - "strings" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -const nodeName = "test-node" - -func TestHandlerConsulDataplaneSidecar(t *testing.T) { - cases := map[string]struct { - webhookSetupFunc func(w *MeshWebhook) - additionalExpCmdArgs string - }{ - "default": { - webhookSetupFunc: nil, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with custom gRPC port": { - webhookSetupFunc: func(w *MeshWebhook) { - w.ConsulConfig.GRPCPort = 8602 - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and namespace mirroring": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-namespace=default -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and single destination namespace": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.EnableNamespaces = true - w.ConsulDestinationNamespace = "test-ns" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-namespace=test-ns -proxy-namespace=test-ns -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with ACLs and partitions": { - webhookSetupFunc: func(w *MeshWebhook) { - w.AuthMethod = "test-auth-method" - w.ConsulPartition = "test-part" - }, - additionalExpCmdArgs: " -credential-type=login -login-auth-method=test-auth-method -login-bearer-token-path=/var/run/secrets/kubernetes.io/serviceaccount/token " + - "-login-partition=test-part -proxy-partition=test-part -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with TLS and CA cert provided": { - webhookSetupFunc: func(w *MeshWebhook) { - w.TLSEnabled = true - w.ConsulTLSServerName = "server.dc1.consul" - w.ConsulCACert = "consul-ca-cert" - }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -ca-certs=/consul/mesh-inject/consul-ca.pem -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with TLS and no CA cert provided": { - webhookSetupFunc: func(w *MeshWebhook) { - w.TLSEnabled = true - w.ConsulTLSServerName = "server.dc1.consul" - }, - additionalExpCmdArgs: " -tls-server-name=server.dc1.consul -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with single destination namespace": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.ConsulDestinationNamespace = "consul-namespace" - }, - additionalExpCmdArgs: " -proxy-namespace=consul-namespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with namespace mirroring": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - }, - additionalExpCmdArgs: " -proxy-namespace=k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with namespace mirroring prefix": { - webhookSetupFunc: func(w *MeshWebhook) { - w.EnableNamespaces = true - w.EnableK8SNSMirroring = true - w.K8SNSMirroringPrefix = "foo-" - }, - additionalExpCmdArgs: " -proxy-namespace=foo-k8snamespace -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with partitions": { - webhookSetupFunc: func(w *MeshWebhook) { - w.ConsulPartition = "partition-1" - }, - additionalExpCmdArgs: " -proxy-partition=partition-1 -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with different log level": { - webhookSetupFunc: func(w *MeshWebhook) { - w.LogLevel = "debug" - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "with different log level and log json": { - webhookSetupFunc: func(w *MeshWebhook) { - w.LogLevel = "debug" - w.LogJSON = true - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "skip server watch enabled": { - webhookSetupFunc: func(w *MeshWebhook) { - w.SkipServerWatch = true - }, - additionalExpCmdArgs: " -server-watch-disabled=true -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/metrics", - }, - "custom prometheus scrape path": { - webhookSetupFunc: func(w *MeshWebhook) { - w.MetricsConfig.DefaultPrometheusScrapePath = "/scrape-path" // Simulate what would be passed as a flag - }, - additionalExpCmdArgs: " -tls-disabled -graceful-port=20600 -telemetry-prom-scrape-path=/scrape-path", - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := &MeshWebhook{ - ConsulAddress: "1.1.1.1", - ConsulConfig: &consul.Config{GRPCPort: 8502}, - LogLevel: "info", - LogJSON: false, - } - if c.webhookSetupFunc != nil { - c.webhookSetupFunc(w) - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - { - Name: "auth-method-secret", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }, - }, - }, - ServiceAccountName: "web", - NodeName: nodeName, - }, - } - - container, err := w.consulDataplaneSidecar(testNS, pod) - require.NoError(t, err) - expCmd := "-addresses 1.1.1.1 -grpc-port=" + strconv.Itoa(w.ConsulConfig.GRPCPort) + - " -log-level=" + w.LogLevel + " -log-json=" + strconv.FormatBool(w.LogJSON) + " -envoy-concurrency=0" + c.additionalExpCmdArgs - require.Equal(t, expCmd, strings.Join(container.Args, " ")) - - if w.AuthMethod != "" { - require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }) - } else { - require.Equal(t, container.VolumeMounts, []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - }) - } - - expectedProbe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(constants.ProxyDefaultInboundPort), - }, - }, - InitialDelaySeconds: 1, - } - require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Nil(t, container.StartupProbe) - require.Len(t, container.Env, 6) - require.Equal(t, container.Env[0].Name, "TMPDIR") - require.Equal(t, container.Env[0].Value, "/consul/mesh-inject") - require.Equal(t, container.Env[2].Name, "POD_NAME") - require.Equal(t, container.Env[3].Name, "POD_NAMESPACE") - require.Equal(t, container.Env[4].Name, "DP_PROXY_ID") - require.Equal(t, container.Env[4].Value, "$(POD_NAME)") - require.Equal(t, container.Env[5].Name, "DP_CREDENTIAL_LOGIN_META") - require.Equal(t, container.Env[5].Value, "pod=$(POD_NAMESPACE)/$(POD_NAME)") - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Concurrency(t *testing.T) { - cases := map[string]struct { - annotations map[string]string - expFlags string - expErr string - }{ - "default settings, no annotations": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - expFlags: "-envoy-concurrency=0", - }, - "default settings, annotation override": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "42", - }, - expFlags: "-envoy-concurrency=42", - }, - "default settings, invalid concurrency annotation negative number": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "-42", - }, - expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"-42\": invalid syntax", - }, - "default settings, not-parseable concurrency annotation": { - annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationEnvoyProxyConcurrency: "not-int", - }, - expErr: "unable to parse annotation \"consul.hashicorp.com/consul-envoy-proxy-concurrency\": strconv.ParseUint: parsing \"not-int\": invalid syntax", - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := h.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.EqualError(t, err, c.expErr) - } else { - require.NoError(t, err) - require.Contains(t, strings.Join(container.Args, " "), c.expFlags) - } - }) - } -} - -// Test that we pass the dns proxy flag to dataplane correctly. -func TestHandlerConsulDataplaneSidecar_DNSProxy(t *testing.T) { - - // We only want the flag passed when DNS and tproxy are both enabled. DNS/tproxy can - // both be enabled/disabled with annotations/labels on the pod and namespace and then globally - // through the helm chart. To test this we use an outer loop with the possible DNS settings and then - // and inner loop with possible tproxy settings. - dnsCases := []struct { - GlobalConsulDNS bool - NamespaceDNS *bool - PodDNS *bool - ExpEnabled bool - }{ - { - GlobalConsulDNS: false, - ExpEnabled: false, - }, - { - GlobalConsulDNS: true, - ExpEnabled: true, - }, - { - GlobalConsulDNS: false, - NamespaceDNS: boolPtr(true), - ExpEnabled: true, - }, - { - GlobalConsulDNS: false, - PodDNS: boolPtr(true), - ExpEnabled: true, - }, - } - tproxyCases := []struct { - GlobalTProxy bool - NamespaceTProxy *bool - PodTProxy *bool - ExpEnabled bool - }{ - { - GlobalTProxy: false, - ExpEnabled: false, - }, - { - GlobalTProxy: true, - ExpEnabled: true, - }, - { - GlobalTProxy: false, - NamespaceTProxy: boolPtr(true), - ExpEnabled: true, - }, - { - GlobalTProxy: false, - PodTProxy: boolPtr(true), - ExpEnabled: true, - }, - } - - // Outer loop is permutations of dns being enabled. Inner loop is permutations of tproxy being enabled. - // Both must be enabled for dns to be enabled. - for i, dnsCase := range dnsCases { - for j, tproxyCase := range tproxyCases { - t.Run(fmt.Sprintf("dns=%d,tproxy=%d", i, j), func(t *testing.T) { - - // Test setup. - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - EnableTransparentProxy: tproxyCase.GlobalTProxy, - EnableConsulDNS: dnsCase.GlobalConsulDNS, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - if dnsCase.PodDNS != nil { - pod.Annotations[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.PodDNS) - } - if tproxyCase.PodTProxy != nil { - pod.Annotations[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.PodTProxy) - } - - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Labels: map[string]string{}, - }, - } - if dnsCase.NamespaceDNS != nil { - ns.Labels[constants.KeyConsulDNS] = strconv.FormatBool(*dnsCase.NamespaceDNS) - } - if tproxyCase.NamespaceTProxy != nil { - ns.Labels[constants.KeyTransparentProxy] = strconv.FormatBool(*tproxyCase.NamespaceTProxy) - } - - // Actual test here. - container, err := h.consulDataplaneSidecar(ns, pod) - require.NoError(t, err) - // Flag should only be passed if both tproxy and dns are enabled. - if tproxyCase.ExpEnabled && dnsCase.ExpEnabled { - require.Contains(t, container.Args, "-consul-dns-bind-port=8600") - } else { - require.NotContains(t, container.Args, "-consul-dns-bind-port=8600") - } - }) - } - } -} - -func TestHandlerConsulDataplaneSidecar_ProxyHealthCheck(t *testing.T) { - h := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - ConsulAddress: "1.1.1.1", - LogLevel: "info", - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := h.consulDataplaneSidecar(testNS, pod) - expectedProbe := &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(21000), - Path: "/ready", - }, - }, - InitialDelaySeconds: 1, - } - require.NoError(t, err) - require.Contains(t, container.Args, "-envoy-ready-bind-port=21000") - require.Equal(t, expectedProbe, container.ReadinessProbe) - require.Contains(t, container.Env, corev1.EnvVar{ - Name: "DP_ENVOY_READY_BIND_ADDRESS", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"}, - }, - }) - require.Contains(t, container.Ports, corev1.ContainerPort{ - Name: "proxy-health", - ContainerPort: 21000, - }) -} - -func TestHandlerConsulDataplaneSidecar_withSecurityContext(t *testing.T) { - cases := map[string]struct { - tproxyEnabled bool - openShiftEnabled bool - expSecurityContext *corev1.SecurityContext - }{ - "tproxy disabled; openshift disabled": { - tproxyEnabled: false, - openShiftEnabled: false, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - }, - }, - "tproxy enabled; openshift disabled": { - tproxyEnabled: true, - openShiftEnabled: false, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - }, - }, - "tproxy disabled; openshift enabled": { - tproxyEnabled: false, - openShiftEnabled: true, - expSecurityContext: nil, - }, - "tproxy enabled; openshift enabled": { - tproxyEnabled: true, - openShiftEnabled: true, - expSecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - RunAsGroup: pointer.Int64(sidecarUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - ReadOnlyRootFilesystem: pointer.Bool(true), - }, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableTransparentProxy: c.tproxyEnabled, - EnableOpenShift: c.openShiftEnabled, - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - ec, err := w.consulDataplaneSidecar(testNS, pod) - require.NoError(t, err) - require.Equal(t, c.expSecurityContext, ec.SecurityContext) - }) - } -} - -// Test that if the user specifies a pod security context with the same uid as `sidecarUserAndGroupID` that we return -// an error to the meshWebhook. -func TestHandlerConsulDataplaneSidecar_FailsWithDuplicatePodSecurityContextUID(t *testing.T) { - require := require.New(t) - w := MeshWebhook{ - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - pod := corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - SecurityContext: &corev1.PodSecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - }, - } - _, err := w.consulDataplaneSidecar(testNS, pod) - require.EqualError(err, fmt.Sprintf("pod's security context cannot have the same UID as consul-dataplane: %v", sidecarUserAndGroupID)) -} - -// Test that if the user specifies a container with security context with the same uid as `sidecarUserAndGroupID` that we -// return an error to the meshWebhook. If a container using the consul-dataplane image has the same uid, we don't return an error -// because in multiport pod there can be multiple consul-dataplane sidecars. -func TestHandlerConsulDataplaneSidecar_FailsWithDuplicateContainerSecurityContextUID(t *testing.T) { - cases := []struct { - name string - pod corev1.Pod - webhook MeshWebhook - expErr bool - expErrMessage string - }{ - { - name: "fails with non consul-dataplane image", - pod: corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - // Setting RunAsUser: 1 should succeed. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), - }, - }, - { - Name: "app", - // Setting RunAsUser: 5995 should fail. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - Image: "not-consul-dataplane", - }, - }, - }, - }, - webhook: MeshWebhook{}, - expErr: true, - expErrMessage: fmt.Sprintf("container \"app\" has runAsUser set to the same UID \"%d\" as consul-dataplane which is not allowed", sidecarUserAndGroupID), - }, - { - name: "doesn't fail with envoy image", - pod: corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - // Setting RunAsUser: 1 should succeed. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(1), - }, - }, - { - Name: "sidecar", - // Setting RunAsUser: 5995 should succeed if the image matches h.ImageConsulDataplane. - SecurityContext: &corev1.SecurityContext{ - RunAsUser: pointer.Int64(sidecarUserAndGroupID), - }, - Image: "envoy", - }, - }, - }, - }, - webhook: MeshWebhook{ - ImageConsulDataplane: "envoy", - }, - expErr: false, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - tc.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - _, err := tc.webhook.consulDataplaneSidecar(testNS, tc.pod) - if tc.expErr { - require.EqualError(t, err, tc.expErrMessage) - } else { - require.NoError(t, err) - } - }) - } -} - -// Test that we can pass extra args to envoy via the extraEnvoyArgs flag -// or via pod annotations. When arguments are passed in both ways, the -// arguments set via pod annotations are used. -func TestHandlerConsulDataplaneSidecar_EnvoyExtraArgs(t *testing.T) { - cases := []struct { - name string - envoyExtraArgs string - pod *corev1.Pod - expectedExtraArgs string - }{ - { - name: "no extra options provided", - envoyExtraArgs: "", - pod: &corev1.Pod{}, - expectedExtraArgs: "", - }, - { - name: "via flag: extra log-level option", - envoyExtraArgs: "--log-level debug", - pod: &corev1.Pod{}, - expectedExtraArgs: "-- --log-level debug", - }, - { - name: "via flag: multiple arguments with quotes", - envoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - pod: &corev1.Pod{}, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - { - name: "via annotation: multiple arguments with quotes", - envoyExtraArgs: "", - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - }, - }, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - { - name: "via flag and annotation: should prefer setting via the annotation", - envoyExtraArgs: "this should be overwritten", - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - }, - }, - expectedExtraArgs: "-- --log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - h := MeshWebhook{ - ImageConsul: "hashicorp/consul:latest", - ImageConsulDataplane: "hashicorp/consul-k8s:latest", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - EnvoyExtraArgs: tc.envoyExtraArgs, - } - - c, err := h.consulDataplaneSidecar(testNS, *tc.pod) - require.NoError(t, err) - require.Contains(t, strings.Join(c.Args, " "), tc.expectedExtraArgs) - }) - } -} - -func TestHandlerConsulDataplaneSidecar_UserVolumeMounts(t *testing.T) { - cases := []struct { - name string - pod corev1.Pod - expectedContainerVolumeMounts []corev1.VolumeMount - expErr string - }{ - { - name: "able to set a sidecar container volume mount via annotation", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - constants.AnnotationConsulSidecarUserVolumeMount: "[{\"name\": \"tls-cert\", \"mountPath\": \"/custom/path\"}, {\"name\": \"tls-ca\", \"mountPath\": \"/custom/path2\"}]", - }, - }, - }, - expectedContainerVolumeMounts: []corev1.VolumeMount{ - { - Name: "consul-mesh-inject-data", - MountPath: "/consul/mesh-inject", - }, - { - Name: "tls-cert", - MountPath: "/custom/path", - }, - { - Name: "tls-ca", - MountPath: "/custom/path2", - }, - }, - }, - { - name: "invalid annotation results in error", - pod: corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationEnvoyExtraArgs: "--log-level debug --admin-address-path \"/tmp/consul/foo bar\"", - constants.AnnotationConsulSidecarUserVolumeMount: "[abcdefg]", - }, - }, - }, - expErr: "invalid character 'a' looking ", - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - h := MeshWebhook{ - ImageConsul: "hashicorp/consul:latest", - ImageConsulDataplane: "hashicorp/consul-k8s:latest", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - } - c, err := h.consulDataplaneSidecar(testNS, tc.pod) - if tc.expErr == "" { - require.NoError(t, err) - require.Equal(t, tc.expectedContainerVolumeMounts, c.VolumeMounts) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), tc.expErr) - } - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Resources(t *testing.T) { - mem1 := resource.MustParse("100Mi") - mem2 := resource.MustParse("200Mi") - cpu1 := resource.MustParse("100m") - cpu2 := resource.MustParse("200m") - zero := resource.MustParse("0") - - cases := map[string]struct { - webhook MeshWebhook - annotations map[string]string - expResources corev1.ResourceRequirements - expErr string - }{ - "no defaults, no annotations": { - webhook: MeshWebhook{}, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{}, - Requests: corev1.ResourceList{}, - }, - }, - "all defaults, no annotations": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: cpu1, - DefaultProxyCPULimit: cpu2, - DefaultProxyMemoryRequest: mem1, - DefaultProxyMemoryLimit: mem2, - }, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "no defaults, all annotations": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "100m", - constants.AnnotationSidecarProxyMemoryRequest: "100Mi", - constants.AnnotationSidecarProxyCPULimit: "200m", - constants.AnnotationSidecarProxyMemoryLimit: "200Mi", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "annotations override defaults": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: zero, - DefaultProxyCPULimit: zero, - DefaultProxyMemoryRequest: zero, - DefaultProxyMemoryLimit: zero, - }, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "100m", - constants.AnnotationSidecarProxyMemoryRequest: "100Mi", - constants.AnnotationSidecarProxyCPULimit: "200m", - constants.AnnotationSidecarProxyMemoryLimit: "200Mi", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: cpu2, - corev1.ResourceMemory: mem2, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: cpu1, - corev1.ResourceMemory: mem1, - }, - }, - }, - "defaults set to zero, no annotations": { - webhook: MeshWebhook{ - DefaultProxyCPURequest: zero, - DefaultProxyCPULimit: zero, - DefaultProxyMemoryRequest: zero, - DefaultProxyMemoryLimit: zero, - }, - annotations: nil, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - }, - }, - "annotations set to 0": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "0", - constants.AnnotationSidecarProxyMemoryRequest: "0", - constants.AnnotationSidecarProxyCPULimit: "0", - constants.AnnotationSidecarProxyMemoryLimit: "0", - }, - expResources: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: zero, - corev1.ResourceMemory: zero, - }, - }, - }, - "invalid cpu request": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPURequest: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-request:\"invalid\": quantities must match the regular expression", - }, - "invalid cpu limit": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyCPULimit: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-cpu-limit:\"invalid\": quantities must match the regular expression", - }, - "invalid memory request": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyMemoryRequest: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-request:\"invalid\": quantities must match the regular expression", - }, - "invalid memory limit": { - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationSidecarProxyMemoryLimit: "invalid", - }, - expErr: "parsing annotation consul.hashicorp.com/sidecar-proxy-memory-limit:\"invalid\": quantities must match the regular expression", - }, - } - - for name, c := range cases { - t.Run(name, func(tt *testing.T) { - c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - require := require.New(tt) - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := c.webhook.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.NotNil(err) - require.Contains(err.Error(), c.expErr) - } else { - require.NoError(err) - require.Equal(c.expResources, container.Resources) - } - }) - } -} - -func TestHandlerConsulDataplaneSidecar_Lifecycle(t *testing.T) { - gracefulShutdownSeconds := 10 - gracefulPort := "20307" - gracefulShutdownPath := "/exit" - - cases := []struct { - name string - webhook MeshWebhook - annotations map[string]string - expCmdArgs string - expErr string - }{ - { - name: "no defaults, no annotations", - webhook: MeshWebhook{}, - annotations: nil, - expCmdArgs: "", - }, - { - name: "all defaults, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: nil, - expCmdArgs: "graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", - }, - { - name: "no defaults, all annotations", - webhook: MeshWebhook{}, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "true", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds), - constants.AnnotationSidecarProxyLifecycleGracefulPort: gracefulPort, - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: gracefulShutdownPath, - }, - expCmdArgs: "-graceful-port=20307 -shutdown-drain-listeners -shutdown-grace-period-seconds=10 -graceful-shutdown-path=/exit", - }, - { - name: "annotations override defaults", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "true", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: fmt.Sprint(gracefulShutdownSeconds + 5), - constants.AnnotationSidecarProxyLifecycleGracefulPort: "20317", - constants.AnnotationSidecarProxyLifecycleGracefulShutdownPath: "/foo", - }, - expCmdArgs: "-graceful-port=20317 -shutdown-grace-period-seconds=15 -graceful-shutdown-path=/foo", - }, - { - name: "lifecycle disabled, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: nil, - expCmdArgs: "-graceful-port=20307", - }, - { - name: "lifecycle enabled, defaults omited, no annotations", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - }, - }, - annotations: nil, - expCmdArgs: "", - }, - { - name: "annotations disable lifecycle default", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: true, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - DefaultGracefulPort: gracefulPort, - DefaultGracefulShutdownPath: gracefulShutdownPath, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "false", - }, - expCmdArgs: "-graceful-port=20307", - }, - { - name: "annotations skip graceful shutdown", - webhook: MeshWebhook{ - LifecycleConfig: lifecycle.Config{ - DefaultEnableProxyLifecycle: false, - DefaultEnableShutdownDrainListeners: true, - DefaultShutdownGracePeriodSeconds: gracefulShutdownSeconds, - }, - }, - annotations: map[string]string{ - constants.AnnotationEnableSidecarProxyLifecycle: "false", - constants.AnnotationEnableSidecarProxyLifecycleShutdownDrainListeners: "false", - constants.AnnotationSidecarProxyLifecycleShutdownGracePeriodSeconds: "0", - }, - expCmdArgs: "", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - c.webhook.ConsulConfig = &consul.Config{HTTPPort: 8500, GRPCPort: 8502} - require := require.New(t) - pod := corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: c.annotations, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := c.webhook.consulDataplaneSidecar(testNS, pod) - if c.expErr != "" { - require.NotNil(err) - require.Contains(err.Error(), c.expErr) - } else { - require.NoError(err) - require.Contains(strings.Join(container.Args, " "), c.expCmdArgs) - } - }) - } -} - -// boolPtr returns pointer to b. -func boolPtr(b bool) *bool { - return &b -} diff --git a/control-plane/connect-inject/webhookv2/container_env.go b/control-plane/connect-inject/webhookv2/container_env.go deleted file mode 100644 index b612b3c6aa..0000000000 --- a/control-plane/connect-inject/webhookv2/container_env.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - "strings" - - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" -) - -func (w *MeshWebhook) containerEnvVars(pod corev1.Pod) ([]corev1.EnvVar, error) { - destinations, err := common.ProcessPodDestinationsForMeshWebhook(pod) - if err != nil { - return nil, fmt.Errorf("error processing the destination for container environment variable creation: %s", err.Error()) - } - if destinations == nil { - return nil, nil - } - - var result []corev1.EnvVar - for _, destination := range destinations.Destinations { - serviceName := strings.TrimSpace(destination.DestinationRef.Name) - serviceName = strings.ToUpper(strings.Replace(serviceName, "-", "_", -1)) - portName := strings.TrimSpace(destination.DestinationPort) - portName = strings.ToUpper(strings.Replace(portName, "-", "_", -1)) - - result = append(result, corev1.EnvVar{ - Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_HOST", serviceName, portName), - Value: destination.GetIpPort().Ip, - }, corev1.EnvVar{ - Name: fmt.Sprintf("%s_%s_CONNECT_SERVICE_PORT", serviceName, portName), - Value: strconv.Itoa(int(destination.GetIpPort().Port)), - }) - } - - return result, nil -} diff --git a/control-plane/connect-inject/webhookv2/container_env_test.go b/control-plane/connect-inject/webhookv2/container_env_test.go deleted file mode 100644 index 01f5b1f82e..0000000000 --- a/control-plane/connect-inject/webhookv2/container_env_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func TestContainerEnvVars(t *testing.T) { - cases := []struct { - Name string - Upstream string - ExpectError bool - }{ - { - // TODO: This will not error out when dcs are supported - Name: "Upstream with datacenter", - Upstream: "myPort.static-server:7890:dc1", - ExpectError: true, - }, - { - Name: "Upstream without datacenter", - Upstream: "myPort.static-server:7890", - }, - { - // TODO: This will not error out when dcs are supported - Name: "Upstream with labels and datacenter", - Upstream: "myPort.port.static-server.svc.dc1.dc:7890", - ExpectError: true, - }, - { - Name: "Upstream with labels and no datacenter", - Upstream: "myPort.port.static-server.svc:7890", - }, - { - Name: "Error expected, wrong order", - Upstream: "static-server.svc.myPort.port:7890", - ExpectError: true, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - var w MeshWebhook - envVars, err := w.containerEnvVars(corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - constants.AnnotationMeshDestinations: tt.Upstream, - }, - }, - }) - - if !tt.ExpectError { - require.NoError(err) - require.ElementsMatch(envVars, []corev1.EnvVar{ - { - Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_HOST", - Value: "127.0.0.1", - }, { - Name: "STATIC_SERVER_MYPORT_CONNECT_SERVICE_PORT", - Value: "7890", - }, - }) - } else { - require.Error(err) - } - }) - } -} diff --git a/control-plane/connect-inject/webhookv2/container_init.go b/control-plane/connect-inject/webhookv2/container_init.go deleted file mode 100644 index dcd486660f..0000000000 --- a/control-plane/connect-inject/webhookv2/container_init.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "bytes" - "strconv" - "strings" - "text/template" - - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -const ( - injectInitContainerName = "consul-mesh-init" - rootUserAndGroupID = 0 - sidecarUserAndGroupID = 5995 - initContainersUserAndGroupID = 5996 - netAdminCapability = "NET_ADMIN" -) - -type initContainerCommandData struct { - ServiceName string - ServiceAccountName string - AuthMethod string - - // Log settings for the mesh-init command. - LogLevel string - LogJSON bool -} - -// containerInit returns the init container spec for mesh-init that polls for the workload's bootstrap config -// so that it optionally set up iptables for transparent proxy. Otherwise, it ensures the workload exists before -// the pod starts. -func (w *MeshWebhook) containerInit(namespace corev1.Namespace, pod corev1.Pod) (corev1.Container, error) { - // Check if tproxy is enabled on this pod. - tproxyEnabled, err := common.TransparentProxyEnabled(namespace, pod, w.EnableTransparentProxy) - if err != nil { - return corev1.Container{}, err - } - - data := initContainerCommandData{ - AuthMethod: w.AuthMethod, - LogLevel: w.LogLevel, - LogJSON: w.LogJSON, - } - - // Create expected volume mounts - volMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: "/consul/mesh-inject", - }, - } - - data.ServiceName = pod.Annotations[constants.AnnotationService] - var bearerTokenFile string - if w.AuthMethod != "" { - data.ServiceAccountName = pod.Spec.ServiceAccountName - // Extract the service account token's volume mount - var saTokenVolumeMount corev1.VolumeMount - saTokenVolumeMount, bearerTokenFile, err = findServiceAccountVolumeMount(pod) - if err != nil { - return corev1.Container{}, err - } - - // Append to volume mounts - volMounts = append(volMounts, saTokenVolumeMount) - } - - // Render the command - var buf bytes.Buffer - tpl := template.Must(template.New("root").Parse(strings.TrimSpace( - initContainerCommandTpl))) - err = tpl.Execute(&buf, &data) - if err != nil { - return corev1.Container{}, err - } - - initContainerName := injectInitContainerName - container := corev1.Container{ - Name: initContainerName, - Image: w.ImageConsulK8S, - Env: []corev1.EnvVar{ - { - Name: "POD_NAME", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}, - }, - }, - { - Name: "POD_NAMESPACE", - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}, - }, - }, - { - Name: "CONSUL_ADDRESSES", - Value: w.ConsulAddress, - }, - { - Name: "CONSUL_GRPC_PORT", - Value: strconv.Itoa(w.ConsulConfig.GRPCPort), - }, - { - Name: "CONSUL_HTTP_PORT", - Value: strconv.Itoa(w.ConsulConfig.HTTPPort), - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: w.ConsulConfig.APITimeout.String(), - }, - }, - Resources: w.InitContainerResources, - VolumeMounts: volMounts, - Command: []string{"/bin/sh", "-ec", buf.String()}, - } - - if w.TLSEnabled { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_USE_TLS", - Value: "true", - }, - corev1.EnvVar{ - Name: "CONSUL_CACERT_PEM", - Value: w.ConsulCACert, - }, - corev1.EnvVar{ - Name: "CONSUL_TLS_SERVER_NAME", - Value: w.ConsulTLSServerName, - }) - } - - if w.AuthMethod != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: w.AuthMethod, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: bearerTokenFile, - }, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }) - - if w.EnableNamespaces { - if w.EnableK8SNSMirroring { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "default", - }) - } else { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_NAMESPACE", - Value: w.consulNamespace(namespace.Name), - }) - } - } - - if w.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_LOGIN_PARTITION", - Value: w.ConsulPartition, - }) - } - } - if w.EnableNamespaces { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_NAMESPACE", - Value: w.consulNamespace(namespace.Name), - }) - } - - if w.ConsulPartition != "" { - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_PARTITION", - Value: w.ConsulPartition, - }) - } - - // OpenShift without CNI is the only environment where privileged must be true. - privileged := false - if w.EnableOpenShift && !w.EnableCNI { - privileged = true - } - - if tproxyEnabled { - if !w.EnableCNI { - // Set redirect traffic config for the container so that we can apply iptables rules. - redirectTrafficConfig, err := w.iptablesConfigJSON(pod, namespace) - if err != nil { - return corev1.Container{}, err - } - container.Env = append(container.Env, - corev1.EnvVar{ - Name: "CONSUL_REDIRECT_TRAFFIC_CONFIG", - Value: redirectTrafficConfig, - }) - - // Running consul mesh-init redirect-traffic with iptables - // requires both being a root user and having NET_ADMIN capability. - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(rootUserAndGroupID), - RunAsGroup: pointer.Int64(rootUserAndGroupID), - // RunAsNonRoot overrides any setting in the Pod so that we can still run as root here as required. - RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netAdminCapability}, - }, - } - } else { - container.SecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - } - } - } - - return container, nil -} - -// consulDNSEnabled returns true if Consul DNS should be enabled for this pod. -// It returns an error when the annotation value cannot be parsed by strconv.ParseBool or if we are unable -// to read the pod's namespace label when it exists. -func consulDNSEnabled(namespace corev1.Namespace, pod corev1.Pod, globalDNSEnabled bool, globalTProxyEnabled bool) (bool, error) { - // DNS is only possible when tproxy is also enabled because it relies - // on traffic being redirected. - tproxy, err := common.TransparentProxyEnabled(namespace, pod, globalTProxyEnabled) - if err != nil { - return false, err - } - if !tproxy { - return false, nil - } - - // First check to see if the pod annotation exists to override the namespace or global settings. - if raw, ok := pod.Annotations[constants.KeyConsulDNS]; ok { - return strconv.ParseBool(raw) - } - // Next see if the namespace has been defaulted. - if raw, ok := namespace.Labels[constants.KeyConsulDNS]; ok { - return strconv.ParseBool(raw) - } - // Else fall back to the global default. - return globalDNSEnabled, nil -} - -// splitCommaSeparatedItemsFromAnnotation takes an annotation and a pod -// and returns the comma-separated value of the annotation as a list of strings. -func splitCommaSeparatedItemsFromAnnotation(annotation string, pod corev1.Pod) []string { - var items []string - if raw, ok := pod.Annotations[annotation]; ok { - items = append(items, strings.Split(raw, ",")...) - } - - return items -} - -// initContainerCommandTpl is the template for the command executed by -// the init container. -const initContainerCommandTpl = ` -consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level={{ .LogLevel }} \ - -log-json={{ .LogJSON }} \ -` diff --git a/control-plane/connect-inject/webhookv2/container_init_test.go b/control-plane/connect-inject/webhookv2/container_init_test.go deleted file mode 100644 index 33189f0d0c..0000000000 --- a/control-plane/connect-inject/webhookv2/container_init_test.go +++ /dev/null @@ -1,806 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -const k8sNamespace = "k8snamespace" - -func TestHandlerContainerInit(t *testing.T) { - minimal := func() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-pod", - Namespace: "test-namespace", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - Status: corev1.PodStatus{ - HostIP: "1.1.1.1", - PodIP: "2.2.2.2", - }, - } - } - - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - Webhook MeshWebhook - ExpCmd string // Strings.Contains test - ExpEnv []corev1.EnvVar - }{ - { - "default cmd and env", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502}, - LogLevel: "info", - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "0s", - }, - }, - }, - - { - "with auth method", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - pod.Spec.ServiceAccountName = "a-service-account-name" - pod.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ - { - Name: "sa", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - } - return pod - }, - MeshWebhook{ - AuthMethod: "an-auth-method", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - LogLevel: "debug", - LogJSON: true, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=debug \ - -log-json=true \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "an-auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - w := tt.Webhook - pod := *tt.Pod(minimal()) - container, err := w.containerInit(testNS, pod) - require.NoError(t, err) - actual := strings.Join(container.Command, " ") - require.Contains(t, actual, tt.ExpCmd) - require.EqualValues(t, container.Env[2:], tt.ExpEnv) - }) - } -} - -func TestHandlerContainerInit_transparentProxy(t *testing.T) { - cases := map[string]struct { - globalEnabled bool - cniEnabled bool - annotations map[string]string - expTproxyEnabled bool - namespaceLabel map[string]string - openShiftEnabled bool - }{ - "enabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { - true, - false, - nil, - true, - nil, - false, - }, - "enabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { - true, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - nil, - false, - }, - "enabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { - true, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - true, - nil, - false, - }, - "disabled globally, ns not set, annotation not provided, cni disabled, openshift disabled": { - false, - false, - nil, - false, - nil, - false, - }, - "disabled globally, ns not set, annotation is false, cni disabled, openshift disabled": { - false, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - nil, - false, - }, - "disabled globally, ns not set, annotation is true, cni disabled, openshift disabled": { - false, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - true, - nil, - false, - }, - "disabled globally, ns enabled, annotation not set, cni disabled, openshift disabled": { - false, - false, - nil, - true, - map[string]string{constants.KeyTransparentProxy: "true"}, - false, - }, - "enabled globally, ns disabled, annotation not set, cni disabled, openshift disabled": { - true, - false, - nil, - false, - map[string]string{constants.KeyTransparentProxy: "false"}, - false, - }, - "disabled globally, ns enabled, annotation not set, cni enabled, openshift disabled": { - false, - true, - nil, - false, - map[string]string{constants.KeyTransparentProxy: "true"}, - false, - }, - - "enabled globally, ns not set, annotation not set, cni enabled, openshift disabled": { - true, - true, - nil, - false, - nil, - false, - }, - "enabled globally, ns not set, annotation not set, cni enabled, openshift enabled": { - true, - true, - nil, - false, - nil, - true, - }, - "enabled globally, ns not set, annotation not set, cni disabled, openshift enabled": { - true, - false, - nil, - true, - nil, - true, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableTransparentProxy: c.globalEnabled, - EnableCNI: c.cniEnabled, - ConsulConfig: &consul.Config{HTTPPort: 8500}, - EnableOpenShift: c.openShiftEnabled, - } - pod := minimal() - pod.Annotations = c.annotations - - privileged := false - if c.openShiftEnabled && !c.cniEnabled { - privileged = true - } - - var expectedSecurityContext *corev1.SecurityContext - if c.cniEnabled { - expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(initContainersUserAndGroupID), - RunAsGroup: pointer.Int64(initContainersUserAndGroupID), - RunAsNonRoot: pointer.Bool(true), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{"ALL"}, - }, - } - } else if c.expTproxyEnabled { - expectedSecurityContext = &corev1.SecurityContext{ - RunAsUser: pointer.Int64(0), - RunAsGroup: pointer.Int64(0), - RunAsNonRoot: pointer.Bool(false), - Privileged: pointer.Bool(privileged), - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{netAdminCapability}, - }, - } - } - ns := testNS - ns.Labels = c.namespaceLabel - container, err := w.containerInit(ns, *pod) - require.NoError(t, err) - - redirectTrafficEnvVarFound := false - for _, ev := range container.Env { - if ev.Name == "CONSUL_REDIRECT_TRAFFIC_CONFIG" { - redirectTrafficEnvVarFound = true - break - } - } - - require.Equal(t, c.expTproxyEnabled, redirectTrafficEnvVarFound) - require.Equal(t, expectedSecurityContext, container.SecurityContext) - }) - } -} - -func TestHandlerContainerInit_namespacesAndPartitionsEnabled(t *testing.T) { - minimal := func() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - { - Name: "auth-method-secret", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "service-account-secret", - MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", - }, - }, - }, - }, - ServiceAccountName: "web", - }, - } - } - - cases := []struct { - Name string - Pod func(*corev1.Pod) *corev1.Pod - Webhook MeshWebhook - Cmd string - ExpEnv []corev1.EnvVar - }{ - { - "default namespace, no partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - ConsulPartition: "", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "default", - }, - }, - }, - { - "default namespace, default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "default", - ConsulPartition: "default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "default", - }, - { - Name: "CONSUL_PARTITION", - Value: "default", - }, - }, - }, - { - "non-default namespace, no partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - }, - }, - { - "non-default namespace, non-default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "web" - return pod - }, - MeshWebhook{ - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "non-default-part", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_PARTITION", - Value: "non-default-part", - }, - }, - }, - { - "auth method, non-default namespace, mirroring disabled, default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "" - return pod - }, - MeshWebhook{ - AuthMethod: "auth-method", - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", - ConsulPartition: "default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - { - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_LOGIN_PARTITION", - Value: "default", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "non-default", - }, - { - Name: "CONSUL_PARTITION", - Value: "default", - }, - }, - }, - { - "auth method, non-default namespace, mirroring enabled, non-default partition", - func(pod *corev1.Pod) *corev1.Pod { - pod.Annotations[constants.AnnotationService] = "" - return pod - }, - MeshWebhook{ - AuthMethod: "auth-method", - EnableNamespaces: true, - ConsulDestinationNamespace: "non-default", // Overridden by mirroring - EnableK8SNSMirroring: true, - ConsulPartition: "non-default", - ConsulAddress: "10.0.0.0", - ConsulConfig: &consul.Config{HTTPPort: 8500, GRPCPort: 8502, APITimeout: 5 * time.Second}, - }, - `/bin/sh -ec consul-k8s-control-plane mesh-init -proxy-name=${POD_NAME} \ - -log-level=info \ - -log-json=false \`, - []corev1.EnvVar{ - { - Name: "CONSUL_ADDRESSES", - Value: "10.0.0.0", - }, - { - Name: "CONSUL_GRPC_PORT", - Value: "8502", - }, - { - Name: "CONSUL_HTTP_PORT", - Value: "8500", - }, - { - Name: "CONSUL_API_TIMEOUT", - Value: "5s", - }, - { - Name: "CONSUL_LOGIN_AUTH_METHOD", - Value: "auth-method", - }, - { - Name: "CONSUL_LOGIN_BEARER_TOKEN_FILE", - Value: "/var/run/secrets/kubernetes.io/serviceaccount/token", - }, - { - Name: "CONSUL_LOGIN_META", - Value: "pod=$(POD_NAMESPACE)/$(POD_NAME)", - }, - { - Name: "CONSUL_LOGIN_NAMESPACE", - Value: "default", - }, - { - Name: "CONSUL_LOGIN_PARTITION", - Value: "non-default", - }, - { - Name: "CONSUL_NAMESPACE", - Value: "k8snamespace", - }, - { - Name: "CONSUL_PARTITION", - Value: "non-default", - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - h := tt.Webhook - h.LogLevel = "info" - container, err := h.containerInit(testNS, *tt.Pod(minimal())) - require.NoError(t, err) - actual := strings.Join(container.Command, " ") - require.Equal(t, tt.Cmd, actual) - if tt.ExpEnv != nil { - require.Equal(t, tt.ExpEnv, container.Env[2:]) - } - }) - } -} - -// If TLSEnabled is set, -// Consul addresses should use HTTPS -// and CA cert should be set as env variable if provided. -// Additionally, test that the init container is correctly configured -// when http or gRPC ports are different from defaults. -func TestHandlerContainerInit_WithTLSAndCustomPorts(t *testing.T) { - for _, caProvided := range []bool{true, false} { - name := fmt.Sprintf("ca provided: %t", caProvided) - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - ConsulAddress: "10.0.0.0", - TLSEnabled: true, - ConsulConfig: &consul.Config{HTTPPort: 443, GRPCPort: 8503}, - } - if caProvided { - w.ConsulCACert = "consul-ca-cert" - } - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := w.containerInit(testNS, *pod) - require.NoError(t, err) - require.Equal(t, "CONSUL_ADDRESSES", container.Env[2].Name) - require.Equal(t, w.ConsulAddress, container.Env[2].Value) - require.Equal(t, "CONSUL_GRPC_PORT", container.Env[3].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.GRPCPort), container.Env[3].Value) - require.Equal(t, "CONSUL_HTTP_PORT", container.Env[4].Name) - require.Equal(t, fmt.Sprintf("%d", w.ConsulConfig.HTTPPort), container.Env[4].Value) - if w.TLSEnabled { - require.Equal(t, "CONSUL_USE_TLS", container.Env[6].Name) - require.Equal(t, "true", container.Env[6].Value) - if caProvided { - require.Equal(t, "CONSUL_CACERT_PEM", container.Env[7].Name) - require.Equal(t, "consul-ca-cert", container.Env[7].Value) - } else { - for _, ev := range container.Env { - if ev.Name == "CONSUL_CACERT_PEM" { - require.Empty(t, ev.Value) - } - } - } - } - - }) - } -} - -func TestHandlerContainerInit_Resources(t *testing.T) { - w := MeshWebhook{ - InitContainerResources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("10m"), - corev1.ResourceMemory: resource.MustParse("10Mi"), - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("20m"), - corev1.ResourceMemory: resource.MustParse("25Mi"), - }, - }, - ConsulConfig: &consul.Config{HTTPPort: 8500, APITimeout: 5 * time.Second}, - } - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - container, err := w.containerInit(testNS, *pod) - require.NoError(t, err) - require.Equal(t, corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("20m"), - corev1.ResourceMemory: resource.MustParse("25Mi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("10m"), - corev1.ResourceMemory: resource.MustParse("10Mi"), - }, - }, container.Resources) -} - -var testNS = corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: k8sNamespace, - Labels: map[string]string{}, - }, -} - -func minimal() *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespaces.DefaultNamespace, - Name: "minimal", - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - } -} diff --git a/control-plane/connect-inject/webhookv2/container_volume.go b/control-plane/connect-inject/webhookv2/container_volume.go deleted file mode 100644 index a05a6720db..0000000000 --- a/control-plane/connect-inject/webhookv2/container_volume.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - corev1 "k8s.io/api/core/v1" -) - -// volumeName is the name of the volume that is created to store the -// Consul Connect injection data. -const volumeName = "consul-mesh-inject-data" - -// containerVolume returns the volume data to add to the pod. This volume -// is used for shared data between containers. -func (w *MeshWebhook) containerVolume() corev1.Volume { - return corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{Medium: corev1.StorageMediumMemory}, - }, - } -} diff --git a/control-plane/connect-inject/webhookv2/dns.go b/control-plane/connect-inject/webhookv2/dns.go deleted file mode 100644 index 883c9ed034..0000000000 --- a/control-plane/connect-inject/webhookv2/dns.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "fmt" - "strconv" - - "github.com/miekg/dns" - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" -) - -const ( - // These defaults are taken from the /etc/resolv.conf man page - // and are used by the dns library. - defaultDNSOptionNdots = 1 - defaultDNSOptionTimeout = 5 - defaultDNSOptionAttempts = 2 - - // defaultEtcResolvConfFile is the default location of the /etc/resolv.conf file. - defaultEtcResolvConfFile = "/etc/resolv.conf" -) - -func (w *MeshWebhook) configureDNS(pod *corev1.Pod, k8sNS string) error { - // First, we need to determine the nameservers configured in this cluster from /etc/resolv.conf. - etcResolvConf := defaultEtcResolvConfFile - if w.etcResolvFile != "" { - etcResolvConf = w.etcResolvFile - } - cfg, err := dns.ClientConfigFromFile(etcResolvConf) - if err != nil { - return err - } - - // Set DNS policy on the pod to None because we want DNS to work according to the config we will provide. - pod.Spec.DNSPolicy = corev1.DNSNone - - // Set the consul-dataplane's DNS server as the first server in the list (i.e. localhost). - // We want to do that so that when consul cannot resolve the record, we will fall back to the nameservers - // configured in our /etc/resolv.conf. It's important to add Consul DNS as the first nameserver because - // if we put kube DNS first, it will return NXDOMAIN response and a DNS client will not fall back to other nameservers. - if pod.Spec.DNSConfig == nil { - nameservers := []string{consulDataplaneDNSBindHost} - nameservers = append(nameservers, cfg.Servers...) - var options []corev1.PodDNSConfigOption - if cfg.Ndots != defaultDNSOptionNdots { - ndots := strconv.Itoa(cfg.Ndots) - options = append(options, corev1.PodDNSConfigOption{ - Name: "ndots", - Value: &ndots, - }) - } - if cfg.Timeout != defaultDNSOptionTimeout { - options = append(options, corev1.PodDNSConfigOption{ - Name: "timeout", - Value: pointer.String(strconv.Itoa(cfg.Timeout)), - }) - } - if cfg.Attempts != defaultDNSOptionAttempts { - options = append(options, corev1.PodDNSConfigOption{ - Name: "attempts", - Value: pointer.String(strconv.Itoa(cfg.Attempts)), - }) - } - - // Replace release namespace in the searches with the pod namespace. - // This is so that the searches we generate will be for the pod's namespace - // instead of the namespace of the connect-injector. E.g. instead of - // consul.svc.cluster.local it should be .svc.cluster.local. - var searches []string - // Kubernetes will add a search domain for .svc.cluster.local so we can always - // expect it to be there. See https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#namespaces-of-services. - consulReleaseNSSearchDomain := fmt.Sprintf("%s.svc.cluster.local", w.ReleaseNamespace) - for _, search := range cfg.Search { - if search == consulReleaseNSSearchDomain { - searches = append(searches, fmt.Sprintf("%s.svc.cluster.local", k8sNS)) - } else { - searches = append(searches, search) - } - } - - pod.Spec.DNSConfig = &corev1.PodDNSConfig{ - Nameservers: nameservers, - Searches: searches, - Options: options, - } - } else { - return fmt.Errorf("DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") - } - return nil -} diff --git a/control-plane/connect-inject/webhookv2/dns_test.go b/control-plane/connect-inject/webhookv2/dns_test.go deleted file mode 100644 index e7a380b271..0000000000 --- a/control-plane/connect-inject/webhookv2/dns_test.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/pointer" -) - -func TestMeshWebhook_configureDNS(t *testing.T) { - cases := map[string]struct { - etcResolv string - expDNSConfig *corev1.PodDNSConfig - }{ - "empty /etc/resolv.conf file": { - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1"}, - }, - }, - "one nameserver": { - etcResolv: `nameserver 1.1.1.1`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1"}, - }, - }, - "mutiple nameservers, searches, and options": { - etcResolv: ` -nameserver 1.1.1.1 -nameserver 2.2.2.2 -search foo.bar bar.baz -options ndots:5 timeout:6 attempts:3`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, - Searches: []string{"foo.bar", "bar.baz"}, - Options: []corev1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("5"), - }, - { - Name: "timeout", - Value: pointer.String("6"), - }, - { - Name: "attempts", - Value: pointer.String("3"), - }, - }, - }, - }, - "replaces release specific search domains": { - etcResolv: ` -nameserver 1.1.1.1 -nameserver 2.2.2.2 -search consul.svc.cluster.local svc.cluster.local cluster.local -options ndots:5`, - expDNSConfig: &corev1.PodDNSConfig{ - Nameservers: []string{"127.0.0.1", "1.1.1.1", "2.2.2.2"}, - Searches: []string{"default.svc.cluster.local", "svc.cluster.local", "cluster.local"}, - Options: []corev1.PodDNSConfigOption{ - { - Name: "ndots", - Value: pointer.String("5"), - }, - }, - }, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - etcResolvFile, err := os.CreateTemp("", "") - require.NoError(t, err) - t.Cleanup(func() { - _ = os.RemoveAll(etcResolvFile.Name()) - }) - _, err = etcResolvFile.WriteString(c.etcResolv) - require.NoError(t, err) - w := MeshWebhook{ - etcResolvFile: etcResolvFile.Name(), - ReleaseNamespace: "consul", - } - - pod := minimal() - err = w.configureDNS(pod, "default") - require.NoError(t, err) - require.Equal(t, corev1.DNSNone, pod.Spec.DNSPolicy) - require.Equal(t, c.expDNSConfig, pod.Spec.DNSConfig) - }) - } -} - -func TestMeshWebhook_configureDNS_error(t *testing.T) { - w := MeshWebhook{} - - pod := minimal() - pod.Spec.DNSConfig = &corev1.PodDNSConfig{Nameservers: []string{"1.1.1.1"}} - err := w.configureDNS(pod, "default") - require.EqualError(t, err, "DNS redirection to Consul is not supported with an already defined DNSConfig on the pod") -} diff --git a/control-plane/connect-inject/webhookv2/health_checks_test.go b/control-plane/connect-inject/webhookv2/health_checks_test.go deleted file mode 100644 index 82b7cdd99d..0000000000 --- a/control-plane/connect-inject/webhookv2/health_checks_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestReady(t *testing.T) { - - var cases = []struct { - name string - certFileContents *string - keyFileContents *string - expectError bool - }{ - {"Both cert and key files not present.", nil, nil, true}, - {"Cert file not empty and key file missing.", ptrToString("test"), nil, true}, - {"Key file not empty and cert file missing.", nil, ptrToString("test"), true}, - {"Both cert and key files are present and not empty.", ptrToString("test"), ptrToString("test"), false}, - {"Both cert and key files are present but both are empty.", ptrToString(""), ptrToString(""), true}, - {"Both cert and key files are present but key file is empty.", ptrToString("test"), ptrToString(""), true}, - {"Both cert and key files are present but cert file is empty.", ptrToString(""), ptrToString("test"), true}, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - tmpDir, err := os.MkdirTemp("", "") - require.NoError(t, err) - if tt.certFileContents != nil { - err := os.WriteFile(filepath.Join(tmpDir, "tls.crt"), []byte(*tt.certFileContents), 0666) - require.NoError(t, err) - } - if tt.keyFileContents != nil { - err := os.WriteFile(filepath.Join(tmpDir, "tls.key"), []byte(*tt.keyFileContents), 0666) - require.NoError(t, err) - } - rc := ReadinessCheck{tmpDir} - err = rc.Ready(nil) - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} - -func ptrToString(s string) *string { - return &s -} diff --git a/control-plane/connect-inject/webhookv2/heath_checks.go b/control-plane/connect-inject/webhookv2/heath_checks.go deleted file mode 100644 index 6bd11f6efa..0000000000 --- a/control-plane/connect-inject/webhookv2/heath_checks.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "errors" - "net/http" - "os" - "path/filepath" -) - -type ReadinessCheck struct { - CertDir string -} - -func (r ReadinessCheck) Ready(_ *http.Request) error { - certFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.crt")) - if err != nil { - return err - } - keyFile, err := os.ReadFile(filepath.Join(r.CertDir, "tls.key")) - if err != nil { - return err - } - if len(certFile) == 0 || len(keyFile) == 0 { - return errors.New("certificate files have not been loaded") - } - return nil -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook.go b/control-plane/connect-inject/webhookv2/mesh_webhook.go deleted file mode 100644 index 839b3fd4ab..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook.go +++ /dev/null @@ -1,544 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "strconv" - - mapset "github.com/deckarep/golang-set" - "github.com/go-logr/logr" - "golang.org/x/exp/slices" - "gomodules.xyz/jsonpatch/v2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -const ( - sidecarContainer = "consul-dataplane" - - // exposedPathsLivenessPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a liveness probe. - exposedPathsLivenessPortsRangeStart = 20300 - - // exposedPathsReadinessPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a readiness probe. - exposedPathsReadinessPortsRangeStart = 20400 - - // exposedPathsStartupPortsRangeStart is the start of the port range that we will use as - // the ListenerPort for the Expose configuration of the proxy registration for a startup probe. - exposedPathsStartupPortsRangeStart = 20500 -) - -// kubeSystemNamespaces is a set of namespaces that are considered -// "system" level namespaces and are always skipped (never injected). -var kubeSystemNamespaces = mapset.NewSetWith(metav1.NamespaceSystem, metav1.NamespacePublic) - -// MeshWebhook is the HTTP meshWebhook for admission webhooks. -type MeshWebhook struct { - Clientset kubernetes.Interface - - // ConsulClientConfig is the config to create a Consul API client. - ConsulConfig *consul.Config - - // ConsulServerConnMgr is the watcher for the Consul server addresses. - ConsulServerConnMgr consul.ServerConnectionManager - - // ImageConsul is the container image for Consul to use. - // ImageConsulDataplane is the container image for Envoy to use. - // - // Both of these MUST be set. - ImageConsul string - ImageConsulDataplane string - - // ImageConsulK8S is the container image for consul-k8s to use. - // This image is used for the consul-sidecar container. - ImageConsulK8S string - - // Optional: set when you need extra options to be set when running envoy - // See a list of args here: https://www.envoyproxy.io/docs/envoy/latest/operations/cli - EnvoyExtraArgs string - - // RequireAnnotation means that the annotation must be given to inject. - // If this is false, injection is default. - RequireAnnotation bool - - // AuthMethod is the name of the Kubernetes Auth Method to - // use for identity with connectInjection if ACLs are enabled. - AuthMethod string - - // The PEM-encoded CA certificate string - // to use when communicating with Consul clients over HTTPS. - // If not set, will use HTTP. - ConsulCACert string - - // TLSEnabled indicates whether we should use TLS for communicating to Consul. - TLSEnabled bool - - // ConsulAddress is the address of the Consul server. This should be only the - // host (i.e. not including port or protocol). - ConsulAddress string - - // ConsulTLSServerName is the SNI header to use to connect to the Consul servers - // over TLS. - ConsulTLSServerName string - - // ConsulPartition is the name of the Admin Partition that the controller - // is deployed in. It is an enterprise feature requiring Consul Enterprise 1.11+. - // Its value is an empty string if partitions aren't enabled. - ConsulPartition string - - // EnableNamespaces indicates that a user is running Consul Enterprise - // with version 1.7+ which is namespace aware. It enables Consul namespaces, - // with injection into either a single Consul namespace or mirrored from - // k8s namespaces. - EnableNamespaces bool - - // AllowK8sNamespacesSet is a set of k8s namespaces to explicitly allow for - // injection. It supports the special character `*` which indicates that - // all k8s namespaces are eligible unless explicitly denied. This filter - // is applied before checking pod annotations. - AllowK8sNamespacesSet mapset.Set - - // DenyK8sNamespacesSet is a set of k8s namespaces to explicitly deny - // injection and thus service registration with Consul. An empty set - // means that no namespaces are removed from consideration. This filter - // takes precedence over AllowK8sNamespacesSet. - DenyK8sNamespacesSet mapset.Set - - // ConsulDestinationNamespace is the name of the Consul namespace to register all - // injected services into if Consul namespaces are enabled and mirroring - // is disabled. This may be set, but will not be used if mirroring is enabled. - ConsulDestinationNamespace string - - // EnableK8SNSMirroring causes Consul namespaces to be created to match the - // k8s namespace of any service being registered into Consul. Services are - // registered into the Consul namespace that mirrors their k8s namespace. - EnableK8SNSMirroring bool - - // K8SNSMirroringPrefix is an optional prefix that can be added to the Consul - // namespaces created while mirroring. For example, if it is set to "k8s-", - // then the k8s `default` namespace will be mirrored in Consul's - // `k8s-default` namespace. - K8SNSMirroringPrefix string - - // CrossNamespaceACLPolicy is the name of the ACL policy to attach to - // any created Consul namespaces to allow cross namespace service discovery. - // Only necessary if ACLs are enabled. - CrossNamespaceACLPolicy string - - // Default resource settings for sidecar proxies. Some of these - // fields may be empty. - DefaultProxyCPURequest resource.Quantity - DefaultProxyCPULimit resource.Quantity - DefaultProxyMemoryRequest resource.Quantity - DefaultProxyMemoryLimit resource.Quantity - - // LifecycleConfig contains proxy lifecycle management configuration from the inject-connect command and has methods to determine whether - // configuration should come from the default flags or annotations. The meshWebhook uses this to configure container sidecar proxy args. - LifecycleConfig lifecycle.Config - - // Default Envoy concurrency flag, this is the number of worker threads to be used by the proxy. - DefaultEnvoyProxyConcurrency int - - // MetricsConfig contains metrics configuration from the inject-connect command and has methods to determine whether - // configuration should come from the default flags or annotations. The meshWebhook uses this to configure prometheus - // annotations and the merged metrics server. - MetricsConfig metrics.Config - - // Resource settings for init container. All of these fields - // will be populated by the defaults provided in the initial flags. - InitContainerResources corev1.ResourceRequirements - - // Resource settings for Consul sidecar. All of these fields - // will be populated by the defaults provided in the initial flags. - DefaultConsulSidecarResources corev1.ResourceRequirements - - // EnableTransparentProxy enables transparent proxy mode. - // This means that the injected init container will apply traffic redirection rules - // so that all traffic will go through the Envoy proxy. - EnableTransparentProxy bool - - // EnableCNI enables the CNI plugin and prevents the connect-inject init container - // from running the consul redirect-traffic command as the CNI plugin handles traffic - // redirection - EnableCNI bool - - // TProxyOverwriteProbes controls whether the webhook should mutate pod's HTTP probes - // to point them to the Envoy proxy. - TProxyOverwriteProbes bool - - // EnableConsulDNS enables traffic redirection so that DNS requests are directed to Consul - // from mesh services. - EnableConsulDNS bool - - // EnableOpenShift indicates that when tproxy is enabled, the security context for the Envoy and init - // containers should not be added because OpenShift sets a random user for those and will not allow - // those containers to be created otherwise. - EnableOpenShift bool - - // SkipServerWatch prevents consul-dataplane from consuming the server update stream. This is useful - // for situations where Consul servers are behind a load balancer. - SkipServerWatch bool - - // ReleaseNamespace is the Kubernetes namespace where this webhook is running. - ReleaseNamespace string - - // Log - Log logr.Logger - // Log settings for consul-dataplane and connect-init containers. - LogLevel string - LogJSON bool - - decoder *admission.Decoder - // etcResolvFile is only used in tests to stub out /etc/resolv.conf file. - etcResolvFile string -} - -// Handle is the admission.Webhook implementation that actually handles the -// webhook request for admission control. This should be registered or -// served via the controller runtime manager. -func (w *MeshWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { - var pod corev1.Pod - - // Decode the pod from the request - if err := w.decoder.Decode(req, &pod); err != nil { - w.Log.Error(err, "could not unmarshal request to pod") - return admission.Errored(http.StatusBadRequest, err) - } - - // Marshall the contents of the pod that was received. This is compared with the - // marshalled contents of the pod after it has been updated to create the jsonpatch. - origPodJson, err := json.Marshal(pod) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Setup the default annotation values that are used for the container. - // This MUST be done before shouldInject is called since that function - // uses these annotations. - if err := w.defaultAnnotations(&pod, string(origPodJson)); err != nil { - w.Log.Error(err, "error creating default annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating default annotations: %s", err)) - } - - // Check if we should inject, for example we don't inject in the - // system namespaces. - if shouldInject, err := w.shouldInject(pod, req.Namespace); err != nil { - w.Log.Error(err, "error checking if should inject", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error checking if should inject: %s", err)) - } else if !shouldInject { - return admission.Allowed(fmt.Sprintf("%s %s does not require injection", pod.Kind, pod.Name)) - } - - w.Log.Info("received pod", "name", req.Name, "ns", req.Namespace) - - // Add our volume that will be shared by the init container and - // the sidecar for passing data in the pod. - pod.Spec.Volumes = append(pod.Spec.Volumes, w.containerVolume()) - - // Optionally mount data volume to other containers - w.injectVolumeMount(pod) - - // Optionally add any volumes that are to be used by the envoy sidecar. - if _, ok := pod.Annotations[constants.AnnotationConsulSidecarUserVolume]; ok { - var userVolumes []corev1.Volume - err := json.Unmarshal([]byte(pod.Annotations[constants.AnnotationConsulSidecarUserVolume]), &userVolumes) - if err != nil { - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error unmarshalling sidecar user volumes: %s", err)) - } - pod.Spec.Volumes = append(pod.Spec.Volumes, userVolumes...) - } - - // Add the upstream services as environment variables for easy - // service discovery. - containerEnvVars, err := w.containerEnvVars(pod) - if err != nil { - w.Log.Error(err, "error creating the port environment variables based on pod annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error creating the port environment variables based on pod annotations: %s", err)) - } - for i := range pod.Spec.InitContainers { - pod.Spec.InitContainers[i].Env = append(pod.Spec.InitContainers[i].Env, containerEnvVars...) - } - - for i := range pod.Spec.Containers { - pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, containerEnvVars...) - } - - // A user can enable/disable tproxy for an entire namespace via a label. - ns, err := w.Clientset.CoreV1().Namespaces().Get(ctx, req.Namespace, metav1.GetOptions{}) - if err != nil { - w.Log.Error(err, "error fetching namespace metadata for container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error getting namespace metadata for container: %s", err)) - } - - lifecycleEnabled, ok := w.LifecycleConfig.EnableProxyLifecycle(pod) - if ok != nil { - w.Log.Error(err, "unable to get lifecycle enabled status") - } - // Add the init container that registers the service and sets up the Envoy configuration. - initContainer, err := w.containerInit(*ns, pod) - if err != nil { - w.Log.Error(err, "error configuring injection init container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection init container: %s", err)) - } - pod.Spec.InitContainers = append(pod.Spec.InitContainers, initContainer) - - // Add the Envoy sidecar. - envoySidecar, err := w.consulDataplaneSidecar(*ns, pod) - if err != nil { - w.Log.Error(err, "error configuring injection sidecar container", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring injection sidecar container: %s", err)) - } - //Append the Envoy sidecar before the application container only if lifecycle enabled. - - if lifecycleEnabled && ok == nil { - pod.Spec.Containers = append([]corev1.Container{envoySidecar}, pod.Spec.Containers...) - } else { - pod.Spec.Containers = append(pod.Spec.Containers, envoySidecar) - } - - // pod.Annotations has already been initialized by h.defaultAnnotations() - // and does not need to be checked for being a nil value. - pod.Annotations[constants.KeyMeshInjectStatus] = constants.Injected - - tproxyEnabled, err := common.TransparentProxyEnabled(*ns, pod, w.EnableTransparentProxy) - if err != nil { - w.Log.Error(err, "error determining if transparent proxy is enabled", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if transparent proxy is enabled: %s", err)) - } - - // Add an annotation to the pod sets transparent-proxy-status to enabled or disabled. Used by the CNI plugin - // to determine if it should traffic redirect or not. - if tproxyEnabled { - pod.Annotations[constants.KeyTransparentProxyStatus] = constants.Enabled - } - - // If DNS redirection is enabled, we want to configure dns on the pod. - dnsEnabled, err := consulDNSEnabled(*ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - w.Log.Error(err, "error determining if dns redirection is enabled", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error determining if dns redirection is enabled: %s", err)) - } - if dnsEnabled { - if err = w.configureDNS(&pod, req.Namespace); err != nil { - w.Log.Error(err, "error configuring DNS on the pod", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring DNS on the pod: %s", err)) - } - } - - // Add annotations for metrics. - if err = w.prometheusAnnotations(&pod); err != nil { - w.Log.Error(err, "error configuring prometheus annotations", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring prometheus annotations: %s", err)) - } - - if pod.Labels == nil { - pod.Labels = make(map[string]string) - } - pod.Labels[constants.KeyMeshInjectStatus] = constants.Injected - - // Consul-ENT only: Add the Consul destination namespace as an annotation to the pod. - if w.EnableNamespaces { - pod.Annotations[constants.AnnotationConsulNamespace] = w.consulNamespace(req.Namespace) - } - - // Overwrite readiness/liveness probes if needed. - err = w.overwriteProbes(*ns, &pod) - if err != nil { - w.Log.Error(err, "error overwriting readiness or liveness probes", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error overwriting readiness or liveness probes: %s", err)) - } - - // When CNI and tproxy are enabled, we add an annotation to the pod that contains the iptables config so that the CNI - // plugin can apply redirect traffic rules on the pod. - if w.EnableCNI && tproxyEnabled { - if err = w.addRedirectTrafficConfigAnnotation(&pod, *ns); err != nil { - w.Log.Error(err, "error configuring annotation for CNI traffic redirection", "request name", req.Name) - return admission.Errored(http.StatusInternalServerError, fmt.Errorf("error configuring annotation for CNI traffic redirection: %s", err)) - } - } - - // Marshall the pod into JSON after it has the desired envs, annotations, labels, - // sidecars and initContainers appended to it. - updatedPodJson, err := json.Marshal(pod) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Create a patches based on the Pod that was received by the meshWebhook - // and the desired Pod spec. - patches, err := jsonpatch.CreatePatch(origPodJson, updatedPodJson) - if err != nil { - return admission.Errored(http.StatusBadRequest, err) - } - - // Return a Patched response along with the patches we intend on applying to the - // Pod received by the meshWebhook. - return admission.Patched(fmt.Sprintf("valid %s request", pod.Kind), patches...) -} - -// overwriteProbes overwrites readiness/liveness probes of this pod when -// both transparent proxy is enabled and overwrite probes is true for the pod. -func (w *MeshWebhook) overwriteProbes(ns corev1.Namespace, pod *corev1.Pod) error { - tproxyEnabled, err := common.TransparentProxyEnabled(ns, *pod, w.EnableTransparentProxy) - if err != nil { - return err - } - - overwriteProbes, err := common.ShouldOverwriteProbes(*pod, w.TProxyOverwriteProbes) - if err != nil { - return err - } - - if tproxyEnabled && overwriteProbes { - // We don't use the loop index because this needs to line up w.withiptablesConfigJSON, - // which is performed before the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "envoy-sidecar" container from having it's probes overridden - if container.Name == sidecarContainer { - continue - } - if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - container.LivenessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsLivenessPortsRangeStart + idx) - } - if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - container.ReadinessProbe.HTTPGet.Port = intstr.FromInt(exposedPathsReadinessPortsRangeStart + idx) - } - if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - container.StartupProbe.HTTPGet.Port = intstr.FromInt(exposedPathsStartupPortsRangeStart + idx) - } - idx++ - } - } - return nil -} - -func (w *MeshWebhook) injectVolumeMount(pod corev1.Pod) { - containersToInject := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationMeshInjectMountVolumes, pod) - - for index, container := range pod.Spec.Containers { - if slices.Contains(containersToInject, container.Name) { - pod.Spec.Containers[index].VolumeMounts = append(pod.Spec.Containers[index].VolumeMounts, corev1.VolumeMount{ - Name: volumeName, - MountPath: "/consul/mesh-inject", - }) - } - } -} - -func (w *MeshWebhook) shouldInject(pod corev1.Pod, namespace string) (bool, error) { - // Don't inject in the Kubernetes system namespaces - if kubeSystemNamespaces.Contains(namespace) { - return false, nil - } - - // Namespace logic - // If in deny list, don't inject - if w.DenyK8sNamespacesSet.Contains(namespace) { - return false, nil - } - - // If not in allow list or allow list is not *, don't inject - if !w.AllowK8sNamespacesSet.Contains("*") && !w.AllowK8sNamespacesSet.Contains(namespace) { - return false, nil - } - - // If we already injected then don't inject again - if pod.Annotations[constants.KeyMeshInjectStatus] != "" || pod.Annotations[constants.KeyInjectStatus] != "" { - return false, nil - } - - // If the explicit true/false is on, then take that value. Note that - // this has to be the last check since it sets a default value after - // all other checks. - if raw, ok := pod.Annotations[constants.AnnotationMeshInject]; ok { - return strconv.ParseBool(raw) - } - - return !w.RequireAnnotation, nil -} - -func (w *MeshWebhook) defaultAnnotations(pod *corev1.Pod, podJson string) error { - if pod.Annotations == nil { - pod.Annotations = make(map[string]string) - } - - pod.Annotations[constants.AnnotationOriginalPod] = podJson - pod.Annotations[constants.AnnotationConsulK8sVersion] = version.GetHumanVersion() - - return nil -} - -// prometheusAnnotations sets the Prometheus scraping configuration -// annotations on the Pod. -func (w *MeshWebhook) prometheusAnnotations(pod *corev1.Pod) error { - enableMetrics, err := w.MetricsConfig.EnableMetrics(*pod) - if err != nil { - return err - } - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(*pod) - if err != nil { - return err - } - prometheusScrapePath := w.MetricsConfig.PrometheusScrapePath(*pod) - - if enableMetrics { - pod.Annotations[constants.AnnotationPrometheusScrape] = "true" - pod.Annotations[constants.AnnotationPrometheusPort] = prometheusScrapePort - pod.Annotations[constants.AnnotationPrometheusPath] = prometheusScrapePath - } - return nil -} - -// consulNamespace returns the namespace that a service should be -// registered in based on the namespace options. It returns an -// empty string if namespaces aren't enabled. -func (w *MeshWebhook) consulNamespace(ns string) string { - return namespaces.ConsulNamespace(ns, w.EnableNamespaces, w.ConsulDestinationNamespace, w.EnableK8SNSMirroring, w.K8SNSMirroringPrefix) -} - -func findServiceAccountVolumeMount(pod corev1.Pod) (corev1.VolumeMount, string, error) { - // Find the volume mount that is mounted at the known - // service account token location - var volumeMount corev1.VolumeMount - for _, container := range pod.Spec.Containers { - for _, vm := range container.VolumeMounts { - if vm.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" { - volumeMount = vm - break - } - } - } - - // Return an error if volumeMount is still empty - if (corev1.VolumeMount{}) == volumeMount { - return volumeMount, "", errors.New("unable to find service account token volumeMount") - } - - return volumeMount, "/var/run/secrets/kubernetes.io/serviceaccount/token", nil -} - -func (w *MeshWebhook) InjectDecoder(d *admission.Decoder) error { - w.decoder = d - return nil -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go deleted file mode 100644 index 0924a01e6c..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_ent_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package webhookv2 - -import ( - "context" - "testing" - - "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testing" - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admission/v1" - corev1 "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -// Test that the annotation for the Consul namespace is added. -func TestHandler_MutateWithNamespaces_Annotation(t *testing.T) { - t.Parallel() - sourceKubeNS := "kube-ns" - - cases := map[string]struct { - ConsulDestinationNamespace string - Mirroring bool - MirroringPrefix string - ExpNamespaceAnnotation string - }{ - "dest: default": { - ConsulDestinationNamespace: "default", - ExpNamespaceAnnotation: "default", - }, - "dest: foo": { - ConsulDestinationNamespace: "foo", - ExpNamespaceAnnotation: "foo", - }, - "mirroring": { - Mirroring: true, - ExpNamespaceAnnotation: sourceKubeNS, - }, - "mirroring with prefix": { - Mirroring: true, - MirroringPrefix: "prefix-", - ExpNamespaceAnnotation: "prefix-" + sourceKubeNS, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - testClient := test.TestServerWithMockConnMgrWatcher(t, nil) - - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{Group: "", Version: "v1"}, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - require.NoError(t, err) - - webhook := MeshWebhook{ - Log: logrtest.NewTestLogger(t), - AllowK8sNamespacesSet: mapset.NewSet("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableNamespaces: true, - ConsulDestinationNamespace: c.ConsulDestinationNamespace, - EnableK8SNSMirroring: c.Mirroring, - K8SNSMirroringPrefix: c.MirroringPrefix, - ConsulConfig: testClient.Cfg, - ConsulServerConnMgr: testClient.Watcher, - decoder: decoder, - Clientset: clientWithNamespace(sourceKubeNS), - } - - pod := corev1.Pod{ - ObjectMeta: v1.ObjectMeta{ - Namespace: sourceKubeNS, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - } - request := admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &pod), - Namespace: sourceKubeNS, - }, - } - resp := webhook.Handle(context.Background(), request) - require.Equal(t, resp.Allowed, true) - - // Check that the annotation was added as a patch. - var consulNamespaceAnnotationValue string - for _, patch := range resp.Patches { - if patch.Path == "/metadata/annotations" { - for annotationName, annotationValue := range patch.Value.(map[string]interface{}) { - if annotationName == constants.AnnotationConsulNamespace { - consulNamespaceAnnotationValue = annotationValue.(string) - } - } - } - } - require.NotEmpty(t, consulNamespaceAnnotationValue, "no namespace annotation set") - require.Equal(t, c.ExpNamespaceAnnotation, consulNamespaceAnnotationValue) - }) - } -} diff --git a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go b/control-plane/connect-inject/webhookv2/mesh_webhook_test.go deleted file mode 100644 index ee68db5c14..0000000000 --- a/control-plane/connect-inject/webhookv2/mesh_webhook_test.go +++ /dev/null @@ -1,2068 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "context" - "encoding/json" - "strconv" - "strings" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" - "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" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -func TestHandlerHandle(t *testing.T) { - t.Parallel() - basicSpec := corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - } - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "kube-system namespace", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: metav1.NamespaceSystem, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "already injected", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.KeyMeshInjectStatus: constants.Injected, - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "empty pod basic", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - }, - }, - { - "empty pod basic with lifecycle", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - }, - }, - { - "pod with destinations specified", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshDestinations: "myPort1.echo:1234,myPort2.db:1234", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - }, - }, - { - "error pod with incorrect destinations specified", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshDestinations: "db:1234", - }, - }, - Spec: basicSpec, - }), - }, - }, - "error creating the port environment variables based on pod annotations", - []jsonpatch.Operation{}, - }, - { - "empty pod with injection disabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInject: "false", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - nil, - }, - - { - "empty pod with injection truthy", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInject: "t", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - - { - "pod with empty volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInjectMountVolumes: "", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationMeshInjectMountVolumes: "web,unknown,web_three_point_oh", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web_two_point_oh", - }, - { - Name: "web_three_point_oh", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/2/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/3", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with sidecar volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationConsulSidecarUserVolume: "[{\"name\":\"bbb\",\"csi\":{\"driver\":\"bob\"}}]", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - { - "pod with sidecar invalid volume mount annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationConsulSidecarUserVolume: "[a]", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "error unmarshalling sidecar user volumes: invalid character 'a' looking for beginning of value", - nil, - }, - { - "pod with service annotation", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - Spec: basicSpec, - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "foo", - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - }, - }, - - { - "pod with existing label", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "testLabel": "123", - }, - }, - Spec: basicSpec, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/annotations", - }, - { - Operation: "add", - Path: "/metadata/labels/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - }, - }, - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "replace", - Path: "/spec/containers/0/livenessProbe/httpGet/port", - }, - { - Operation: "replace", - Path: "/spec/containers/0/readinessProbe/httpGet/port", - }, - }, - }, - { - "dns redirection enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{constants.KeyConsulDNS: "true"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - { - Operation: "add", - Path: "/spec/dnsPolicy", - }, - { - Operation: "add", - Path: "/spec/dnsConfig", - }, - }, - }, - { - "dns redirection only enabled if tproxy enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{ - constants.KeyConsulDNS: "true", - constants.KeyTransparentProxy: "false", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - // Note: no DNS policy/config additions. - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - actual[i].Value = nil - } - } - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - -// This test validates that overwrite probes match the iptables configuration fromiptablesConfigJSON() -// Because they happen at different points in the injection, the port numbers can get out of sync. -func TestHandlerHandle_ValidateOverwriteProbes(t *testing.T) { - // TODO (v2/nitya): enable when expose paths and L7 are implemented - t.Skip("Tproxy probes are not supported yet") - t.Parallel() - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - - cases := []struct { - Name string - Webhook MeshWebhook - Req admission.Request - Err string // expected error string, not exact - Patches []jsonpatch.Operation - }{ - { - "tproxy with overwriteProbes is enabled", - MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - EnableTransparentProxy: true, - TProxyOverwriteProbes: true, - LifecycleConfig: lifecycle.Config{DefaultEnableProxyLifecycle: true}, - decoder: decoder, - Clientset: defaultTestClientWithNamespace(), - }, - admission.Request{ - AdmissionRequest: admissionv1.AdmissionRequest{ - Namespace: namespaces.DefaultNamespace, - Object: encodeRaw(t, &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - // We're setting an existing annotation so that we can assert on the - // specific annotations that are set as a result of probes being overwritten. - Annotations: map[string]string{"foo": "bar"}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - }, - }), - }, - }, - "", - []jsonpatch.Operation{ - { - Operation: "add", - Path: "/spec/volumes", - }, - { - Operation: "add", - Path: "/spec/initContainers", - }, - { - Operation: "add", - Path: "/spec/containers/1", - }, - { - Operation: "replace", - Path: "/spec/containers/0/name", - }, - { - Operation: "add", - Path: "/spec/containers/0/args", - }, - { - Operation: "add", - Path: "/spec/containers/0/env", - }, - { - Operation: "add", - Path: "/spec/containers/0/volumeMounts", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/tcpSocket", - }, - { - Operation: "add", - Path: "/spec/containers/0/readinessProbe/initialDelaySeconds", - }, - { - Operation: "remove", - Path: "/spec/containers/0/readinessProbe/httpGet", - }, - { - Operation: "add", - Path: "/spec/containers/0/securityContext", - }, - { - Operation: "remove", - Path: "/spec/containers/0/startupProbe", - }, - { - Operation: "remove", - Path: "/spec/containers/0/livenessProbe", - }, - { - Operation: "add", - Path: "/metadata/labels", - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyMeshInjectStatus), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.KeyTransparentProxyStatus), - }, - - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationOriginalPod), - }, - { - Operation: "add", - Path: "/metadata/annotations/" + escapeJSONPointer(constants.AnnotationConsulK8sVersion), - }, - }, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - tt.Webhook.ConsulConfig = &consul.Config{HTTPPort: 8500} - ctx := context.Background() - resp := tt.Webhook.Handle(ctx, tt.Req) - if (tt.Err == "") != resp.Allowed { - t.Fatalf("allowed: %v, expected err: %v", resp.Allowed, tt.Err) - } - if tt.Err != "" { - require.Contains(t, resp.Result.Message, tt.Err) - return - } - - var iptablesCfg iptables.Config - var overwritePorts []string - actual := resp.Patches - if len(actual) > 0 { - for i := range actual { - - // We want to grab the iptables configuration from the connect-init container's - // environment. - if actual[i].Path == "/spec/initContainers" { - value := actual[i].Value.([]any) - valueMap := value[0].(map[string]any) - envs := valueMap["env"].([]any) - redirectEnv := envs[6].(map[string]any) - require.Equal(t, redirectEnv["name"].(string), "CONSUL_REDIRECT_TRAFFIC_CONFIG") - iptablesJson := redirectEnv["value"].(string) - - err := json.Unmarshal([]byte(iptablesJson), &iptablesCfg) - require.NoError(t, err) - } - - // We want to accumulate the httpGet Probes from the application container to - // compare them to the iptables rules. This is now the second container in the spec - if strings.Contains(actual[i].Path, "/spec/containers/1") { - valueMap, ok := actual[i].Value.(map[string]any) - require.True(t, ok) - - for k, v := range valueMap { - if strings.Contains(k, "Probe") { - probe := v.(map[string]any) - httpProbe := probe["httpGet"] - httpProbeMap := httpProbe.(map[string]any) - port := httpProbeMap["port"] - portNum := port.(float64) - - overwritePorts = append(overwritePorts, strconv.Itoa(int(portNum))) - } - } - } - - // nil out all the patch values to just compare the keys changing. - actual[i].Value = nil - } - } - // Make sure the iptables excluded ports match the ports on the container - require.ElementsMatch(t, iptablesCfg.ExcludeInboundPorts, overwritePorts) - require.ElementsMatch(t, tt.Patches, actual) - }) - } -} - -func TestHandlerDefaultAnnotations(t *testing.T) { - cases := []struct { - Name string - Pod *corev1.Pod - Expected map[string]string - Err string - }{ - { - "empty", - &corev1.Pod{}, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":null},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - { - "basic pod, no ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - { - "basic pod, with ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - Name: "http", - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"name\":\"http\",\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - - { - "basic pod, with unnamed ports", - &corev1.Pod{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "web", - Ports: []corev1.ContainerPort{ - { - ContainerPort: 8080, - }, - }, - }, - { - Name: "web-side", - }, - }, - }, - }, - map[string]string{ - constants.AnnotationOriginalPod: "{\"metadata\":{\"creationTimestamp\":null},\"spec\":{\"containers\":[{\"name\":\"web\",\"ports\":[{\"containerPort\":8080}],\"resources\":{}},{\"name\":\"web-side\",\"resources\":{}}]},\"status\":{}}", - constants.AnnotationConsulK8sVersion: version.GetHumanVersion(), - }, - "", - }, - } - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - podJson, err := json.Marshal(tt.Pod) - require.NoError(t, err) - - var w MeshWebhook - err = w.defaultAnnotations(tt.Pod, string(podJson)) - if (tt.Err != "") != (err != nil) { - t.Fatalf("actual: %v, expected err: %v", err, tt.Err) - } - if tt.Err != "" { - require.Contains(t, err.Error(), tt.Err) - return - } - - actual := tt.Pod.Annotations - if len(actual) == 0 { - actual = nil - } - require.Equal(t, tt.Expected, actual) - }) - } -} - -func TestHandlerPrometheusAnnotations(t *testing.T) { - cases := []struct { - Name string - Webhook MeshWebhook - Expected map[string]string - }{ - { - Name: "Sets the correct prometheus annotations on the pod if metrics are enabled", - Webhook: MeshWebhook{ - MetricsConfig: metrics.Config{ - DefaultEnableMetrics: true, - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - }, - }, - Expected: map[string]string{ - constants.AnnotationPrometheusScrape: "true", - constants.AnnotationPrometheusPort: "20200", - constants.AnnotationPrometheusPath: "/metrics", - }, - }, - { - Name: "Does not set annotations if metrics are not enabled", - Webhook: MeshWebhook{ - MetricsConfig: metrics.Config{ - DefaultEnableMetrics: false, - DefaultPrometheusScrapePort: "20200", - DefaultPrometheusScrapePath: "/metrics", - }, - }, - Expected: map[string]string{}, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - h := tt.Webhook - pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}} - - err := h.prometheusAnnotations(pod) - require.NoError(err) - - require.Equal(pod.Annotations, tt.Expected) - }) - } -} - -// Test consulNamespace function. -func TestConsulNamespace(t *testing.T) { - cases := []struct { - Name string - EnableNamespaces bool - ConsulDestinationNamespace string - EnableK8SNSMirroring bool - K8SNSMirroringPrefix string - K8sNamespace string - Expected string - }{ - { - "namespaces disabled", - false, - "default", - false, - "", - "namespace", - "", - }, - - { - "namespaces disabled, mirroring enabled", - false, - "default", - true, - "", - "namespace", - "", - }, - - { - "namespaces disabled, mirroring enabled, prefix defined", - false, - "default", - true, - "test-", - "namespace", - "", - }, - - { - "namespaces enabled, mirroring disabled", - true, - "default", - false, - "", - "namespace", - "default", - }, - - { - "namespaces enabled, mirroring disabled, prefix defined", - true, - "default", - false, - "test-", - "namespace", - "default", - }, - - { - "namespaces enabled, mirroring enabled", - true, - "default", - true, - "", - "namespace", - "namespace", - }, - - { - "namespaces enabled, mirroring enabled, prefix defined", - true, - "default", - true, - "test-", - "namespace", - "test-namespace", - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - - w := MeshWebhook{ - EnableNamespaces: tt.EnableNamespaces, - ConsulDestinationNamespace: tt.ConsulDestinationNamespace, - EnableK8SNSMirroring: tt.EnableK8SNSMirroring, - K8SNSMirroringPrefix: tt.K8SNSMirroringPrefix, - } - - ns := w.consulNamespace(tt.K8sNamespace) - - require.Equal(tt.Expected, ns) - }) - } -} - -// Test shouldInject function. -func TestShouldInject(t *testing.T) { - cases := []struct { - Name string - Pod *corev1.Pod - K8sNamespace string - EnableNamespaces bool - AllowK8sNamespacesSet mapset.Set - DenyK8sNamespacesSet mapset.Set - Expected bool - }{ - { - "kube-system not injected", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - // Service annotation is required for injection - constants.AnnotationService: "testing", - }, - }, - }, - "kube-system", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "kube-public not injected", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "kube-public", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, empty allow/deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, allow *", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("default"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow * and default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*", "default"), - mapset.NewSet(), - true, - }, - { - "namespaces disabled, allow only ns1 and ns2", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("ns1", "ns2"), - mapset.NewSet(), - false, - }, - { - "namespaces disabled, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSet(), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces disabled, allow *, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("*"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces disabled, default ns in both allow and deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - false, - mapset.NewSetWith("default"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, empty allow/deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSet(), - mapset.NewSet(), - false, - }, - { - "namespaces enabled, allow *", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("default"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow * and default", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*", "default"), - mapset.NewSet(), - true, - }, - { - "namespaces enabled, allow only ns1 and ns2", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("ns1", "ns2"), - mapset.NewSet(), - false, - }, - { - "namespaces enabled, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSet(), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, allow *, deny default ns", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("*"), - mapset.NewSetWith("default"), - false, - }, - { - "namespaces enabled, default ns in both allow and deny lists", - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - constants.AnnotationService: "testing", - }, - }, - }, - "default", - true, - mapset.NewSetWith("default"), - mapset.NewSetWith("default"), - false, - }, - } - - for _, tt := range cases { - t.Run(tt.Name, func(t *testing.T) { - require := require.New(t) - - w := MeshWebhook{ - RequireAnnotation: false, - EnableNamespaces: tt.EnableNamespaces, - AllowK8sNamespacesSet: tt.AllowK8sNamespacesSet, - DenyK8sNamespacesSet: tt.DenyK8sNamespacesSet, - } - - injected, err := w.shouldInject(*tt.Pod, tt.K8sNamespace) - - require.Equal(nil, err) - require.Equal(tt.Expected, injected) - }) - } -} - -func TestOverwriteProbes(t *testing.T) { - t.Parallel() - - cases := map[string]struct { - tproxyEnabled bool - overwriteProbes bool - podContainers []corev1.Container - expLivenessPort []int - expReadinessPort []int - expStartupPort []int - additionalAnnotations map[string]string - }{ - "transparent proxy disabled; overwrites probes disabled": { - tproxyEnabled: false, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "transparent proxy enabled; overwrite probes disabled": { - tproxyEnabled: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "transparent proxy disabled; overwrite probes enabled": { - tproxyEnabled: false, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{8080}, - }, - "just the readiness probe": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - }, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, - }, - "just the liveness probe": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, - }, - "skips envoy sidecar": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: sidecarContainer, - }, - }, - }, - "readiness, liveness and startup probes": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart}, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart}, - expStartupPort: []int{exposedPathsStartupPortsRangeStart}, - }, - "readiness, liveness and startup probes multiple containers": { - tproxyEnabled: true, - overwriteProbes: true, - podContainers: []corev1.Container{ - { - Name: "test", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8081, - }, - { - Name: "http", - ContainerPort: 8080, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8081), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8080), - }, - }, - }, - }, - { - Name: "test-2", - Ports: []corev1.ContainerPort{ - { - Name: "tcp", - ContainerPort: 8083, - }, - { - Name: "http", - ContainerPort: 8082, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8083), - }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(8082), - }, - }, - }, - }, - }, - expLivenessPort: []int{exposedPathsLivenessPortsRangeStart, exposedPathsLivenessPortsRangeStart + 1}, - expReadinessPort: []int{exposedPathsReadinessPortsRangeStart, exposedPathsReadinessPortsRangeStart + 1}, - expStartupPort: []int{exposedPathsStartupPortsRangeStart, exposedPathsStartupPortsRangeStart + 1}, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - pod := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{}, - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: c.podContainers, - }, - } - if c.additionalAnnotations != nil { - pod.ObjectMeta.Annotations = c.additionalAnnotations - } - - w := MeshWebhook{ - EnableTransparentProxy: c.tproxyEnabled, - TProxyOverwriteProbes: c.overwriteProbes, - } - err := w.overwriteProbes(corev1.Namespace{}, pod) - require.NoError(t, err) - for i, container := range pod.Spec.Containers { - if container.ReadinessProbe != nil { - require.Equal(t, c.expReadinessPort[i], container.ReadinessProbe.HTTPGet.Port.IntValue()) - } - if container.LivenessProbe != nil { - require.Equal(t, c.expLivenessPort[i], container.LivenessProbe.HTTPGet.Port.IntValue()) - } - if container.StartupProbe != nil { - require.Equal(t, c.expStartupPort[i], container.StartupProbe.HTTPGet.Port.IntValue()) - } - } - }) - } -} - -// encodeRaw is a helper to encode some data into a RawExtension. -func encodeRaw(t *testing.T, input interface{}) runtime.RawExtension { - data, err := json.Marshal(input) - require.NoError(t, err) - return runtime.RawExtension{Raw: data} -} - -// https://tools.ietf.org/html/rfc6901 -func escapeJSONPointer(s string) string { - s = strings.Replace(s, "~", "~0", -1) - s = strings.Replace(s, "/", "~1", -1) - return s -} - -func defaultTestClientWithNamespace() kubernetes.Interface { - return clientWithNamespace("default") -} - -func clientWithNamespace(name string) kubernetes.Interface { - ns := corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - } - return fake.NewSimpleClientset(&ns) -} diff --git a/control-plane/connect-inject/webhookv2/redirect_traffic.go b/control-plane/connect-inject/webhookv2/redirect_traffic.go deleted file mode 100644 index 8432372831..0000000000 --- a/control-plane/connect-inject/webhookv2/redirect_traffic.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - - "github.com/hashicorp/consul/sdk/iptables" - corev1 "k8s.io/api/core/v1" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/common" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -// addRedirectTrafficConfigAnnotation creates an iptables.Config in JSON format based on proxy configuration. -// iptables.Config: -// -// ConsulDNSIP: an environment variable named RESOURCE_PREFIX_DNS_SERVICE_HOST where RESOURCE_PREFIX is the consul.fullname in helm. -// ProxyUserID: a constant set in Annotations -// ProxyInboundPort: the service port or bind port -// ProxyOutboundPort: default transparent proxy outbound port or transparent proxy outbound listener port -// ExcludeInboundPorts: prometheus, envoy stats, expose paths, checks and excluded pod annotations -// ExcludeOutboundPorts: pod annotations -// ExcludeOutboundCIDRs: pod annotations -// ExcludeUIDs: pod annotations -func (w *MeshWebhook) iptablesConfigJSON(pod corev1.Pod, ns corev1.Namespace) (string, error) { - cfg := iptables.Config{ - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - } - - // Set the proxy's inbound port. - cfg.ProxyInboundPort = constants.ProxyDefaultInboundPort - - // Set the proxy's outbound port. - cfg.ProxyOutboundPort = iptables.DefaultTProxyOutboundPort - - // If metrics are enabled, get the prometheusScrapePort and exclude it from the inbound ports - enableMetrics, err := w.MetricsConfig.EnableMetrics(pod) - if err != nil { - return "", err - } - if enableMetrics { - prometheusScrapePort, err := w.MetricsConfig.PrometheusScrapePort(pod) - if err != nil { - return "", err - } - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, prometheusScrapePort) - } - - // Exclude any overwritten liveness/readiness/startup ports from redirection. - overwriteProbes, err := common.ShouldOverwriteProbes(pod, w.TProxyOverwriteProbes) - if err != nil { - return "", err - } - - // Exclude the port on which the proxy health check port will be configured if - // using the proxy health check for a service. - if useProxyHealthCheck(pod) { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(constants.ProxyDefaultHealthPort)) - } - - if overwriteProbes { - // We don't use the loop index because this needs to line up w.overwriteProbes(), - // which is performed after the sidecar is injected. - idx := 0 - for _, container := range pod.Spec.Containers { - // skip the "consul-dataplane" container from having its probes overridden - if container.Name == sidecarContainer { - continue - } - if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsLivenessPortsRangeStart+idx)) - } - if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsReadinessPortsRangeStart+idx)) - } - if container.StartupProbe != nil && container.StartupProbe.HTTPGet != nil { - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, strconv.Itoa(exposedPathsStartupPortsRangeStart+idx)) - } - idx++ - } - } - - // Inbound ports - excludeInboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeInboundPorts, pod) - cfg.ExcludeInboundPorts = append(cfg.ExcludeInboundPorts, excludeInboundPorts...) - - // Outbound ports - excludeOutboundPorts := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundPorts, pod) - cfg.ExcludeOutboundPorts = append(cfg.ExcludeOutboundPorts, excludeOutboundPorts...) - - // Outbound CIDRs - excludeOutboundCIDRs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeOutboundCIDRs, pod) - cfg.ExcludeOutboundCIDRs = append(cfg.ExcludeOutboundCIDRs, excludeOutboundCIDRs...) - - // UIDs - excludeUIDs := splitCommaSeparatedItemsFromAnnotation(constants.AnnotationTProxyExcludeUIDs, pod) - cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, excludeUIDs...) - - // Add init container user ID to exclude from traffic redirection. - cfg.ExcludeUIDs = append(cfg.ExcludeUIDs, strconv.Itoa(initContainersUserAndGroupID)) - - dnsEnabled, err := consulDNSEnabled(ns, pod, w.EnableConsulDNS, w.EnableTransparentProxy) - if err != nil { - return "", err - } - - if dnsEnabled { - // If Consul DNS is enabled, we find the environment variable that has the value - // of the ClusterIP of the Consul DNS Service. constructDNSServiceHostName returns - // the name of the env variable whose value is the ClusterIP of the Consul DNS Service. - cfg.ConsulDNSIP = consulDataplaneDNSBindHost - cfg.ConsulDNSPort = consulDataplaneDNSBindPort - } - - iptablesConfigJson, err := json.Marshal(&cfg) - if err != nil { - return "", fmt.Errorf("could not marshal iptables config: %w", err) - } - - return string(iptablesConfigJson), nil -} - -// addRedirectTrafficConfigAnnotation add the created iptables JSON config as an annotation on the provided pod. -func (w *MeshWebhook) addRedirectTrafficConfigAnnotation(pod *corev1.Pod, ns corev1.Namespace) error { - iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) - if err != nil { - return err - } - - pod.Annotations[constants.AnnotationRedirectTraffic] = iptablesConfig - - return nil -} diff --git a/control-plane/connect-inject/webhookv2/redirect_traffic_test.go b/control-plane/connect-inject/webhookv2/redirect_traffic_test.go deleted file mode 100644 index 62b25722db..0000000000 --- a/control-plane/connect-inject/webhookv2/redirect_traffic_test.go +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookv2 - -import ( - "encoding/json" - "fmt" - "strconv" - "testing" - - mapset "github.com/deckarep/golang-set" - logrtest "github.com/go-logr/logr/testr" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" -) - -const ( - defaultPodName = "fakePod" - defaultNamespace = "default" -) - -func TestAddRedirectTrafficConfig(t *testing.T) { - s := runtime.NewScheme() - s.AddKnownTypes(schema.GroupVersion{ - Group: "", - Version: "v1", - }, &corev1.Pod{}) - decoder, err := admission.NewDecoder(s) - require.NoError(t, err) - cases := []struct { - name string - webhook MeshWebhook - pod *corev1.Pod - namespace corev1.Namespace - dnsEnabled bool - expCfg iptables.Config - expErr error - }{ - { - name: "basic bare minimum pod", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - }, - }, - { - name: "proxy health checks enabled", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationUseProxyHealthCheck: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"21000"}, - }, - }, - { - name: "metrics enabled", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationEnableMetrics: "true", - constants.AnnotationPrometheusScrapePort: "13373", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"13373"}, - }, - }, - { - name: "metrics enabled with incorrect annotation", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationEnableMetrics: "invalid", - constants.AnnotationPrometheusScrapePort: "13373", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"13373"}, - }, - expErr: fmt.Errorf("%s annotation value of %s was invalid: %s", constants.AnnotationEnableMetrics, "invalid", "strconv.ParseBool: parsing \"invalid\": invalid syntax"), - }, - { - name: "overwrite probes, transparent proxy annotation set", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTransparentProxyOverwriteProbes: "true", - constants.KeyTransparentProxy: "true", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.FromInt(exposedPathsLivenessPortsRangeStart), - }, - }, - }, - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{strconv.Itoa(exposedPathsLivenessPortsRangeStart)}, - }, - }, - { - name: "exclude inbound ports", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeInboundPorts: []string{"1111", "11111"}, - }, - }, - { - name: "exclude outbound ports", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"5996"}, - ExcludeOutboundPorts: []string{"2222", "22222"}, - }, - }, - { - name: "exclude outbound CIDRs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{strconv.Itoa(initContainersUserAndGroupID)}, - ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, - }, - }, - { - name: "exclude UIDs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeUIDs: "4444,44444", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ConsulDNSIP: "", - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, - }, - }, - { - name: "exclude inbound ports, outbound ports, outbound CIDRs, and UIDs", - webhook: MeshWebhook{ - Log: logrtest.New(t), - AllowK8sNamespacesSet: mapset.NewSetWith("*"), - DenyK8sNamespacesSet: mapset.NewSet(), - decoder: decoder, - }, - pod: &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: defaultNamespace, - Name: defaultPodName, - Annotations: map[string]string{ - constants.AnnotationTProxyExcludeInboundPorts: "1111,11111", - constants.AnnotationTProxyExcludeOutboundPorts: "2222,22222", - constants.AnnotationTProxyExcludeOutboundCIDRs: "3.3.3.3,3.3.3.3/24", - constants.AnnotationTProxyExcludeUIDs: "4444,44444", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - }, - }, - }, - }, - expCfg: iptables.Config{ - ProxyUserID: strconv.Itoa(sidecarUserAndGroupID), - ProxyInboundPort: constants.ProxyDefaultInboundPort, - ProxyOutboundPort: iptables.DefaultTProxyOutboundPort, - ExcludeInboundPorts: []string{"1111", "11111"}, - ExcludeOutboundPorts: []string{"2222", "22222"}, - ExcludeOutboundCIDRs: []string{"3.3.3.3", "3.3.3.3/24"}, - ExcludeUIDs: []string{"4444", "44444", strconv.Itoa(initContainersUserAndGroupID)}, - }, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - err = c.webhook.addRedirectTrafficConfigAnnotation(c.pod, c.namespace) - - // Only compare annotation and iptables config on successful runs - if c.expErr == nil { - require.NoError(t, err) - anno, ok := c.pod.Annotations[constants.AnnotationRedirectTraffic] - require.Equal(t, ok, true) - - actualConfig := iptables.Config{} - err = json.Unmarshal([]byte(anno), &actualConfig) - require.NoError(t, err) - require.Equal(t, c.expCfg, actualConfig) - } else { - require.EqualError(t, err, c.expErr.Error()) - } - }) - } -} - -func TestRedirectTraffic_consulDNS(t *testing.T) { - cases := map[string]struct { - globalEnabled bool - annotations map[string]string - namespaceLabel map[string]string - expectConsulDNSConfig bool - }{ - "enabled globally, ns not set, annotation not provided": { - globalEnabled: true, - expectConsulDNSConfig: true, - }, - "enabled globally, ns not set, annotation is false": { - globalEnabled: true, - annotations: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - "enabled globally, ns not set, annotation is true": { - globalEnabled: true, - annotations: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "disabled globally, ns not set, annotation not provided": { - expectConsulDNSConfig: false, - }, - "disabled globally, ns not set, annotation is false": { - annotations: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - "disabled globally, ns not set, annotation is true": { - annotations: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "disabled globally, ns enabled, annotation not set": { - namespaceLabel: map[string]string{constants.KeyConsulDNS: "true"}, - expectConsulDNSConfig: true, - }, - "enabled globally, ns disabled, annotation not set": { - globalEnabled: true, - namespaceLabel: map[string]string{constants.KeyConsulDNS: "false"}, - expectConsulDNSConfig: false, - }, - } - for name, c := range cases { - t.Run(name, func(t *testing.T) { - w := MeshWebhook{ - EnableConsulDNS: c.globalEnabled, - EnableTransparentProxy: true, - ConsulConfig: &consul.Config{HTTPPort: 8500}, - } - - pod := minimal() - pod.Annotations = c.annotations - - ns := testNS - ns.Labels = c.namespaceLabel - iptablesConfig, err := w.iptablesConfigJSON(*pod, ns) - require.NoError(t, err) - - actualConfig := iptables.Config{} - err = json.Unmarshal([]byte(iptablesConfig), &actualConfig) - require.NoError(t, err) - if c.expectConsulDNSConfig { - require.Equal(t, "127.0.0.1", actualConfig.ConsulDNSIP) - require.Equal(t, 8600, actualConfig.ConsulDNSPort) - } else { - require.Empty(t, actualConfig.ConsulDNSIP) - } - }) - } -} diff --git a/control-plane/consul/consul.go b/control-plane/consul/consul.go index 8dea334607..3cf916ffbf 100644 --- a/control-plane/consul/consul.go +++ b/control-plane/consul/consul.go @@ -8,10 +8,9 @@ import ( "net/http" "time" + "github.com/hashicorp/consul-k8s/control-plane/version" "github.com/hashicorp/consul-server-connection-manager/discovery" capi "github.com/hashicorp/consul/api" - - "github.com/hashicorp/consul-k8s/control-plane/version" ) //go:generate mockery --name ServerConnectionManager --inpkg @@ -21,7 +20,7 @@ type ServerConnectionManager interface { Stop() } -// NewClient returns a V1 Consul API client. It adds a required User-Agent +// NewClient returns a Consul API client. It adds a required User-Agent // header that describes the version of consul-k8s making the call. func NewClient(config *capi.Config, consulAPITimeout time.Duration) (*capi.Client, error) { if consulAPITimeout <= 0 { @@ -70,7 +69,7 @@ type Config struct { } // todo (ishustava): replace all usages of this one. -// NewClientFromConnMgrState creates a new V1 API client with an IP address from the state +// NewClientFromConnMgrState creates a new API client with an IP address from the state // of the consul-server-connection-manager. func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Client, error) { ipAddress := state.Address.IP @@ -81,7 +80,7 @@ func NewClientFromConnMgrState(config *Config, state discovery.State) (*capi.Cli return NewClient(config.APIClientConfig, config.APITimeout) } -// NewClientFromConnMgr creates a new V1 API client by first getting the state of the passed watcher. +// NewClientFromConnMgr creates a new API client by first getting the state of the passed watcher. func NewClientFromConnMgr(config *Config, watcher ServerConnectionManager) (*capi.Client, error) { // Create a new consul client. serverState, err := watcher.State() diff --git a/control-plane/consul/dataplane_client.go b/control-plane/consul/dataplane_client.go deleted file mode 100644 index 628d353252..0000000000 --- a/control-plane/consul/dataplane_client.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "fmt" - - "github.com/hashicorp/consul/proto-public/pbdataplane" -) - -// NewDataplaneServiceClient creates a pbdataplane.DataplaneServiceClient for gathering proxy bootstrap config. -// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul -// server addresses. -func NewDataplaneServiceClient(watcher ServerConnectionManager) (pbdataplane.DataplaneServiceClient, error) { - - // We recycle the GRPC connection from the discovery client because it - // should have all the necessary dial options, including the resolver that - // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager - // would need to be duplicated - state, err := watcher.State() - if err != nil { - return nil, fmt.Errorf("unable to get connection manager state: %w", err) - } - dpClient := pbdataplane.NewDataplaneServiceClient(state.GRPCConn) - - return dpClient, nil -} diff --git a/control-plane/consul/dataplane_client_test.go b/control-plane/consul/dataplane_client_test.go deleted file mode 100644 index 9463839dda..0000000000 --- a/control-plane/consul/dataplane_client_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "context" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" -) - -func Test_NewDataplaneServiceClient(t *testing.T) { - - var serverConfig *testutil.TestServerConfig - server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverConfig = c - }) - require.NoError(t, err) - defer server.Stop() - - server.WaitForLeader(t) - server.WaitForActiveCARoot(t) - - t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) - - // Create discovery configuration - discoverConfig := discovery.Config{ - Addresses: "127.0.0.1", - GRPCPort: serverConfig.Ports.GRPC, - } - - opts := hclog.LoggerOptions{Name: "dataplane-service-client"} - logger := hclog.New(&opts) - - watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) - require.NoError(t, err) - require.NotNil(t, watcher) - - defer watcher.Stop() - go watcher.Run() - - // Create a workload and create a proxyConfiguration - createWorkload(t, watcher, "foo") - pc := createProxyConfiguration(t, watcher, "foo") - - client, err := NewDataplaneServiceClient(watcher) - require.NoError(t, err) - require.NotNil(t, client) - require.NotNil(t, watcher) - - req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: "foo", - Namespace: "default", - Partition: "default", - } - - res, err := client.GetEnvoyBootstrapParams(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, "foo", res.GetIdentity()) - require.Equal(t, "default", res.GetNamespace()) - require.Equal(t, "default", res.GetPartition()) - - if diff := cmp.Diff(pc.BootstrapConfig, res.GetBootstrapConfig(), protocmp.Transform()); diff != "" { - t.Errorf("unexpected difference:\n%v", diff) - } - - // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the - // discovery server. The discovery response only includes the IP address of the host, so all servers - // for a local test are de-duplicated as a single entry. -} - -func createWorkload(t *testing.T, watcher ServerConnectionManager, name string) { - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "mesh": { - Port: 20000, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0-virtual", - Identity: name, - } - - id := &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - Namespace: "default", - PeerName: "local", - }, - } - - proto, err := anypb.New(workload) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: id, - Data: proto, - }, - } - - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - - resourceHasPersisted(t, client, id) -} - -func createProxyConfiguration(t *testing.T, watcher ServerConnectionManager, name string) *pbmesh.ProxyConfiguration { - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - - pc := &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{"foo"}, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsBindAddr: "127.0.0.2:1234", - ReadyBindAddr: "127.0.0.3:5678", - }, - } - - id := &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: "default", - Namespace: "default", - PeerName: "local", - }, - } - - proto, err := anypb.New(pc) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: id, - Data: proto, - }, - } - - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - - resourceHasPersisted(t, client, id) - return pc -} - -// resourceHasPersisted checks that a recently written resource exists in the Consul -// state store with a valid version. This must be true before a resource is overwritten -// or deleted. -// TODO: refactor so that there isn't an import cycle when using test.ResourceHasPersisted. -func resourceHasPersisted(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID) { - req := &pbresource.ReadRequest{Id: id} - - require.Eventually(t, func() bool { - res, err := client.Read(context.Background(), req) - if err != nil { - return false - } - - if res.GetResource().GetVersion() == "" { - return false - } - - return true - }, 5*time.Second, - time.Second) -} diff --git a/control-plane/consul/resource_client.go b/control-plane/consul/resource_client.go deleted file mode 100644 index 82c24af34f..0000000000 --- a/control-plane/consul/resource_client.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "fmt" - - "github.com/hashicorp/consul/proto-public/pbresource" -) - -// NewResourceServiceClient creates a pbresource.ResourceServiceClient for creating V2 Consul resources. -// It is initialized with a consul-server-connection-manager Watcher to continuously find Consul -// server addresses. -func NewResourceServiceClient(watcher ServerConnectionManager) (pbresource.ResourceServiceClient, error) { - - // We recycle the GRPC connection from the discovery client because it - // should have all the necessary dial options, including the resolver that - // continuously updates Consul server addresses. Otherwise, a lot of code from consul-server-connection-manager - // would need to be duplicated - state, err := watcher.State() - if err != nil { - return nil, fmt.Errorf("unable to get connection manager state: %w", err) - } - resourceClient := pbresource.NewResourceServiceClient(state.GRPCConn) - - return resourceClient, nil -} diff --git a/control-plane/consul/resource_client_test.go b/control-plane/consul/resource_client_test.go deleted file mode 100644 index 0dbecc9798..0000000000 --- a/control-plane/consul/resource_client_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "context" - "testing" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/hashicorp/go-hclog" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" -) - -func Test_NewResourceServiceClient(t *testing.T) { - - var serverConfig *testutil.TestServerConfig - server, err := testutil.NewTestServerConfigT(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverConfig = c - }) - require.NoError(t, err) - defer server.Stop() - - server.WaitForLeader(t) - server.WaitForActiveCARoot(t) - - t.Logf("server grpc address on %d", serverConfig.Ports.GRPC) - - // Create discovery configuration - discoverConfig := discovery.Config{ - Addresses: "127.0.0.1", - GRPCPort: serverConfig.Ports.GRPC, - } - - opts := hclog.LoggerOptions{Name: "resource-service-client"} - logger := hclog.New(&opts) - - watcher, err := discovery.NewWatcher(context.Background(), discoverConfig, logger) - require.NoError(t, err) - require.NotNil(t, watcher) - - defer watcher.Stop() - go watcher.Run() - - client, err := NewResourceServiceClient(watcher) - require.NoError(t, err) - require.NotNil(t, client) - require.NotNil(t, watcher) - - req := createWriteRequest(t, "foo") - res, err := client.Write(context.Background(), req) - require.NoError(t, err) - require.NotNil(t, res) - require.Equal(t, "foo", res.GetResource().GetId().GetName()) - - // NOTE: currently it isn't possible to test that the grpc connection responds to changes in the - // discovery server. The discovery response only includes the IP address of the host, so all servers - // for a local test are de-duplicated as a single entry. -} - -func createWriteRequest(t *testing.T, name string) *pbresource.WriteRequest { - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "mesh": { - Port: 20000, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0-virtual", - Identity: name, - } - - proto, err := anypb.New(workload) - require.NoError(t, err) - - req := &pbresource.WriteRequest{ - Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Namespace: constants.DefaultConsulNS, - Partition: constants.DefaultConsulPartition, - PeerName: constants.DefaultConsulPeer, - }, - }, - Data: proto, - }, - } - return req -} diff --git a/control-plane/config-entries/controllers/configentry_controller.go b/control-plane/controllers/configentry_controller.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller.go rename to control-plane/controllers/configentry_controller.go index f1a4e316e8..133afc0a1c 100644 --- a/control-plane/config-entries/controllers/configentry_controller.go +++ b/control-plane/controllers/configentry_controller.go @@ -11,6 +11,9 @@ import ( "time" "github.com/go-logr/logr" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" capi "github.com/hashicorp/consul/api" "golang.org/x/time/rate" corev1 "k8s.io/api/core/v1" @@ -22,10 +25,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) const ( diff --git a/control-plane/config-entries/controllers/configentry_controller_ent_test.go b/control-plane/controllers/configentry_controller_ent_test.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller_ent_test.go rename to control-plane/controllers/configentry_controller_ent_test.go index a0fb5ce91e..fd4763ae27 100644 --- a/control-plane/config-entries/controllers/configentry_controller_ent_test.go +++ b/control-plane/controllers/configentry_controller_ent_test.go @@ -13,6 +13,10 @@ import ( "github.com/go-logr/logr" logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -23,11 +27,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // NOTE: We're not testing each controller type here because that's mostly done in diff --git a/control-plane/config-entries/controllers/configentry_controller_test.go b/control-plane/controllers/configentry_controller_test.go similarity index 99% rename from control-plane/config-entries/controllers/configentry_controller_test.go rename to control-plane/controllers/configentry_controller_test.go index 57e48dd46c..07a3ea3730 100644 --- a/control-plane/config-entries/controllers/configentry_controller_test.go +++ b/control-plane/controllers/configentry_controller_test.go @@ -13,6 +13,10 @@ import ( logrtest "github.com/go-logr/logr/testr" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -23,11 +27,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) const datacenterName = "datacenter" diff --git a/control-plane/config-entries/controllers/controlplanerequestlimit_controller.go b/control-plane/controllers/controlplanerequestlimit_controller.go similarity index 100% rename from control-plane/config-entries/controllers/controlplanerequestlimit_controller.go rename to control-plane/controllers/controlplanerequestlimit_controller.go diff --git a/control-plane/config-entries/controllers/exportedservices_controller.go b/control-plane/controllers/exportedservices_controller.go similarity index 100% rename from control-plane/config-entries/controllers/exportedservices_controller.go rename to control-plane/controllers/exportedservices_controller.go diff --git a/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go b/control-plane/controllers/exportedservices_controller_ent_test.go similarity index 99% rename from control-plane/config-entries/controllers/exportedservices_controller_ent_test.go rename to control-plane/controllers/exportedservices_controller_ent_test.go index 70b774eb53..94a605eab4 100644 --- a/control-plane/config-entries/controllers/exportedservices_controller_ent_test.go +++ b/control-plane/controllers/exportedservices_controller_ent_test.go @@ -12,6 +12,10 @@ import ( "time" logrtest "github.com/go-logr/logr/testing" + "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/controllers" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" capi "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -20,11 +24,6 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // This tests explicitly tests ExportedServicesController instead of using the existing diff --git a/control-plane/config-entries/controllers/ingressgateway_controller.go b/control-plane/controllers/ingressgateway_controller.go similarity index 100% rename from control-plane/config-entries/controllers/ingressgateway_controller.go rename to control-plane/controllers/ingressgateway_controller.go diff --git a/control-plane/config-entries/controllers/jwtprovider_controller.go b/control-plane/controllers/jwtprovider_controller.go similarity index 100% rename from control-plane/config-entries/controllers/jwtprovider_controller.go rename to control-plane/controllers/jwtprovider_controller.go diff --git a/control-plane/config-entries/controllers/mesh_controller.go b/control-plane/controllers/mesh_controller.go similarity index 100% rename from control-plane/config-entries/controllers/mesh_controller.go rename to control-plane/controllers/mesh_controller.go diff --git a/control-plane/config-entries/controllers/proxydefaults_controller.go b/control-plane/controllers/proxydefaults_controller.go similarity index 100% rename from control-plane/config-entries/controllers/proxydefaults_controller.go rename to control-plane/controllers/proxydefaults_controller.go diff --git a/control-plane/config-entries/controllers/samenessgroups_controller.go b/control-plane/controllers/samenessgroups_controller.go similarity index 100% rename from control-plane/config-entries/controllers/samenessgroups_controller.go rename to control-plane/controllers/samenessgroups_controller.go diff --git a/control-plane/config-entries/controllers/servicedefaults_controller.go b/control-plane/controllers/servicedefaults_controller.go similarity index 100% rename from control-plane/config-entries/controllers/servicedefaults_controller.go rename to control-plane/controllers/servicedefaults_controller.go diff --git a/control-plane/config-entries/controllers/serviceintentions_controller.go b/control-plane/controllers/serviceintentions_controller.go similarity index 100% rename from control-plane/config-entries/controllers/serviceintentions_controller.go rename to control-plane/controllers/serviceintentions_controller.go diff --git a/control-plane/config-entries/controllers/serviceresolver_controller.go b/control-plane/controllers/serviceresolver_controller.go similarity index 100% rename from control-plane/config-entries/controllers/serviceresolver_controller.go rename to control-plane/controllers/serviceresolver_controller.go diff --git a/control-plane/config-entries/controllers/servicerouter_controller.go b/control-plane/controllers/servicerouter_controller.go similarity index 100% rename from control-plane/config-entries/controllers/servicerouter_controller.go rename to control-plane/controllers/servicerouter_controller.go diff --git a/control-plane/config-entries/controllers/servicesplitter_controller.go b/control-plane/controllers/servicesplitter_controller.go similarity index 100% rename from control-plane/config-entries/controllers/servicesplitter_controller.go rename to control-plane/controllers/servicesplitter_controller.go diff --git a/control-plane/config-entries/controllers/terminatinggateway_controller.go b/control-plane/controllers/terminatinggateway_controller.go similarity index 100% rename from control-plane/config-entries/controllers/terminatinggateway_controller.go rename to control-plane/controllers/terminatinggateway_controller.go diff --git a/control-plane/go.mod b/control-plane/go.mod index 6671f553b8..c73c398607 100644 --- a/control-plane/go.mod +++ b/control-plane/go.mod @@ -1,13 +1,5 @@ module github.com/hashicorp/consul-k8s/control-plane -// TODO: remove these when the SDK is released for Consul 1.17 and coinciding patch releases -replace ( - // This replace directive is needed because `api` requires 0.4.1 of proto-public but we need an unreleased version - github.com/hashicorp/consul/proto-public v0.4.1 => github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 - // This replace directive is needed because `api` requires 0.14.1 of `sdk` but we need an unreleased version - github.com/hashicorp/consul/sdk v0.14.1 => github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 -) - require ( github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/cni v1.1.1 @@ -16,10 +8,9 @@ require ( github.com/go-logr/logr v1.2.3 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed + github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d github.com/hashicorp/consul-server-connection-manager v0.1.6 - github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a - github.com/hashicorp/consul/proto-public v0.4.1 + github.com/hashicorp/consul/api v1.25.1 github.com/hashicorp/consul/sdk v0.14.1 github.com/hashicorp/go-bexpr v0.1.11 github.com/hashicorp/go-discover v0.0.0-20230519164032-214571b6a530 @@ -27,7 +18,6 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-netaddrs v0.1.0 github.com/hashicorp/go-rootcerts v1.0.2 - github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.8.3 @@ -38,12 +28,10 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.8.3 go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 + golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/text v0.13.0 golang.org/x/time v0.3.0 gomodules.xyz/jsonpatch/v2 v2.3.0 - google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.30.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.26.3 k8s.io/apimachinery v0.26.3 @@ -83,7 +71,7 @@ require ( github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.14.1 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -100,6 +88,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.7.1 // indirect github.com/gophercloud/gophercloud v0.1.0 // indirect + github.com/hashicorp/consul/proto-public v0.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect @@ -109,6 +98,7 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect @@ -123,7 +113,7 @@ require ( github.com/linode/linodego v0.7.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect @@ -156,18 +146,20 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.14.0 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/tools v0.7.0 // indirect google.golang.org/api v0.114.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e // indirect + google.golang.org/grpc v1.56.3 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/control-plane/go.sum b/control-plane/go.sum index e8929ad883..75d3c7bf47 100644 --- a/control-plane/go.sum +++ b/control-plane/go.sum @@ -140,8 +140,8 @@ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2Vvl github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= @@ -259,16 +259,16 @@ github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6c github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed h1:eM9tGgSqAZbm4Ndkp35Dg8YROT0dNH3ghTYu5pcUIAc= -github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230825213844-4ea04860c5ed/go.mod h1:mwODEC+VTCA1LY/m2RUG4S2c5lNRvBcsvqaMJtMLLos= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d h1:RJ1MZ8JKnfgKQ1kR3IBQAMpOpzXrdseZAYN/QR//MFM= +github.com/hashicorp/consul-k8s/control-plane/cni v0.0.0-20230511143918-bd16ab83383d/go.mod h1:IHIHMzkoMwlv6rLsgwcoFBVYupR7/1pKEOHBMjD4L0k= github.com/hashicorp/consul-server-connection-manager v0.1.6 h1:ktj8Fi+dRXn9hhM+FXsfEJayhzzgTqfH08Ne5M6Fmug= github.com/hashicorp/consul-server-connection-manager v0.1.6/go.mod h1:HngMIv57MT+pqCVeRQMa1eTB5dqnyMm8uxjyv+Hn8cs= -github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a h1:+cjavDME42W6nzw+xyCQ+eczqTFA+Qk4SLl7BHZ2dxI= -github.com/hashicorp/consul/api v1.10.1-0.20231011171434-ca1a755f0c5a/go.mod h1:+pNEP6hQgkrBLjQlYLI13/tyyb1GK3MGVw1PC/IHk9M= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58 h1:3VHvqLs2zTa9YWMsE4A9IricAZB6rAcXVe8e+pb5slw= -github.com/hashicorp/consul/proto-public v0.1.2-0.20231013204122-3d1a606c3b58/go.mod h1:KAOxsaELPpA7JX10kMeygAskAqsQnu3SPgeruMhYZMU= -github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9 h1:j0Rvt1KiKIKlMc2UnA0ilHfIGo66SzrBztGZaCou3+w= -github.com/hashicorp/consul/sdk v0.4.1-0.20231011203909-c26d5cf62cb9/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= +github.com/hashicorp/consul/proto-public v0.1.0 h1:O0LSmCqydZi363hsqc6n2v5sMz3usQMXZF6ziK3SzXU= +github.com/hashicorp/consul/proto-public v0.1.0/go.mod h1:vs2KkuWwtjkIgA5ezp4YKPzQp4GitV+q/+PvksrA92k= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -396,8 +396,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -584,8 +584,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -608,8 +608,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -677,8 +677,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -742,7 +742,6 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -812,8 +811,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go new file mode 100644 index 0000000000..093b1ef908 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mutatingwebhookconfiguration + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates +// their caBundle with the the specified CA. +func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { + if len(caCert) == 0 { + return errors.New("no CA certificate in the bundle") + } + value := base64.StdEncoding.EncodeToString(caCert) + webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) + + if err != nil { + return err + } + type patch struct { + Op string `json:"op,omitempty"` + Path string `json:"path,omitempty"` + Value string `json:"value,omitempty"` + } + + var patches []patch + for i := range webhookCfg.Webhooks { + patches = append(patches, patch{ + Op: "add", + Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), + Value: value, + }) + } + patchesJson, err := json.Marshal(patches) + if err != nil { + return err + } + + if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookConfigName, types.JSONPatchType, patchesJson, metav1.PatchOptions{}); err != nil { + return err + } + + return nil +} diff --git a/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go new file mode 100644 index 0000000000..be1a3b5c64 --- /dev/null +++ b/control-plane/helper/mutating-webhook-configuration/mutating_webhook_configuration_test.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mutatingwebhookconfiguration + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestUpdateWithCABundle_emptyCertReturnsError(t *testing.T) { + var bytes []byte + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + err := UpdateWithCABundle(ctx, clientset, "foo", bytes) + require.Error(t, err, "no CA certificate in the bundle") +} + +func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { + caBundleOne := []byte("ca-bundle-for-mwc") + ctx := context.Background() + clientset := fake.NewSimpleClientset() + + mwc := &admissionv1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mwc-one", + }, + Webhooks: []admissionv1.MutatingWebhook{ + { + Name: "webhook-under-test", + }, + }, + } + mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) + require.NoError(t, err) + err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) + require.NoError(t, err) + mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) +} diff --git a/control-plane/helper/test/test_util.go b/control-plane/helper/test/test_util.go index 3a80232a23..e29e44de59 100644 --- a/control-plane/helper/test/test_util.go +++ b/control-plane/helper/test/test_util.go @@ -4,7 +4,6 @@ package test import ( - "context" "fmt" "net" "net/http" @@ -14,19 +13,12 @@ import ( "testing" "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/protobuf/testing/protocmp" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" ) const ( @@ -54,7 +46,7 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf t.Cleanup(func() { _ = consulServer.Stop() }) - consulServer.WaitForLeader(t) + consulServer.WaitForSerfCheck(t) consulConfig := &consul.Config{ APIClientConfig: &api.Config{Address: consulServer.HTTPAddr}, @@ -70,33 +62,20 @@ func TestServerWithMockConnMgrWatcher(t *testing.T, callback testutil.ServerConf TestServer: consulServer, APIClient: client, Cfg: consulConfig, - Watcher: MockConnMgrForIPAndPort(t, "127.0.0.1", cfg.Ports.GRPC, true), + Watcher: MockConnMgrForIPAndPort("127.0.0.1", cfg.Ports.GRPC), } } -func MockConnMgrForIPAndPort(t *testing.T, ip string, port int, enableGRPCConn bool) *consul.MockServerConnectionManager { +func MockConnMgrForIPAndPort(ip string, port int) *consul.MockServerConnectionManager { parsedIP := net.ParseIP(ip) connMgr := &consul.MockServerConnectionManager{} - mockState := discovery.State{ Address: discovery.Addr{ TCPAddr: net.TCPAddr{ IP: parsedIP, Port: port, }, - }, - } - - // If the connection is enabled, some tests will receive extra HTTP API calls where - // the server is being dialed. - if enableGRPCConn { - conn, err := grpc.DialContext( - context.Background(), - fmt.Sprintf("%s:%d", parsedIP, port), - grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(t, err) - mockState.GRPCConn = conn - } + }} connMgr.On("State").Return(mockState, nil) connMgr.On("Run").Return(nil) connMgr.On("Stop").Return(nil) @@ -219,19 +198,13 @@ func SetupK8sComponentAuthMethod(t *testing.T, consulClient *api.Client, service // SetupK8sAuthMethod create a k8s auth method and a binding rule in Consul for the // given k8s service and namespace. func SetupK8sAuthMethod(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { - SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", false) -} - -// SetupK8sAuthMethodV2 create a k8s auth method and a binding rule in Consul for the -// given k8s service and namespace. -func SetupK8sAuthMethodV2(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS string) { - SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "", true) + SetupK8sAuthMethodWithNamespaces(t, consulClient, serviceName, k8sServiceNS, "", false, "") } // SetupK8sAuthMethodWithNamespaces creates a k8s auth method and binding rule // in Consul for the k8s service name and namespace. It sets up the auth method and the binding // rule so that it works with consul namespaces. -func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string, useV2 bool) { +func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, serviceName, k8sServiceNS, consulNS string, mirrorNS bool, nsPrefix string) { t.Helper() // Start the mock k8s server. k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -268,30 +241,14 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) // Create the binding rule. - var aclBindingRule api.ACLBindingRule - if useV2 { - aclBindingRule = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: AuthMethod, - BindType: api.BindingRuleBindTypeTemplatedPolicy, - BindName: api.ACLTemplatedPolicyWorkloadIdentityName, - BindVars: &api.ACLTemplatedPolicyVariables{ - Name: "${serviceaccount.name}", - }, - Selector: "serviceaccount.name!=default", - Namespace: consulNS, - } - } else { - aclBindingRule = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: AuthMethod, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: "serviceaccount.name!=default", - Namespace: consulNS, - } + aclBindingRule := api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: AuthMethod, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: "serviceaccount.name!=default", + Namespace: consulNS, } - if mirrorNS { aclBindingRule.Namespace = "default" } @@ -300,27 +257,6 @@ func SetupK8sAuthMethodWithNamespaces(t *testing.T, consulClient *api.Client, se require.NoError(t, err) } -// ResourceHasPersisted checks that a recently written resource exists in the Consul -// state store with a valid version. This must be true before a resource is overwritten -// or deleted. -func ResourceHasPersisted(t *testing.T, ctx context.Context, client pbresource.ResourceServiceClient, id *pbresource.ID) { - req := &pbresource.ReadRequest{Id: id} - - require.Eventually(t, func() bool { - res, err := client.Read(ctx, req) - if err != nil { - return false - } - - if res.GetResource().GetVersion() == "" { - return false - } - - return true - }, 5*time.Second, - time.Second) -} - func TokenReviewsResponse(name, ns string) string { return fmt.Sprintf(`{ "kind": "TokenReview", @@ -366,16 +302,6 @@ func ServiceAccountGetResponse(name, ns string) string { }`, name, ns, ns, name, name) } -// CmpProtoIgnoreOrder returns a slice of cmp.Option useful for comparing proto messages independent of the order of -// their repeated fields. -func CmpProtoIgnoreOrder() []cmp.Option { - return []cmp.Option{ - protocmp.Transform(), - // Stringify any type passed to the sorter so that we can reliably compare most values. - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), - } -} - const AuthMethod = "consul-k8s-auth-method" const ServiceAccountJWTToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImtoYWtpLWFyYWNobmlkLWNvbnN1bC1jb25uZWN0LWluamVjdG9yLWF1dGhtZXRob2Qtc3ZjLWFjY29obmRidiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJraGFraS1hcmFjaG5pZC1jb25zdWwtY29ubmVjdC1pbmplY3Rvci1hdXRobWV0aG9kLXN2Yy1hY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiN2U5NWUxMjktZTQ3My0xMWU5LThmYWEtNDIwMTBhODAwMTIyIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6a2hha2ktYXJhY2huaWQtY29uc3VsLWNvbm5lY3QtaW5qZWN0b3ItYXV0aG1ldGhvZC1zdmMtYWNjb3VudCJ9.Yi63MMtzh5MBWKKd3a7dzCJjTITE15ikFy_Tnpdk_AwdwA9J4AMSGEeHN5vWtCuuFjo_lMJqBBPHkK2AqbnoFUj9m5CopWyqICJQlvEOP4fUQ-Rc0W1P_JjU1rZERHG39b5TMLgKPQguyhaiZEJ6CjVtm9wUTagrgiuqYV2iUqLuF6SYNm6SrKtkPS-lqIO-u7C06wVk5m5uqwIVQNpZSIC_5Ls5aLmyZU3nHvH-V7E3HmBhVyZAB76jgKB0TyVX1IOskt9PDFarNtU3suZyCjvqC-UJA6sYeySe4dBNKsKlSZ6YuxUUmn1Rgv32YMdImnsWg8khf-zJvqgWk7B5EA` const serviceAccountCACert = `-----BEGIN CERTIFICATE----- diff --git a/control-plane/helper/webhook-configuration/webhook_configuration.go b/control-plane/helper/webhook-configuration/webhook_configuration.go deleted file mode 100644 index be8588434d..0000000000 --- a/control-plane/helper/webhook-configuration/webhook_configuration.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookconfiguration - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - - admissionv1 "k8s.io/api/admissionregistration/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" -) - -// UpdateWithCABundle iterates over every webhook on the specified webhook configuration and updates -// their caBundle with the the specified CA. -func UpdateWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookConfigName string, caCert []byte) error { - if len(caCert) == 0 { - return errors.New("no CA certificate in the bundle") - } - - mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if !k8serrors.IsNotFound(err) { - err = updateMutatingWebhooksWithCABundle(ctx, clientset, mutatingWebhookCfg, caCert) - if err != nil { - return err - } - } - - validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, webhookConfigName, metav1.GetOptions{}) - if err != nil && !k8serrors.IsNotFound(err) { - return err - } - - if k8serrors.IsNotFound(err) { - return nil - } - - return updateValidatingWebhooksWithCABundle(ctx, clientset, validatingWebhookCfg, caCert) -} - -func updateMutatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.MutatingWebhookConfiguration, caCert []byte) error { - value := base64.StdEncoding.EncodeToString(caCert) - type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` - } - - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJSON, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { - return err - } - - return nil -} - -func updateValidatingWebhooksWithCABundle(ctx context.Context, clientset kubernetes.Interface, webhookCfg *admissionv1.ValidatingWebhookConfiguration, caCert []byte) error { - value := base64.StdEncoding.EncodeToString(caCert) - type patch struct { - Op string `json:"op,omitempty"` - Path string `json:"path,omitempty"` - Value string `json:"value,omitempty"` - } - - var patches []patch - for i := range webhookCfg.Webhooks { - patches = append(patches, patch{ - Op: "add", - Path: fmt.Sprintf("/webhooks/%d/clientConfig/caBundle", i), - Value: value, - }) - } - patchesJSON, err := json.Marshal(patches) - if err != nil { - return err - } - - if _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Patch(ctx, webhookCfg.Name, types.JSONPatchType, patchesJSON, metav1.PatchOptions{}); err != nil { - return err - } - - return nil -} diff --git a/control-plane/helper/webhook-configuration/webhook_configuration_test.go b/control-plane/helper/webhook-configuration/webhook_configuration_test.go deleted file mode 100644 index bb02573804..0000000000 --- a/control-plane/helper/webhook-configuration/webhook_configuration_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package webhookconfiguration - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - admissionv1 "k8s.io/api/admissionregistration/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes/fake" -) - -func TestUpdateWithCABundle_emptyCertReturnsError(t *testing.T) { - var bytes []byte - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - err := UpdateWithCABundle(ctx, clientset, "foo", bytes) - require.Error(t, err, "no CA certificate in the bundle") -} - -func TestUpdateWithCABundle_patchesExistingConfiguration(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - mwc := &admissionv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.MutatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} - -func TestUpdateWithCABundle_patchesExistingConfigurationForValidating(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - mwc := &admissionv1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.ValidatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - mwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} - -func TestUpdateWithCABundle_patchesExistingConfigurationWhenMutatingAndValidatingExist(t *testing.T) { - caBundleOne := []byte("ca-bundle-for-mwc") - ctx := context.Background() - clientset := fake.NewSimpleClientset() - - vwc := &admissionv1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.ValidatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - - mwc := &admissionv1.MutatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "mwc-one", - }, - Webhooks: []admissionv1.MutatingWebhook{ - { - Name: "webhook-under-test", - }, - }, - } - mwcCreated, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, mwc, metav1.CreateOptions{}) - require.NoError(t, err) - _, err = clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, vwc, metav1.CreateOptions{}) - require.NoError(t, err) - err = UpdateWithCABundle(ctx, clientset, mwcCreated.Name, caBundleOne) - require.NoError(t, err) - vwcFetched, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, vwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, vwcFetched.Webhooks[0].ClientConfig.CABundle) - - mwcFetched, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, mwc.Name, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, caBundleOne, mwcFetched.Webhooks[0].ClientConfig.CABundle) -} diff --git a/control-plane/namespaces/namespaces.go b/control-plane/namespaces/namespaces.go index 217765c59b..84b8c15ee4 100644 --- a/control-plane/namespaces/namespaces.go +++ b/control-plane/namespaces/namespaces.go @@ -55,29 +55,6 @@ func EnsureExists(client *capi.Client, ns string, crossNSAClPolicy string) (bool return true, err } -// EnsureDeleted ensures a Consul namespace with name ns is deleted. If it is already not found -// the call to delete will be skipped. -func EnsureDeleted(client *capi.Client, ns string) error { - if ns == WildcardNamespace || ns == DefaultNamespace { - return nil - } - // Check if the Consul namespace exists. - namespaceInfo, _, err := client.Namespaces().Read(ns, nil) - if err != nil { - return fmt.Errorf("could not read namespace %s: %w", ns, err) - } - if namespaceInfo == nil { - return nil - } - - // If not empty, delete it. - _, err = client.Namespaces().Delete(ns, nil) - if err != nil { - return fmt.Errorf("could not delete namespace %s: %w", ns, err) - } - return nil -} - // ConsulNamespace returns the consul namespace that a service should be // registered in based on the namespace options. It returns an // empty string if namespaces aren't enabled. diff --git a/control-plane/namespaces/namespaces_test.go b/control-plane/namespaces/namespaces_test.go index 6e1305d959..a2c9989ae8 100644 --- a/control-plane/namespaces/namespaces_test.go +++ b/control-plane/namespaces/namespaces_test.go @@ -40,7 +40,7 @@ func TestEnsureExists_AlreadyExists(tt *testing.T) { }) req.NoError(err) defer consul.Stop() - consul.WaitForLeader(t) + consul.WaitForSerfCheck(t) consulClient, err := capi.NewClient(&capi.Config{ Address: consul.HTTPAddr, Token: masterToken, @@ -159,56 +159,6 @@ func TestEnsureExists_CreatesNS(tt *testing.T) { } } -// Test that it creates the namespace if it doesn't exist. -func TestEnsureDelete(tt *testing.T) { - name := "foo" - for _, c := range []struct { - NamespaceExists bool - }{ - { - NamespaceExists: true, - }, - { - NamespaceExists: false, - }, - } { - tt.Run(fmt.Sprintf("namespace: %t", c.NamespaceExists), func(t *testing.T) { - consul, err := testutil.NewTestServerConfigT(t, nil) - require.NoError(t, err) - defer consul.Stop() - consul.WaitForLeader(t) - - consulClient, err := capi.NewClient(&capi.Config{ - Address: consul.HTTPAddr, - }) - require.NoError(t, err) - - if c.NamespaceExists { - ns := capi.Namespace{ - Name: name, - } - _, _, err = consulClient.Namespaces().Create(&ns, nil) - require.NoError(t, err) - - check, _, err := consulClient.Namespaces().Read(name, nil) - require.NoError(t, err) - require.NotNil(t, check) - require.Equal(t, name, check.Name) - } - - err = EnsureDeleted(consulClient, name) - require.NoError(t, err) - - // Ensure it was deleted. - cNS, _, err := consulClient.Namespaces().Read(name, nil) - require.NoError(t, err) - if cNS != nil && cNS.DeletedAt == nil { - require.Fail(t, "the namespace was not deleted") - } - }) - } -} - func TestConsulNamespace(t *testing.T) { cases := map[string]struct { kubeNS string diff --git a/control-plane/subcommand/acl-init/command.go b/control-plane/subcommand/acl-init/command.go index 24ae5c7ebf..77e8f87d87 100644 --- a/control-plane/subcommand/acl-init/command.go +++ b/control-plane/subcommand/acl-init/command.go @@ -18,6 +18,10 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-netaddrs" @@ -25,11 +29,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/acl-init/command_test.go b/control-plane/subcommand/acl-init/command_test.go index ca236bb031..acdafc939f 100644 --- a/control-plane/subcommand/acl-init/command_test.go +++ b/control-plane/subcommand/acl-init/command_test.go @@ -13,15 +13,14 @@ import ( "testing" "text/template" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const ( diff --git a/control-plane/subcommand/common/common.go b/control-plane/subcommand/common/common.go index 2b1475daa4..1636c0b10e 100644 --- a/control-plane/subcommand/common/common.go +++ b/control-plane/subcommand/common/common.go @@ -13,13 +13,12 @@ import ( "github.com/cenkalti/backoff" "github.com/go-logr/logr" + godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "go.uber.org/zap/zapcore" "sigs.k8s.io/controller-runtime/pkg/log/zap" - - godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" ) const ( diff --git a/control-plane/subcommand/common/common_test.go b/control-plane/subcommand/common/common_test.go index 2925638c6e..9e50302b17 100644 --- a/control-plane/subcommand/common/common_test.go +++ b/control-plane/subcommand/common/common_test.go @@ -12,13 +12,12 @@ import ( "os" "testing" + "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-discover" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover/mocks" ) func TestLogger_InvalidLogLevel(t *testing.T) { diff --git a/control-plane/subcommand/connect-init/command.go b/control-plane/subcommand/connect-init/command.go index 6bb66d0948..4f83ea98f1 100644 --- a/control-plane/subcommand/connect-init/command.go +++ b/control-plane/subcommand/connect-init/command.go @@ -199,7 +199,7 @@ func (c *Command) Run(args []string) int { // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. if c.consul.UseTLS && c.consul.CACertPEM != "" { - if err = common.WriteFileWithPerms(constants.LegacyConsulCAFile, c.consul.CACertPEM, 0444); err != nil { + if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { c.logger.Error("error writing CA cert file", "error", err) return 1 } diff --git a/control-plane/subcommand/connect-init/command_ent_test.go b/control-plane/subcommand/connect-init/command_ent_test.go index 743bd511fd..b3ef7109e0 100644 --- a/control-plane/subcommand/connect-init/command_ent_test.go +++ b/control-plane/subcommand/connect-init/command_ent_test.go @@ -12,12 +12,11 @@ import ( "strconv" "testing" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) func TestRun_WithNamespaces(t *testing.T) { diff --git a/control-plane/subcommand/connect-init/command_test.go b/control-plane/subcommand/connect-init/command_test.go index f756ac7359..cb1d32d03b 100644 --- a/control-plane/subcommand/connect-init/command_test.go +++ b/control-plane/subcommand/connect-init/command_test.go @@ -14,14 +14,13 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/iptables" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const nodeName = "test-node" diff --git a/control-plane/subcommand/consul-logout/command.go b/control-plane/subcommand/consul-logout/command.go index b556556d43..a6b599dccd 100644 --- a/control-plane/subcommand/consul-logout/command.go +++ b/control-plane/subcommand/consul-logout/command.go @@ -7,13 +7,12 @@ import ( "flag" "sync" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) const ( diff --git a/control-plane/subcommand/consul-logout/command_test.go b/control-plane/subcommand/consul-logout/command_test.go index 3b4d6d39cc..1a0744996a 100644 --- a/control-plane/subcommand/consul-logout/command_test.go +++ b/control-plane/subcommand/consul-logout/command_test.go @@ -9,13 +9,12 @@ import ( "os" "testing" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) func TestRun_FlagValidation(t *testing.T) { diff --git a/control-plane/subcommand/create-federation-secret/command.go b/control-plane/subcommand/create-federation-secret/command.go index 2d6b66cb83..52600aedda 100644 --- a/control-plane/subcommand/create-federation-secret/command.go +++ b/control-plane/subcommand/create-federation-secret/command.go @@ -15,6 +15,10 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -22,11 +26,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( diff --git a/control-plane/subcommand/create-federation-secret/command_test.go b/control-plane/subcommand/create-federation-secret/command_test.go index 29fe3b3d61..15f12b132c 100644 --- a/control-plane/subcommand/create-federation-secret/command_test.go +++ b/control-plane/subcommand/create-federation-secret/command_test.go @@ -14,6 +14,8 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -23,9 +25,6 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagValidation(t *testing.T) { diff --git a/control-plane/subcommand/delete-completed-job/command.go b/control-plane/subcommand/delete-completed-job/command.go index eb3705223f..f6f594393d 100644 --- a/control-plane/subcommand/delete-completed-job/command.go +++ b/control-plane/subcommand/delete-completed-job/command.go @@ -10,16 +10,15 @@ import ( "sync" "time" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" v1 "k8s.io/api/batch/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // Command is the command for deleting completed jobs. diff --git a/control-plane/subcommand/fetch-server-region/command.go b/control-plane/subcommand/fetch-server-region/command.go index 58297a10da..248ce971e7 100644 --- a/control-plane/subcommand/fetch-server-region/command.go +++ b/control-plane/subcommand/fetch-server-region/command.go @@ -11,6 +11,8 @@ import ( "os" "sync" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" @@ -18,9 +20,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // The consul-logout command issues a Consul logout API request to delete an ACL token. diff --git a/control-plane/subcommand/flags/consul.go b/control-plane/subcommand/flags/consul.go index cd577790fc..9368b95b3d 100644 --- a/control-plane/subcommand/flags/consul.go +++ b/control-plane/subcommand/flags/consul.go @@ -11,11 +11,10 @@ import ( "strings" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-rootcerts" - - "github.com/hashicorp/consul-k8s/control-plane/consul" ) const ( diff --git a/control-plane/subcommand/flags/http.go b/control-plane/subcommand/flags/http.go index 5421b4c672..21ccb45df1 100644 --- a/control-plane/subcommand/flags/http.go +++ b/control-plane/subcommand/flags/http.go @@ -9,9 +9,8 @@ import ( "strings" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul/api" ) // Taken from https://github.com/hashicorp/consul/blob/b5b9c8d953cd3c79c6b795946839f4cf5012f507/command/flags/http.go diff --git a/control-plane/subcommand/gateway-cleanup/command.go b/control-plane/subcommand/gateway-cleanup/command.go index 583b153d01..04aa8f60a9 100644 --- a/control-plane/subcommand/gateway-cleanup/command.go +++ b/control-plane/subcommand/gateway-cleanup/command.go @@ -12,6 +12,9 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -19,10 +22,6 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/gateway-cleanup/command_test.go b/control-plane/subcommand/gateway-cleanup/command_test.go index 038c5b5667..56b7651270 100644 --- a/control-plane/subcommand/gateway-cleanup/command_test.go +++ b/control-plane/subcommand/gateway-cleanup/command_test.go @@ -6,6 +6,7 @@ package gatewaycleanup import ( "testing" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -13,8 +14,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func TestRun(t *testing.T) { diff --git a/control-plane/subcommand/gateway-resources/command.go b/control-plane/subcommand/gateway-resources/command.go index ea4338f2d3..19c7b28742 100644 --- a/control-plane/subcommand/gateway-resources/command.go +++ b/control-plane/subcommand/gateway-resources/command.go @@ -15,8 +15,12 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/mitchellh/cli" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -26,11 +30,6 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) // this dupes the Kubernetes tolerations diff --git a/control-plane/subcommand/gateway-resources/command_test.go b/control-plane/subcommand/gateway-resources/command_test.go index 8455c1a315..f60e376042 100644 --- a/control-plane/subcommand/gateway-resources/command_test.go +++ b/control-plane/subcommand/gateway-resources/command_test.go @@ -6,6 +6,7 @@ package gatewayresources import ( "testing" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -13,8 +14,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" ) func TestRun_flagValidation(t *testing.T) { diff --git a/control-plane/subcommand/get-consul-client-ca/command.go b/control-plane/subcommand/get-consul-client-ca/command.go index 069b21ad0f..619f08625d 100644 --- a/control-plane/subcommand/get-consul-client-ca/command.go +++ b/control-plane/subcommand/get-consul-client-ca/command.go @@ -13,15 +13,14 @@ import ( "time" "github.com/cenkalti/backoff" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/go-discover" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/consul" godiscover "github.com/hashicorp/consul-k8s/control-plane/helper/go-discover" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul/api" + "github.com/hashicorp/go-discover" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) // get-consul-client-ca command talks to the Consul servers diff --git a/control-plane/subcommand/gossip-encryption-autogenerate/command.go b/control-plane/subcommand/gossip-encryption-autogenerate/command.go index e14f24f847..cf871eca69 100644 --- a/control-plane/subcommand/gossip-encryption-autogenerate/command.go +++ b/control-plane/subcommand/gossip-encryption-autogenerate/command.go @@ -11,16 +11,15 @@ import ( "fmt" "sync" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/inject-connect/command.go b/control-plane/subcommand/inject-connect/command.go index 1a8a32901c..6767e60130 100644 --- a/control-plane/subcommand/inject-connect/command.go +++ b/control-plane/subcommand/inject-connect/command.go @@ -15,6 +15,20 @@ import ( "sync" "syscall" + gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" + gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" + apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" + "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" + "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" + "github.com/hashicorp/consul-k8s/control-plane/controllers" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/mitchellh/cli" "go.uber.org/zap/zapcore" @@ -27,15 +41,9 @@ import ( "k8s.io/client-go/rest" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" gwv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( @@ -56,7 +64,6 @@ type Command struct { flagEnableWebhookCAUpdate bool flagLogLevel string flagLogJSON bool - flagResourceAPIs bool // Use V2 APIs flagAllowK8sNamespacesList []string // K8s namespaces to explicitly inject flagDenyK8sNamespacesList []string // K8s namespaces to deny injection (has precedence) @@ -134,18 +141,6 @@ type Command struct { clientset kubernetes.Interface - // sidecarProxy* are resource limits that are parsed and validated from other flags - // these are individual members because there are override annotations - sidecarProxyCPULimit resource.Quantity - sidecarProxyCPURequest resource.Quantity - sidecarProxyMemoryLimit resource.Quantity - sidecarProxyMemoryRequest resource.Quantity - - // static resources requirements for connect-init - initContainerResources corev1.ResourceRequirements - - caCertPem []byte - once sync.Once help string } @@ -157,16 +152,10 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - // We need v1alpha1 here to add the peering api to the scheme utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(gwv1beta1.AddToScheme(scheme)) utilruntime.Must(gwv1alpha2.AddToScheme(scheme)) - - // V2 resources - utilruntime.Must(authv2beta1.AddAuthToScheme(scheme)) - utilruntime.Must(meshv2beta1.AddMeshToScheme(scheme)) - //+kubebuilder:scaffold:scheme } @@ -233,8 +222,6 @@ func (c *Command) init() { "%q, %q, %q, and %q.", zapcore.DebugLevel.String(), zapcore.InfoLevel.String(), zapcore.WarnLevel.String(), zapcore.ErrorLevel.String())) c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, "Enable or disable JSON output format for logging.") - c.flagSet.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable or disable Consul V2 Resource APIs.") // Proxy sidecar resource setting flags. c.flagSet.StringVar(&c.flagDefaultSidecarProxyCPURequest, "default-sidecar-proxy-cpu-request", "", "Default sidecar proxy CPU request.") @@ -287,13 +274,67 @@ func (c *Command) Run(args []string) int { return 1 } - if err := c.parseAndValidateSidecarProxyFlags(); err != nil { + // Proxy resources. + var sidecarProxyCPULimit, sidecarProxyCPURequest, sidecarProxyMemoryLimit, sidecarProxyMemoryRequest resource.Quantity + var err error + if c.flagDefaultSidecarProxyCPURequest != "" { + sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-request is invalid: %s", err)) + return 1 + } + } + + if c.flagDefaultSidecarProxyCPULimit != "" { + sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-cpu-limit is invalid: %s", err)) + return 1 + } + } + if sidecarProxyCPULimit.Value() != 0 && sidecarProxyCPURequest.Cmp(sidecarProxyCPULimit) > 0 { + c.UI.Error(fmt.Sprintf( + "request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", + c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit)) + return 1 + } + + if c.flagDefaultSidecarProxyMemoryRequest != "" { + sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-request is invalid: %s", err)) + return 1 + } + } + if c.flagDefaultSidecarProxyMemoryLimit != "" { + sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) + if err != nil { + c.UI.Error(fmt.Sprintf("-default-sidecar-proxy-memory-limit is invalid: %s", err)) + return 1 + } + } + if sidecarProxyMemoryLimit.Value() != 0 && sidecarProxyMemoryRequest.Cmp(sidecarProxyMemoryLimit) > 0 { + c.UI.Error(fmt.Sprintf( + "request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", + c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit)) + return 1 + } + + // Validate ports in metrics flags. + err = common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) + if err != nil { c.UI.Error(err.Error()) return 1 } // Validate resource request/limit flags and parse into corev1.ResourceRequirements - if err := c.parseAndValidateResourceFlags(); err != nil { + initResources, err := c.parseAndValidateResourceFlags() + if err != nil { c.UI.Error(err.Error()) return 1 } @@ -312,6 +353,10 @@ func (c *Command) Run(args []string) int { } } + // Convert allow/deny lists to sets. + allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) + denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) + zapLogger, err := common.ZapLogger(c.flagLogLevel, c.flagLogJSON) if err != nil { c.UI.Error(fmt.Sprintf("Error setting up logging: %s", err.Error())) @@ -338,9 +383,13 @@ func (c *Command) Run(args []string) int { return 1 } + // Create Consul API config object. + consulConfig := c.consul.ConsulClientConfig() + + var caCertPem []byte if c.consul.CACertFile != "" { var err error - c.caCertPem, err = os.ReadFile(c.consul.CACertFile) + caCertPem, err = os.ReadFile(c.consul.CACertFile) if err != nil { c.UI.Error(fmt.Sprintf("error reading Consul's CA cert file %q", c.consul.CACertFile)) return 1 @@ -357,14 +406,14 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) return 1 } - - watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog.Named("consul-server-connection-manager")) + watcher, err := discovery.NewWatcher(ctx, serverConnMgrCfg, hcLog) if err != nil { c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) return 1 } - defer watcher.Stop() + go watcher.Run() + defer watcher.Stop() // This is a blocking command that is run in order to ensure we only start the // connect-inject controllers only after we have access to the Consul server. @@ -389,18 +438,436 @@ func (c *Command) Run(args []string) int { return 1 } - // Right now we exclusively start controllers for V1 or V2. - // In the future we might add a flag to pick and choose from both. - if c.flagResourceAPIs { - err = c.configureV2Controllers(ctx, mgr, watcher) - } else { - err = c.configureV1Controllers(ctx, mgr, watcher) + lifecycleConfig := lifecycle.Config{ + DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, + DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, + DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, + DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, + DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, + } + + metricsConfig := metrics.Config{ + DefaultEnableMetrics: c.flagDefaultEnableMetrics, + EnableGatewayMetrics: c.flagEnableGatewayMetrics, + DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, + DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, + DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, + DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, + } + + if err = (&endpoints.Controller{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + MetricsConfig: metricsConfig, + EnableConsulPartitions: c.flagEnablePartitions, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableWANFederation: c.flagEnableFederation, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + AuthMethod: c.flagACLAuthMethod, + NodeMeta: c.flagNodeMeta, + Log: ctrl.Log.WithName("controller").WithName("endpoints"), + Scheme: mgr.GetScheme(), + ReleaseName: c.flagReleaseName, + ReleaseNamespace: c.flagReleaseNamespace, + EnableAutoEncrypt: c.flagEnableAutoEncrypt, + EnableTelemetryCollector: c.flagEnableTelemetryCollector, + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) + return 1 } + + // API Gateway Controllers + if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { + setupLog.Error(err, "unable to register field indexes") + return 1 + } + + if err = (&gatewaycontrollers.GatewayClassConfigController{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName("gateways"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) + return 1 + } + + if err := (&gatewaycontrollers.GatewayClassController{ + ControllerName: gatewaycommon.GatewayClassControllerName, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), + }).SetupWithManager(ctx, mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") + return 1 + } + + cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ + HelmConfig: gatewaycommon.HelmConfig{ + ConsulConfig: gatewaycommon.ConsulConfig{ + Address: c.consul.Addresses, + GRPCPort: consulConfig.GRPCPort, + HTTPPort: consulConfig.HTTPPort, + APITimeout: consulConfig.APITimeout, + }, + ImageDataplane: c.flagConsulDataplaneImage, + ImageConsulK8S: c.flagConsulK8sImage, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, + EnableNamespaces: c.flagEnableNamespaces, + PeeringEnabled: c.flagEnablePeering, + EnableOpenShift: c.flagEnableOpenShift, + EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, + AuthMethod: c.consul.ConsulLogin.AuthMethod, + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + TLSEnabled: c.consul.UseTLS, + ConsulTLSServerName: c.consul.TLSServerName, + ConsulPartition: c.consul.Partition, + ConsulCACert: string(caCertPem), + }, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + NamespacesEnabled: c.flagEnableNamespaces, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + Partition: c.consul.Partition, + Datacenter: c.consul.Datacenter, + }) + if err != nil { - setupLog.Error(err, fmt.Sprintf("could not configure controllers: %s", err.Error())) + setupLog.Error(err, "unable to create controller", "controller", "Gateway") + return 1 + } + + go cache.Run(ctx) + + // wait for the cache to fill + setupLog.Info("waiting for Consul cache sync") + cache.WaitSynced(ctx) + setupLog.Info("Consul cache synced") + + configEntryReconciler := &controllers.ConfigEntryController{ + ConsulClientConfig: c.consul.ConsulClientConfig(), + ConsulServerConnMgr: watcher, + DatacenterName: c.consul.Datacenter, + EnableConsulNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableNSMirroring: c.flagEnableK8SNSMirroring, + NSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, + } + if err = (&controllers.ServiceDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) + return 1 + } + if err = (&controllers.ServiceResolverController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) + return 1 + } + if err = (&controllers.ProxyDefaultsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) + return 1 + } + if err = (&controllers.MeshController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) + return 1 + } + if err = (&controllers.ExportedServicesController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) + return 1 + } + if err = (&controllers.ServiceRouterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) + return 1 + } + if err = (&controllers.ServiceSplitterController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) + return 1 + } + if err = (&controllers.ServiceIntentionsController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) + return 1 + } + if err = (&controllers.IngressGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) + return 1 + } + if err = (&controllers.TerminatingGatewayController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) + return 1 + } + if err = (&controllers.SamenessGroupController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) + return 1 + } + if err = (&controllers.JWTProviderController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) + return 1 + } + if err = (&controllers.ControlPlaneRequestLimitController{ + ConfigEntryController: configEntryReconciler, + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) return 1 } + if err = mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { + setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) + return 1 + } + + if c.flagEnablePeering { + if err = (&peering.AcceptorController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", + ReleaseNamespace: c.flagReleaseNamespace, + Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") + return 1 + } + if err = (&peering.PeeringDialerController{ + Client: mgr.GetClient(), + ConsulClientConfig: consulConfig, + ConsulServerConnMgr: watcher, + Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), + Scheme: mgr.GetScheme(), + Context: ctx, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") + return 1 + } + + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), + }}) + } + + mgr.GetWebhookServer().CertDir = c.flagCertDir + + mgr.GetWebhookServer().Register("/mutate", + &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ + Clientset: c.clientset, + ReleaseNamespace: c.flagReleaseNamespace, + ConsulConfig: consulConfig, + ConsulServerConnMgr: watcher, + ImageConsul: c.flagConsulImage, + ImageConsulDataplane: c.flagConsulDataplaneImage, + EnvoyExtraArgs: c.flagEnvoyExtraArgs, + ImageConsulK8S: c.flagConsulK8sImage, + RequireAnnotation: !c.flagDefaultInject, + AuthMethod: c.flagACLAuthMethod, + ConsulCACert: string(caCertPem), + TLSEnabled: c.consul.UseTLS, + ConsulAddress: c.consul.Addresses, + SkipServerWatch: c.consul.SkipServerWatch, + ConsulTLSServerName: c.consul.TLSServerName, + DefaultProxyCPURequest: sidecarProxyCPURequest, + DefaultProxyCPULimit: sidecarProxyCPULimit, + DefaultProxyMemoryRequest: sidecarProxyMemoryRequest, + DefaultProxyMemoryLimit: sidecarProxyMemoryLimit, + DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, + LifecycleConfig: lifecycleConfig, + MetricsConfig: metricsConfig, + InitContainerResources: initResources, + ConsulPartition: c.consul.Partition, + AllowK8sNamespacesSet: allowK8sNamespaces, + DenyK8sNamespacesSet: denyK8sNamespaces, + EnableNamespaces: c.flagEnableNamespaces, + ConsulDestinationNamespace: c.flagConsulDestinationNamespace, + EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, + K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, + CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, + EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, + EnableCNI: c.flagEnableCNI, + TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, + EnableConsulDNS: c.flagEnableConsulDNS, + EnableOpenShift: c.flagEnableOpenShift, + Log: ctrl.Log.WithName("handler").WithName("connect"), + LogLevel: c.flagLogLevel, + LogJSON: c.flagLogJSON, + }}) + + consulMeta := apicommon.ConsulMeta{ + PartitionsEnabled: c.flagEnablePartitions, + Partition: c.consul.Partition, + NamespacesEnabled: c.flagEnableNamespaces, + DestinationNamespace: c.flagConsulDestinationNamespace, + Mirroring: c.flagEnableK8SNSMirroring, + Prefix: c.flagK8SNSMirroringPrefix, + } + + // Note: The path here should be identical to the one on the kubebuilder + // annotation in each webhook file. + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), + ConsulMeta: consulMeta, + }}) + mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", + &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ + Client: mgr.GetClient(), + Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), + ConsulMeta: consulMeta, + }}) + + if c.flagEnableWebhookCAUpdate { + err = c.updateWebhookCABundle(ctx) + if err != nil { + setupLog.Error(err, "problem getting CA Cert") + return 1 + } + } + if err = mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") return 1 @@ -409,6 +876,20 @@ func (c *Command) Run(args []string) int { return 0 } +func (c *Command) updateWebhookCABundle(ctx context.Context) error { + webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) + caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) + caCert, err := os.ReadFile(caPath) + if err != nil { + return err + } + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) + if err != nil { + return err + } + return nil +} + func (c *Command) validateFlags() error { if c.flagConsulK8sImage == "" { return errors.New("-consul-k8s-image must be set") @@ -432,95 +913,48 @@ func (c *Command) validateFlags() error { return errors.New("-default-envoy-proxy-concurrency must be >= 0 if set") } - // Validate ports in metrics flags. - err := common.ValidateUnprivilegedPort("-default-merged-metrics-port", c.flagDefaultMergedMetricsPort) - if err != nil { - return err - } - err = common.ValidateUnprivilegedPort("-default-prometheus-scrape-port", c.flagDefaultPrometheusScrapePort) - if err != nil { - return err - } - return nil } -func (c *Command) parseAndValidateSidecarProxyFlags() error { - var err error - - if c.flagDefaultSidecarProxyCPURequest != "" { - c.sidecarProxyCPURequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPURequest) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-cpu-request is invalid: %w", err) - } - } - - if c.flagDefaultSidecarProxyCPULimit != "" { - c.sidecarProxyCPULimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyCPULimit) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-cpu-limit is invalid: %w", err) - } - } - if c.sidecarProxyCPULimit.Value() != 0 && c.sidecarProxyCPURequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { - return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-cpu-request value of %q is greater than the -default-sidecar-proxy-cpu-limit value of %q", - c.flagDefaultSidecarProxyCPURequest, c.flagDefaultSidecarProxyCPULimit) - } - - if c.flagDefaultSidecarProxyMemoryRequest != "" { - c.sidecarProxyMemoryRequest, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryRequest) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-memory-request is invalid: %w", err) - } - } - if c.flagDefaultSidecarProxyMemoryLimit != "" { - c.sidecarProxyMemoryLimit, err = resource.ParseQuantity(c.flagDefaultSidecarProxyMemoryLimit) - if err != nil { - return fmt.Errorf("-default-sidecar-proxy-memory-limit is invalid: %w", err) - } - } - if c.sidecarProxyMemoryLimit.Value() != 0 && c.sidecarProxyMemoryRequest.Cmp(c.sidecarProxyMemoryLimit) > 0 { - return fmt.Errorf("request must be <= limit: -default-sidecar-proxy-memory-request value of %q is greater than the -default-sidecar-proxy-memory-limit value of %q", - c.flagDefaultSidecarProxyMemoryRequest, c.flagDefaultSidecarProxyMemoryLimit) - } - - return nil -} - -func (c *Command) parseAndValidateResourceFlags() error { +func (c *Command) parseAndValidateResourceFlags() (corev1.ResourceRequirements, error) { // Init container var initContainerCPULimit, initContainerCPURequest, initContainerMemoryLimit, initContainerMemoryRequest resource.Quantity // Parse and validate the initContainer resources. initContainerCPURequest, err := resource.ParseQuantity(c.flagInitContainerCPURequest) if err != nil { - return fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-cpu-request '%s' is invalid: %s", c.flagInitContainerCPURequest, err) } initContainerCPULimit, err = resource.ParseQuantity(c.flagInitContainerCPULimit) if err != nil { - return fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-cpu-limit '%s' is invalid: %s", c.flagInitContainerCPULimit, err) } if initContainerCPULimit.Value() != 0 && initContainerCPURequest.Cmp(initContainerCPULimit) > 0 { - return fmt.Errorf( + return corev1.ResourceRequirements{}, fmt.Errorf( "request must be <= limit: -init-container-cpu-request value of %q is greater than the -init-container-cpu-limit value of %q", c.flagInitContainerCPURequest, c.flagInitContainerCPULimit) } initContainerMemoryRequest, err = resource.ParseQuantity(c.flagInitContainerMemoryRequest) if err != nil { - return fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-memory-request '%s' is invalid: %s", c.flagInitContainerMemoryRequest, err) } initContainerMemoryLimit, err = resource.ParseQuantity(c.flagInitContainerMemoryLimit) if err != nil { - return fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) + return corev1.ResourceRequirements{}, + fmt.Errorf("-init-container-memory-limit '%s' is invalid: %s", c.flagInitContainerMemoryLimit, err) } if initContainerMemoryLimit.Value() != 0 && initContainerMemoryRequest.Cmp(initContainerMemoryLimit) > 0 { - return fmt.Errorf( + return corev1.ResourceRequirements{}, fmt.Errorf( "request must be <= limit: -init-container-memory-request value of %q is greater than the -init-container-memory-limit value of %q", c.flagInitContainerMemoryRequest, c.flagInitContainerMemoryLimit) } // Put into corev1.ResourceRequirements form - c.initContainerResources = corev1.ResourceRequirements{ + initResources := corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: initContainerCPURequest, corev1.ResourceMemory: initContainerMemoryRequest, @@ -531,7 +965,7 @@ func (c *Command) parseAndValidateResourceFlags() error { }, } - return nil + return initResources, nil } func (c *Command) Synopsis() string { return synopsis } diff --git a/control-plane/subcommand/inject-connect/v1controllers.go b/control-plane/subcommand/inject-connect/v1controllers.go deleted file mode 100644 index c407c99ccf..0000000000 --- a/control-plane/subcommand/inject-connect/v1controllers.go +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connectinject - -import ( - "context" - "fmt" - "os" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - - gatewaycommon "github.com/hashicorp/consul-k8s/control-plane/api-gateway/common" - gatewaycontrollers "github.com/hashicorp/consul-k8s/control-plane/api-gateway/controllers" - apicommon "github.com/hashicorp/consul-k8s/control-plane/api/common" - "github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllers" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpoints" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/peering" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -func (c *Command) configureV1Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Convert allow/deny lists to sets. - allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - - lifecycleConfig := lifecycle.Config{ - DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - } - - metricsConfig := metrics.Config{ - DefaultEnableMetrics: c.flagDefaultEnableMetrics, - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - } - - if err := (&endpoints.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - MetricsConfig: metricsConfig, - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableWANFederation: c.flagEnableFederation, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - NodeMeta: c.flagNodeMeta, - Log: ctrl.Log.WithName("controller").WithName("endpoints"), - Scheme: mgr.GetScheme(), - ReleaseName: c.flagReleaseName, - ReleaseNamespace: c.flagReleaseNamespace, - EnableAutoEncrypt: c.flagEnableAutoEncrypt, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", endpoints.Controller{}) - return err - } - - // API Gateway Controllers - if err := gatewaycontrollers.RegisterFieldIndexes(ctx, mgr); err != nil { - setupLog.Error(err, "unable to register field indexes") - return err - } - - if err := (&gatewaycontrollers.GatewayClassConfigController{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName("gateways"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", gatewaycontrollers.GatewayClassConfigController{}) - return err - } - - if err := (&gatewaycontrollers.GatewayClassController{ - ControllerName: gatewaycommon.GatewayClassControllerName, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("GatewayClass"), - }).SetupWithManager(ctx, mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "GatewayClass") - return err - } - - cache, err := gatewaycontrollers.SetupGatewayControllerWithManager(ctx, mgr, gatewaycontrollers.GatewayControllerConfig{ - HelmConfig: gatewaycommon.HelmConfig{ - ConsulConfig: gatewaycommon.ConsulConfig{ - Address: c.consul.Addresses, - GRPCPort: consulConfig.GRPCPort, - HTTPPort: consulConfig.HTTPPort, - APITimeout: consulConfig.APITimeout, - }, - ImageDataplane: c.flagConsulDataplaneImage, - ImageConsulK8S: c.flagConsulK8sImage, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - NamespaceMirroringPrefix: c.flagK8SNSMirroringPrefix, - EnableNamespaces: c.flagEnableNamespaces, - PeeringEnabled: c.flagEnablePeering, - EnableOpenShift: c.flagEnableOpenShift, - EnableNamespaceMirroring: c.flagEnableK8SNSMirroring, - AuthMethod: c.consul.ConsulLogin.AuthMethod, - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - TLSEnabled: c.consul.UseTLS, - ConsulTLSServerName: c.consul.TLSServerName, - ConsulPartition: c.consul.Partition, - ConsulCACert: string(c.caCertPem), - }, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - NamespacesEnabled: c.flagEnableNamespaces, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Partition: c.consul.Partition, - Datacenter: c.consul.Datacenter, - }) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Gateway") - return err - } - - go cache.Run(ctx) - - // wait for the cache to fill - setupLog.Info("waiting for Consul cache sync") - cache.WaitSynced(ctx) - setupLog.Info("Consul cache synced") - - configEntryReconciler := &controllers.ConfigEntryController{ - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - DatacenterName: c.consul.Datacenter, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNSACLPolicy: c.flagCrossNamespaceACLPolicy, - } - if err := (&controllers.ServiceDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceDefaults) - return err - } - if err := (&controllers.ServiceResolverController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceResolver), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceResolver) - return err - } - if err := (&controllers.ProxyDefaultsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ProxyDefaults), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ProxyDefaults) - return err - } - if err := (&controllers.MeshController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.Mesh), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.Mesh) - return err - } - if err := (&controllers.ExportedServicesController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ExportedServices), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ExportedServices) - return err - } - if err := (&controllers.ServiceRouterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceRouter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceRouter) - return err - } - if err := (&controllers.ServiceSplitterController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceSplitter), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceSplitter) - return err - } - if err := (&controllers.ServiceIntentionsController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ServiceIntentions), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ServiceIntentions) - return err - } - if err := (&controllers.IngressGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.IngressGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.IngressGateway) - return err - } - if err := (&controllers.TerminatingGatewayController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.TerminatingGateway), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.TerminatingGateway) - return err - } - if err := (&controllers.SamenessGroupController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.SamenessGroup), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.SamenessGroup) - return err - } - if err := (&controllers.JWTProviderController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.JWTProvider), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.JWTProvider) - return err - } - if err := (&controllers.ControlPlaneRequestLimitController{ - ConfigEntryController: configEntryReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(apicommon.ControlPlaneRequestLimit), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", apicommon.ControlPlaneRequestLimit) - return err - } - - if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { - setupLog.Error(err, "unable to create readiness check", "controller", endpoints.Controller{}) - return err - } - - if c.flagEnablePeering { - if err := (&peering.AcceptorController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - ExposeServersServiceName: c.flagResourcePrefix + "-expose-servers", - ReleaseNamespace: c.flagReleaseNamespace, - Log: ctrl.Log.WithName("controller").WithName("peering-acceptor"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-acceptor") - return err - } - if err := (&peering.PeeringDialerController{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - Log: ctrl.Log.WithName("controller").WithName("peering-dialer"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "peering-dialer") - return err - } - - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringacceptors", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringAcceptorWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-acceptor"), - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-peeringdialers", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.PeeringDialerWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName("peering-dialer"), - }}) - } - - mgr.GetWebhookServer().CertDir = c.flagCertDir - - mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhook.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(c.caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: c.sidecarProxyCPURequest, - DefaultProxyCPULimit: c.sidecarProxyCPULimit, - DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: c.initContainerResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("connect"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - }}) - - consulMeta := apicommon.ConsulMeta{ - PartitionsEnabled: c.flagEnablePartitions, - Partition: c.consul.Partition, - NamespacesEnabled: c.flagEnableNamespaces, - DestinationNamespace: c.flagConsulDestinationNamespace, - Mirroring: c.flagEnableK8SNSMirroring, - Prefix: c.flagK8SNSMirroringPrefix, - } - - // Note: The path here should be identical to the one on the kubebuilder - // annotation in each webhook file. - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicedefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceresolver", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceResolverWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceResolver), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-proxydefaults", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ProxyDefaultsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ProxyDefaults), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-mesh", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.MeshWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.Mesh), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-exportedservices", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ExportedServicesWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ExportedServices), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicerouter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceRouterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceRouter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-servicesplitter", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceSplitterWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceSplitter), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-serviceintentions", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ServiceIntentionsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ServiceIntentions), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-ingressgateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.IngressGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.IngressGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-terminatinggateway", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.TerminatingGatewayWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.TerminatingGateway), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-samenessgroup", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.SamenessGroupWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.SamenessGroup), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-jwtprovider", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.JWTProviderWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.JWTProvider), - ConsulMeta: consulMeta, - }}) - mgr.GetWebhookServer().Register("/mutate-v1alpha1-controlplanerequestlimits", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.ControlPlaneRequestLimitWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.ControlPlaneRequestLimit), - ConsulMeta: consulMeta, - }}) - - mgr.GetWebhookServer().Register("/validate-v1alpha1-gatewaypolicy", - &ctrlRuntimeWebhook.Admission{Handler: &v1alpha1.GatewayPolicyWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(apicommon.GatewayPolicy), - ConsulMeta: consulMeta, - }}) - - if c.flagEnableWebhookCAUpdate { - err = c.updateWebhookCABundle(ctx) - if err != nil { - setupLog.Error(err, "problem getting CA Cert") - return err - } - } - - return nil -} - -func (c *Command) updateWebhookCABundle(ctx context.Context) error { - webhookConfigName := fmt.Sprintf("%s-connect-injector", c.flagResourcePrefix) - caPath := fmt.Sprintf("%s/%s", c.flagCertDir, WebhookCAFilename) - caCert, err := os.ReadFile(caPath) - if err != nil { - return err - } - err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, webhookConfigName, caCert) - if err != nil { - return err - } - return nil -} diff --git a/control-plane/subcommand/inject-connect/v2controllers.go b/control-plane/subcommand/inject-connect/v2controllers.go deleted file mode 100644 index abe4b38c53..0000000000 --- a/control-plane/subcommand/inject-connect/v2controllers.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package connectinject - -import ( - "context" - - "github.com/hashicorp/consul-server-connection-manager/discovery" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/manager" - ctrlRuntimeWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" - - authv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/auth/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/api/common" - meshv2beta1 "github.com/hashicorp/consul-k8s/control-plane/api/mesh/v2beta1" - "github.com/hashicorp/consul-k8s/control-plane/config-entries/controllersv2" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/endpointsv2" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/pod" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/controllers/serviceaccount" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/lifecycle" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/metrics" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/namespace" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhook" - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/webhookv2" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" -) - -func (c *Command) configureV2Controllers(ctx context.Context, mgr manager.Manager, watcher *discovery.Watcher) error { - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Convert allow/deny lists to sets. - allowK8sNamespaces := flags.ToSet(c.flagAllowK8sNamespacesList) - denyK8sNamespaces := flags.ToSet(c.flagDenyK8sNamespacesList) - k8sNsConfig := common.K8sNamespaceConfig{ - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - } - consulTenancyConfig := common.ConsulTenancyConfig{ - EnableConsulPartitions: c.flagEnablePartitions, - EnableConsulNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - ConsulPartition: c.consul.Partition, - } - - lifecycleConfig := lifecycle.Config{ - DefaultEnableProxyLifecycle: c.flagDefaultEnableSidecarProxyLifecycle, - DefaultEnableShutdownDrainListeners: c.flagDefaultEnableSidecarProxyLifecycleShutdownDrainListeners, - DefaultShutdownGracePeriodSeconds: c.flagDefaultSidecarProxyLifecycleShutdownGracePeriodSeconds, - DefaultGracefulPort: c.flagDefaultSidecarProxyLifecycleGracefulPort, - DefaultGracefulShutdownPath: c.flagDefaultSidecarProxyLifecycleGracefulShutdownPath, - } - - metricsConfig := metrics.Config{ - DefaultEnableMetrics: c.flagDefaultEnableMetrics, - EnableGatewayMetrics: c.flagEnableGatewayMetrics, - DefaultEnableMetricsMerging: c.flagDefaultEnableMetricsMerging, - DefaultMergedMetricsPort: c.flagDefaultMergedMetricsPort, - DefaultPrometheusScrapePort: c.flagDefaultPrometheusScrapePort, - DefaultPrometheusScrapePath: c.flagDefaultPrometheusScrapePath, - } - - if err := (&pod.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - AuthMethod: c.flagACLAuthMethod, - MetricsConfig: metricsConfig, - EnableTelemetryCollector: c.flagEnableTelemetryCollector, - Log: ctrl.Log.WithName("controller").WithName("pod"), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", pod.Controller{}) - return err - } - - endpointsLogger := ctrl.Log.WithName("controller").WithName("endpoints") - if err := (&endpointsv2.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - WriteCache: endpointsv2.NewWriteCache(endpointsLogger), - Log: endpointsLogger, - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", endpointsv2.Controller{}) - return err - } - - if err := (&serviceaccount.Controller{ - Client: mgr.GetClient(), - ConsulServerConnMgr: watcher, - K8sNamespaceConfig: k8sNsConfig, - ConsulTenancyConfig: consulTenancyConfig, - Log: ctrl.Log.WithName("controller").WithName("serviceaccount"), - Scheme: mgr.GetScheme(), - Context: ctx, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", serviceaccount.Controller{}) - return err - } - - if c.flagEnableNamespaces { - err := (&namespace.Controller{ - Client: mgr.GetClient(), - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableNSMirroring: c.flagEnableK8SNSMirroring, - NSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - Log: ctrl.Log.WithName("controller").WithName("namespace"), - }).SetupWithManager(mgr) - if err != nil { - setupLog.Error(err, "unable to create controller", "controller", namespace.Controller{}) - return err - } - } - - meshConfigReconciler := &controllersv2.MeshConfigController{ - ConsulClientConfig: consulConfig, - ConsulServerConnMgr: watcher, - ConsulTenancyConfig: consulTenancyConfig, - } - if err := (&controllersv2.TrafficPermissionsController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TrafficPermissions), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.TrafficPermissions) - return err - } - if err := (&controllersv2.GRPCRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.GRPCRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.GRPCRoute) - return err - } - if err := (&controllersv2.HTTPRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.HTTPRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.HTTPRoute) - return err - } - if err := (&controllersv2.TCPRouteController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.TCPRoute), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.TCPRoute) - return err - } - if err := (&controllersv2.ProxyConfigurationController{ - MeshConfigController: meshConfigReconciler, - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controller").WithName(common.ProxyConfiguration), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", common.ProxyConfiguration) - return err - } - - mgr.GetWebhookServer().CertDir = c.flagCertDir - - mgr.GetWebhookServer().Register("/mutate", - &ctrlRuntimeWebhook.Admission{Handler: &webhookv2.MeshWebhook{ - Clientset: c.clientset, - ReleaseNamespace: c.flagReleaseNamespace, - ConsulConfig: consulConfig, - ConsulServerConnMgr: watcher, - ImageConsul: c.flagConsulImage, - ImageConsulDataplane: c.flagConsulDataplaneImage, - EnvoyExtraArgs: c.flagEnvoyExtraArgs, - ImageConsulK8S: c.flagConsulK8sImage, - RequireAnnotation: !c.flagDefaultInject, - AuthMethod: c.flagACLAuthMethod, - ConsulCACert: string(c.caCertPem), - TLSEnabled: c.consul.UseTLS, - ConsulAddress: c.consul.Addresses, - SkipServerWatch: c.consul.SkipServerWatch, - ConsulTLSServerName: c.consul.TLSServerName, - DefaultProxyCPURequest: c.sidecarProxyCPURequest, - DefaultProxyCPULimit: c.sidecarProxyCPULimit, - DefaultProxyMemoryRequest: c.sidecarProxyMemoryRequest, - DefaultProxyMemoryLimit: c.sidecarProxyMemoryLimit, - DefaultEnvoyProxyConcurrency: c.flagDefaultEnvoyProxyConcurrency, - LifecycleConfig: lifecycleConfig, - MetricsConfig: metricsConfig, - InitContainerResources: c.initContainerResources, - ConsulPartition: c.consul.Partition, - AllowK8sNamespacesSet: allowK8sNamespaces, - DenyK8sNamespacesSet: denyK8sNamespaces, - EnableNamespaces: c.flagEnableNamespaces, - ConsulDestinationNamespace: c.flagConsulDestinationNamespace, - EnableK8SNSMirroring: c.flagEnableK8SNSMirroring, - K8SNSMirroringPrefix: c.flagK8SNSMirroringPrefix, - CrossNamespaceACLPolicy: c.flagCrossNamespaceACLPolicy, - EnableTransparentProxy: c.flagDefaultEnableTransparentProxy, - EnableCNI: c.flagEnableCNI, - TProxyOverwriteProbes: c.flagTransparentProxyDefaultOverwriteProbes, - EnableConsulDNS: c.flagEnableConsulDNS, - EnableOpenShift: c.flagEnableOpenShift, - Log: ctrl.Log.WithName("handler").WithName("consul-mesh"), - LogLevel: c.flagLogLevel, - LogJSON: c.flagLogJSON, - }}) - - mgr.GetWebhookServer().Register("/mutate-v2beta1-trafficpermissions", - &ctrlRuntimeWebhook.Admission{Handler: &authv2beta1.TrafficPermissionsWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.TrafficPermissions), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-proxyconfigurations", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.ProxyConfigurationWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.ProxyConfiguration), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-httproute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.HTTPRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.HTTPRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-grpcroute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.GRPCRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.GRPCRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - mgr.GetWebhookServer().Register("/mutate-v2beta1-tcproute", - &ctrlRuntimeWebhook.Admission{Handler: &meshv2beta1.TCPRouteWebhook{ - Client: mgr.GetClient(), - Logger: ctrl.Log.WithName("webhooks").WithName(common.TCPRoute), - ConsulTenancyConfig: consulTenancyConfig, - }}) - - if err := mgr.AddReadyzCheck("ready", webhook.ReadinessCheck{CertDir: c.flagCertDir}.Ready); err != nil { - setupLog.Error(err, "unable to create readiness check") - return err - } - - if c.flagEnableWebhookCAUpdate { - err := c.updateWebhookCABundle(ctx) - if err != nil { - setupLog.Error(err, "problem getting CA Cert") - return err - } - } - - return nil -} diff --git a/control-plane/subcommand/install-cni/command.go b/control-plane/subcommand/install-cni/command.go index c626ee006b..53abe7cda1 100644 --- a/control-plane/subcommand/install-cni/command.go +++ b/control-plane/subcommand/install-cni/command.go @@ -16,11 +16,10 @@ import ( "github.com/fsnotify/fsnotify" "github.com/hashicorp/consul-k8s/control-plane/cni/config" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" ) const ( diff --git a/control-plane/subcommand/install-cni/command_test.go b/control-plane/subcommand/install-cni/command_test.go index 4083ba5153..d5ee65f928 100644 --- a/control-plane/subcommand/install-cni/command_test.go +++ b/control-plane/subcommand/install-cni/command_test.go @@ -12,11 +12,10 @@ import ( "time" "github.com/hashicorp/consul-k8s/control-plane/cni/config" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/serf/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) func TestRun_FlagDefaults(t *testing.T) { diff --git a/control-plane/subcommand/mesh-init/command.go b/control-plane/subcommand/mesh-init/command.go deleted file mode 100644 index 6c21210e9b..0000000000 --- a/control-plane/subcommand/mesh-init/command.go +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package meshinit - -import ( - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "net" - "os" - "os/signal" - "sync" - "syscall" - "time" - - "github.com/cenkalti/backoff" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - "github.com/hashicorp/consul-k8s/control-plane/version" -) - -const ( - // The number of times to attempt to read this proxy registration (120s). - defaultMaxPollingRetries = 120 -) - -type Command struct { - UI cli.Ui - - flagProxyName string - - maxPollingAttempts uint64 // Number of times to poll Consul for proxy registrations. - - flagRedirectTrafficConfig string - flagLogLevel string - flagLogJSON bool - - flagSet *flag.FlagSet - consul *flags.ConsulFlags - - once sync.Once - help string - logger hclog.Logger - - watcher *discovery.Watcher - - // Only used in tests. - iptablesProvider iptables.Provider - iptablesConfig iptables.Config -} - -func (c *Command) init() { - c.flagSet = flag.NewFlagSet("", flag.ContinueOnError) - - // V2 Flags - c.flagSet.StringVar(&c.flagProxyName, "proxy-name", os.Getenv("PROXY_NAME"), "The Consul proxy name. This is the K8s Pod name, which is also the name of the Workload in Consul. (Required)") - - // Universal flags - c.flagSet.StringVar(&c.flagRedirectTrafficConfig, "redirect-traffic-config", os.Getenv("CONSUL_REDIRECT_TRAFFIC_CONFIG"), "Config (in JSON format) to configure iptables for this pod.") - c.flagSet.StringVar(&c.flagLogLevel, "log-level", "info", - "Log verbosity level. Supported values (in order of detail) are \"trace\", "+ - "\"debug\", \"info\", \"warn\", and \"error\".") - c.flagSet.BoolVar(&c.flagLogJSON, "log-json", false, - "Enable or disable JSON output format for logging.") - - if c.maxPollingAttempts == 0 { - c.maxPollingAttempts = defaultMaxPollingRetries - } - - c.consul = &flags.ConsulFlags{} - flags.Merge(c.flagSet, c.consul.Flags()) - c.help = flags.Usage(help, c.flagSet) -} - -func (c *Command) Run(args []string) int { - c.once.Do(c.init) - - if err := c.flagSet.Parse(args); err != nil { - return 1 - } - // Validate flags - if err := c.validateFlags(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if c.consul.Namespace == "" { - c.consul.Namespace = constants.DefaultConsulNS - } - if c.consul.Partition == "" { - c.consul.Partition = constants.DefaultConsulPartition - } - - // Set up logging. - if c.logger == nil { - var err error - c.logger, err = common.Logger(c.flagLogLevel, c.flagLogJSON) - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - } - - // Create Consul API config object. - consulConfig := c.consul.ConsulClientConfig() - - // Create a context to be used by the processes started in this command. - ctx, cancelFunc := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) - defer cancelFunc() - - // Start Consul server Connection manager. - serverConnMgrCfg, err := c.consul.ConsulServerConnMgrConfig() - // Disable server watch because we only need to get server IPs once. - serverConnMgrCfg.ServerWatchDisabled = true - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create config for consul-server-connection-manager: %s", err)) - return 1 - } - if c.watcher == nil { - c.watcher, err = discovery.NewWatcher(ctx, serverConnMgrCfg, c.logger.Named("consul-server-connection-manager")) - if err != nil { - c.UI.Error(fmt.Sprintf("unable to create Consul server watcher: %s", err)) - return 1 - } - go c.watcher.Run() // The actual ACL login happens here - defer c.watcher.Stop() - } - - state, err := c.watcher.State() - if err != nil { - c.logger.Error("Unable to get state from consul-server-connection-manager", "error", err) - return 1 - } - - consulClient, err := consul.NewClientFromConnMgrState(consulConfig, state) - if err != nil { - c.logger.Error("Unable to get client connection", "error", err) - return 1 - } - - if version.IsFIPS() { - // make sure we are also using FIPS Consul - var versionInfo map[string]interface{} - _, err := consulClient.Raw().Query("/v1/agent/version", versionInfo, nil) - if err != nil { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. Unable to verify FIPS Consul while setting up Consul API client.") - } - if val, ok := versionInfo["FIPS"]; !ok || val == "" { - c.logger.Warn("This is a FIPS build of consul-k8s, which should be used with FIPS Consul. A non-FIPS version of Consul was detected.") - } - } - - // todo (agentless): this should eventually be passed to consul-dataplane as a string so we don't need to write it to file. - if c.consul.UseTLS && c.consul.CACertPEM != "" { - if err = common.WriteFileWithPerms(constants.ConsulCAFile, c.consul.CACertPEM, 0444); err != nil { - c.logger.Error("error writing CA cert file", "error", err) - return 1 - } - } - - dc, err := consul.NewDataplaneServiceClient(c.watcher) - if err != nil { - c.logger.Error("failed to create resource client", "error", err) - return 1 - } - - var bootstrapConfig pbmesh.BootstrapConfig - if err := backoff.Retry(c.getBootstrapParams(dc, &bootstrapConfig), backoff.WithMaxRetries(backoff.NewConstantBackOff(1*time.Second), c.maxPollingAttempts)); err != nil { - c.logger.Error("Timed out waiting for bootstrap parameters", "error", err) - return 1 - } - - if c.flagRedirectTrafficConfig != "" { - err := c.applyTrafficRedirectionRules(&bootstrapConfig) // BootstrapConfig is always populated non-nil from the RPC - if err != nil { - c.logger.Error("error applying traffic redirection rules", "err", err) - return 1 - } - } - - c.logger.Info("Proxy initialization completed") - return 0 -} - -func (c *Command) validateFlags() error { - if c.flagProxyName == "" { - return errors.New("-proxy-name must be set") - } - return nil -} - -func (c *Command) Synopsis() string { return synopsis } -func (c *Command) Help() string { - c.once.Do(c.init) - return c.help -} - -func (c *Command) getBootstrapParams( - client pbdataplane.DataplaneServiceClient, - bootstrapConfig *pbmesh.BootstrapConfig) backoff.Operation { - - return func() error { - req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: c.flagProxyName, - Namespace: c.consul.Namespace, - Partition: c.consul.Partition, - } - res, err := client.GetEnvoyBootstrapParams(context.Background(), req) - if err != nil { - c.logger.Error("Unable to get bootstrap parameters", "error", err) - return err - } - if res.GetBootstrapConfig() != nil { - *bootstrapConfig = *res.GetBootstrapConfig() - } - return nil - } -} - -// This below implementation is loosely based on -// https://github.com/hashicorp/consul/blob/fe2d41ddad9ba2b8ff86cbdebbd8f05855b1523c/command/connect/redirecttraffic/redirect_traffic.go#L136. - -func (c *Command) applyTrafficRedirectionRules(config *pbmesh.BootstrapConfig) error { - - err := json.Unmarshal([]byte(c.flagRedirectTrafficConfig), &c.iptablesConfig) - if err != nil { - return err - } - if c.iptablesProvider != nil { - c.iptablesConfig.IptablesProvider = c.iptablesProvider - } - - // TODO: provide dynamic updates to the c.iptablesConfig.ProxyOutboundPort - // We currently don't have a V2 endpoint that can gather the fully synthesized ProxyConfiguration. - // We need this to dynamically set c.iptablesConfig.ProxyOutboundPort with the outbound port configuration from - // pbmesh.DynamicConfiguration.TransparentProxy.OutboundListenerPort. - // We would either need to grab another resource that has this information rendered in it, or add - // pbmesh.DynamicConfiguration to the GetBootstrapParameters rpc. - // Right now this is an edge case because the mesh webhook configured the flagRedirectTrafficConfig with the default - // 15001 port. - - // TODO: provide dyanmic updates to the c.iptablesConfig.ProxyInboundPort - // This is the `mesh` port in the workload resource. - // Right now this will always be the default port (20000) - - if config.StatsBindAddr != "" { - _, port, err := net.SplitHostPort(config.StatsBindAddr) - if err != nil { - return fmt.Errorf("failed parsing host and port from StatsBindAddr: %s", err) - } - - c.iptablesConfig.ExcludeInboundPorts = append(c.iptablesConfig.ExcludeInboundPorts, port) - } - - // Configure any relevant information from the proxy service - err = iptables.Setup(c.iptablesConfig) - if err != nil { - return err - } - c.logger.Info("Successfully applied traffic redirection rules") - return nil -} - -const synopsis = "Inject mesh init command." -const help = ` -Usage: consul-k8s-control-plane mesh-init [options] - - Bootstraps mesh-injected pod components. - Uses V2 Consul Catalog APIs. - Not intended for stand-alone use. -` diff --git a/control-plane/subcommand/mesh-init/command_ent_test.go b/control-plane/subcommand/mesh-init/command_ent_test.go deleted file mode 100644 index ad3ea8c87d..0000000000 --- a/control-plane/subcommand/mesh-init/command_ent_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -//go:build enterprise - -package meshinit - -import ( - "context" - "strconv" - "testing" - - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/namespaces" -) - -func TestRun_WithNamespaces(t *testing.T) { - t.Parallel() - cases := []struct { - name string - consulNamespace string - consulPartition string - }{ - { - name: "default ns, default partition", - consulNamespace: constants.DefaultConsulNS, - consulPartition: constants.DefaultConsulPartition, - }, - { - name: "non-default ns, default partition", - consulNamespace: "bar", - consulPartition: constants.DefaultConsulPartition, - }, - { - name: "non-default ns, non-default partition", - consulNamespace: "bar", - consulPartition: "baz", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - _, err = EnsurePartitionExists(testClient.APIClient, c.consulPartition) - require.NoError(t, err) - - partitionedCfg := testClient.Cfg.APIClientConfig - partitionedCfg.Partition = c.consulPartition - - partitionedClient, err := api.NewClient(partitionedCfg) - require.NoError(t, err) - - _, err = namespaces.EnsureExists(partitionedClient, c.consulNamespace, "") - require.NoError(t, err) - - // Register Consul workload. - loadResource(t, resourceClient, getWorkloadID(testPodName, c.consulNamespace, c.consulPartition), getWorkload(), nil) - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 5, - } - // We build the consul-addr because normally it's defined by the init container setting - // CONSUL_HTTP_ADDR when it processes the command template. - flags := []string{"-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-namespace", c.consulNamespace, - "-partition", c.consulPartition, - } - - // Run the command. - code := cmd.Run(flags) - require.Equal(t, 0, code, ui.ErrorWriter.String()) - }) - } -} - -// EnsurePartitionExists ensures a Consul partition exists. -// Boolean return value indicates if the partition was created by this call. -// This is borrowed from namespaces.EnsureExists -func EnsurePartitionExists(client *api.Client, name string) (bool, error) { - if name == constants.DefaultConsulPartition { - return false, nil - } - // Check if the Consul namespace exists. - partitionInfo, _, err := client.Partitions().Read(context.Background(), name, nil) - if err != nil { - return false, err - } - if partitionInfo != nil { - return false, nil - } - - consulPartition := api.Partition{ - Name: name, - Description: "Auto-generated by consul-k8s", - } - - _, _, err = client.Partitions().Create(context.Background(), &consulPartition, nil) - return true, err -} diff --git a/control-plane/subcommand/mesh-init/command_test.go b/control-plane/subcommand/mesh-init/command_test.go deleted file mode 100644 index 85b68adc65..0000000000 --- a/control-plane/subcommand/mesh-init/command_test.go +++ /dev/null @@ -1,417 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package meshinit - -import ( - "context" - "encoding/json" - "fmt" - "strconv" - "strings" - "sync" - "testing" - "time" - - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/iptables" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/mitchellh/cli" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul-k8s/control-plane/connect-inject/constants" - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" -) - -func TestRun_FlagValidation(t *testing.T) { - t.Parallel() - cases := []struct { - flags []string - env string - expErr string - }{ - { - flags: []string{}, - expErr: "-proxy-name must be set", - }, - { - flags: []string{ - "-proxy-name", testPodName, - "-log-level", "invalid", - }, - expErr: "unknown log level: invalid", - }, - } - for _, c := range cases { - t.Run(c.expErr, func(t *testing.T) { - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - } - code := cmd.Run(c.flags) - require.Equal(t, 1, code) - require.Contains(t, ui.ErrorWriter.String(), c.expErr) - }) - } -} - -// TestRun_MeshServices tests that the command can log in to Consul (if ACLs are enabled) using a kubernetes -// auth method and, using the obtained token, make call to the dataplane GetBootstrapParams() RPC. -func TestRun_MeshServices(t *testing.T) { - t.Parallel() - - cases := []struct { - name string - workload *pbcatalog.Workload - proxyConfiguration *pbmesh.ProxyConfiguration - aclsEnabled bool - expFail bool - }{ - { - name: "basic workload bootstrap", - workload: getWorkload(), - }, - { - name: "workload and proxyconfiguration bootstrap", - workload: getWorkload(), - proxyConfiguration: getProxyConfiguration(), - }, - { - name: "missing workload", - expFail: true, - }, - // TODO: acls enabled - } - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - //tokenFile := fmt.Sprintf("/tmp/%d1", rand.Int()) - //t.Cleanup(func() { - // _ = os.RemoveAll(tokenFile) - //}) - - // Create test consulServer server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.workload, nil) - loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), tt.proxyConfiguration, nil) - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 3, - } - - // We build the consul-addr because normally it's defined by the init container setting - // CONSUL_HTTP_ADDR when it processes the command template. - flags := []string{"-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - } - //if tt.aclsEnabled { - // flags = append(flags, "-auth-method-name", test.AuthMethod, - // "-service-account-name", tt.serviceAccountName, - // "-acl-token-sink", tokenFile) //TODO: what happens if this is unspecified? We don't need this file - //} - - // Run the command. - code := cmd.Run(flags) - if tt.expFail { - require.Equal(t, 1, code) - return - } - require.Equal(t, 0, code, ui.ErrorWriter.String()) - - // TODO: Can we remove the tokenFile from this workflow? - // consul-dataplane performs it's own login using the Serviceaccount bearer token - //if tt.aclsEnabled { - // // Validate the ACL token was written. - // tokenData, err := os.ReadFile(tokenFile) - // require.NoError(t, err) - // require.NotEmpty(t, tokenData) - // - // // Check that the token has the metadata with pod name and pod namespace. - // consulClient, err = api.NewClient(&api.Config{Address: server.HTTPAddr, Token: string(tokenData)}) - // require.NoError(t, err) - // token, _, err := consulClient.ACL().TokenReadSelf(nil) - // require.NoError(t, err) - // require.Equal(t, "token created via login: {\"pod\":\"default-ns/counting-pod\"}", token.Description) - //} - }) - } -} - -// TestRun_RetryServicePolling runs the command but does not register the consul service -// for 2 seconds and then asserts the command exits successfully. -func TestRun_RetryServicePolling(t *testing.T) { - t.Parallel() - - // Start Consul server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - // Start the consul service registration in a go func and delay it so that it runs - // after the cmd.Run() starts. - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - // Wait a moment, this ensures that we are already in the retry logic. - time.Sleep(time.Second * 2) - // Register counting service. - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) - }() - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 10, - } - flags := []string{ - "-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - } - code := cmd.Run(flags) - wg.Wait() - require.Equal(t, 0, code) -} - -func TestRun_TrafficRedirection(t *testing.T) { - cases := map[string]struct { - registerProxyConfiguration bool - expIptablesParamsFunc func(actual iptables.Config) error - }{ - "no proxyConfiguration provided": { - expIptablesParamsFunc: func(actual iptables.Config) error { - if len(actual.ExcludeInboundPorts) != 0 { - return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be empty", actual.ExcludeInboundPorts) - } - if actual.ProxyInboundPort != 20000 { - return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) - } - if actual.ProxyOutboundPort != 15001 { - return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) - } - return nil - }, - }, - "stats bind port is provided in proxyConfiguration": { - registerProxyConfiguration: true, - expIptablesParamsFunc: func(actual iptables.Config) error { - if len(actual.ExcludeInboundPorts) != 1 || actual.ExcludeInboundPorts[0] != "9090" { - return fmt.Errorf("ExcludeInboundPorts in iptables.Config was %v, but should be [9090, 1234]", actual.ExcludeInboundPorts) - } - if actual.ProxyInboundPort != 20000 { - return fmt.Errorf("ProxyInboundPort in iptables.Config was %d, but should be [20000]", actual.ProxyOutboundPort) - } - if actual.ProxyOutboundPort != 15001 { - return fmt.Errorf("ProxyOutboundPort in iptables.Config was %d, but should be [15001]", actual.ProxyOutboundPort) - } - return nil - }, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - - // Start Consul server. - var serverCfg *testutil.TestServerConfig - testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { - c.Experiments = []string{"resource-apis"} - serverCfg = c - }) - resourceClient, err := consul.NewResourceServiceClient(testClient.Watcher) - require.NoError(t, err) - - // Add additional proxy configuration either to a config entry or to the service itself. - if c.registerProxyConfiguration { - loadResource(t, resourceClient, getProxyConfigurationID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getProxyConfiguration(), nil) - } - - // Register Consul workload. - loadResource(t, resourceClient, getWorkloadID(testPodName, constants.DefaultConsulNS, constants.DefaultConsulPartition), getWorkload(), nil) - - iptablesProvider := &fakeIptablesProvider{} - iptablesCfg := iptables.Config{ - ProxyUserID: "5995", - ProxyInboundPort: 20000, - ProxyOutboundPort: 15001, - } - - ui := cli.NewMockUi() - cmd := Command{ - UI: ui, - maxPollingAttempts: 3, - iptablesProvider: iptablesProvider, - } - iptablesCfgJSON, err := json.Marshal(iptablesCfg) - require.NoError(t, err) - flags := []string{ - "-proxy-name", testPodName, - "-addresses", "127.0.0.1", - "-http-port", strconv.Itoa(serverCfg.Ports.HTTP), - "-grpc-port", strconv.Itoa(serverCfg.Ports.GRPC), - "-redirect-traffic-config", string(iptablesCfgJSON), - } - code := cmd.Run(flags) - require.Equal(t, 0, code, ui.ErrorWriter.String()) - require.Truef(t, iptablesProvider.applyCalled, "redirect traffic rules were not applied") - if c.expIptablesParamsFunc != nil { - errMsg := c.expIptablesParamsFunc(cmd.iptablesConfig) - require.NoError(t, errMsg) - } - }) - } -} - -const ( - testPodName = "foo" -) - -type fakeIptablesProvider struct { - applyCalled bool - rules []string -} - -func loadResource(t *testing.T, client pbresource.ResourceServiceClient, id *pbresource.ID, proto proto.Message, owner *pbresource.ID) { - if id == nil || !proto.ProtoReflect().IsValid() { - return - } - - data, err := anypb.New(proto) - require.NoError(t, err) - - resource := &pbresource.Resource{ - Id: id, - Data: data, - Owner: owner, - } - - req := &pbresource.WriteRequest{Resource: resource} - _, err = client.Write(context.Background(), req) - require.NoError(t, err) - test.ResourceHasPersisted(t, context.Background(), client, id) -} - -func getWorkloadID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getWorkload creates a proxyConfiguration that matches the pod from createPod, -// assuming that metrics, telemetry, and overwrite probes are enabled separately. -func getWorkload() *pbcatalog.Workload { - return &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "10.0.0.1", Ports: []string{"public", "admin", "mesh"}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "public": { - Port: 80, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "admin": { - Port: 8080, - Protocol: pbcatalog.Protocol_PROTOCOL_UNSPECIFIED, - }, - "mesh": { - Port: constants.ProxyDefaultInboundPort, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - NodeName: "k8s-node-0", - Identity: testPodName, - } -} - -func getProxyConfigurationID(name, namespace, partition string) *pbresource.ID { - return &pbresource.ID{ - Name: name, - Type: pbmesh.ProxyConfigurationType, - Tenancy: &pbresource.Tenancy{ - Partition: partition, - Namespace: namespace, - - // Because we are explicitly defining NS/partition, this will not default and must be explicit. - // At a future point, this will move out of the Tenancy block. - PeerName: constants.DefaultConsulPeer, - }, - } -} - -// getProxyConfiguration creates a proxyConfiguration that matches the pod from createWorkload. -func getProxyConfiguration() *pbmesh.ProxyConfiguration { - return &pbmesh.ProxyConfiguration{ - Workloads: &pbcatalog.WorkloadSelector{ - Names: []string{testPodName}, - }, - DynamicConfig: &pbmesh.DynamicConfig{ - Mode: pbmesh.ProxyMode_PROXY_MODE_TRANSPARENT, - ExposeConfig: &pbmesh.ExposeConfig{ - ExposePaths: []*pbmesh.ExposePath{ - { - ListenerPort: 20400, - LocalPathPort: 2001, - Path: "/livez", - }, - { - ListenerPort: 20300, - LocalPathPort: 2000, - Path: "/readyz", - }, - { - ListenerPort: 20500, - LocalPathPort: 2002, - Path: "/startupz", - }, - }, - }, - }, - BootstrapConfig: &pbmesh.BootstrapConfig{ - StatsBindAddr: "0.0.0.0:9090", - PrometheusBindAddr: "0.0.0.0:21234", // This gets added to the iptables exclude directly in the webhook - }, - } -} - -func (f *fakeIptablesProvider) AddRule(_ string, args ...string) { - f.rules = append(f.rules, strings.Join(args, " ")) -} - -func (f *fakeIptablesProvider) ApplyRules() error { - f.applyCalled = true - return nil -} - -func (f *fakeIptablesProvider) Rules() []string { - return f.rules -} diff --git a/control-plane/subcommand/partition-init/command.go b/control-plane/subcommand/partition-init/command.go index 0aa8cdc724..72c4ceeff0 100644 --- a/control-plane/subcommand/partition-init/command.go +++ b/control-plane/subcommand/partition-init/command.go @@ -11,14 +11,13 @@ import ( "sync" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { @@ -172,7 +171,6 @@ func (c *Command) validateFlags() error { if c.consul.APITimeout <= 0 { return errors.New("-api-timeout must be set to a value greater than 0") } - return nil } diff --git a/control-plane/subcommand/server-acl-init/anonymous_token_test.go b/control-plane/subcommand/server-acl-init/anonymous_token_test.go index 8ed58c045f..4a36c676e1 100644 --- a/control-plane/subcommand/server-acl-init/anonymous_token_test.go +++ b/control-plane/subcommand/server-acl-init/anonymous_token_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package serveraclinit import ( @@ -14,7 +11,7 @@ import ( func Test_configureAnonymousPolicy(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) consulHTTPAddr := testClient.TestServer.HTTPAddr consulGRPCAddr := testClient.TestServer.GRPCAddr diff --git a/control-plane/subcommand/server-acl-init/command.go b/control-plane/subcommand/server-acl-init/command.go index e1bd3c2356..7ff0ae2268 100644 --- a/control-plane/subcommand/server-acl-init/command.go +++ b/control-plane/subcommand/server-acl-init/command.go @@ -16,6 +16,11 @@ import ( "time" "github.com/cenkalti/backoff" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/consul-server-connection-manager/discovery" "github.com/hashicorp/consul/api" "github.com/hashicorp/go-hclog" @@ -26,12 +31,6 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" - k8sflags "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { @@ -44,8 +43,6 @@ type Command struct { flagResourcePrefix string flagK8sNamespace string - flagResourceAPIs bool // Use V2 APIs - flagAllowDNS bool flagSetServerTokens bool @@ -133,9 +130,6 @@ func (c *Command) init() { c.flags.BoolVar(&c.flagSetServerTokens, "set-server-tokens", true, "Toggle for setting agent tokens for the servers.") - c.flags.BoolVar(&c.flagResourceAPIs, "enable-resource-apis", false, - "Enable or disable Consul V2 Resource APIs. This will affect the binding rule used for Kubernetes auth (Service vs. WorkloadIdentity)") - c.flags.BoolVar(&c.flagAllowDNS, "allow-dns", false, "Toggle for updating the anonymous token to allow DNS queries to work") c.flags.BoolVar(&c.flagClient, "client", true, diff --git a/control-plane/subcommand/server-acl-init/command_ent_test.go b/control-plane/subcommand/server-acl-init/command_ent_test.go index b33e4beb60..ceaad9c834 100644 --- a/control-plane/subcommand/server-acl-init/command_ent_test.go +++ b/control-plane/subcommand/server-acl-init/command_ent_test.go @@ -15,6 +15,9 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/consul" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -23,10 +26,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/consul" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // Test the auth method and acl binding rule created when namespaces are enabled @@ -34,27 +33,10 @@ import ( func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { t.Parallel() - cases := map[string]struct { - Destination string - ExtraFlags []string - V2BindingRule bool - }{ - "consul default ns": { - Destination: "default", - }, - "consul non-default ns": { - Destination: "destination", - }, - "consul non-default ns w/ resource-apis": { - Destination: "destination", - ExtraFlags: []string{"-enable-resource-apis=true"}, - V2BindingRule: true, - }, - } - - for name, c := range cases { - t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, false) + consulDestNamespaces := []string{"default", "destination"} + for _, consulDestNamespace := range consulDestNamespaces { + t.Run(consulDestNamespace, func(tt *testing.T) { + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -72,14 +54,10 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { "-connect-inject", "-partition=default", "-enable-namespaces", - "-consul-inject-destination-namespace", c.Destination, + "-consul-inject-destination-namespace", consulDestNamespace, "-acl-binding-rule-selector=serviceaccount.name!=default", } - if len(c.ExtraFlags) > 0 { - args = append(args, c.ExtraFlags...) - } - responseCode := cmd.Run(args) require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) @@ -92,11 +70,11 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { // Ensure there's only one auth method. namespaceQuery := &api.QueryOptions{ - Namespace: c.Destination, + Namespace: consulDestNamespace, } methods, _, err := consul.ACL().AuthMethodList(namespaceQuery) require.NoError(t, err) - if c.Destination == "default" { + if consulDestNamespace == "default" { // If the destination mamespace is default then AuthMethodList // will return the component-auth-method as well. require.Len(t, methods, 2) @@ -118,19 +96,13 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, namespaceQuery) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, namespaceQuery) require.NoError(t, err) - require.NotNil(t, aclRule) - if c.V2BindingRule { - require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) - require.Equal(t, "builtin/workload-identity", aclRule.BindName) - require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) - } else { - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - } - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) // Check that the default namespace got an attached ACL policy defNamespace, _, err := consul.Namespaces().Read("default", &api.QueryOptions{}) @@ -140,7 +112,7 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, defNamespace.ACLs.PolicyDefaults, 1) require.Equal(t, "cross-namespace-policy", defNamespace.ACLs.PolicyDefaults[0].Name) - if c.Destination != "default" { + if consulDestNamespace != "default" { // Check that only one namespace was created besides the // already existing `default` namespace namespaces, _, err := consul.Namespaces().List(&api.QueryOptions{}) @@ -148,10 +120,10 @@ func TestRun_ConnectInject_SingleDestinationNamespace(t *testing.T) { require.Len(t, namespaces, 2) // Check the created namespace properties - actNamespace, _, err := consul.Namespaces().Read(c.Destination, &api.QueryOptions{}) + actNamespace, _, err := consul.Namespaces().Read(consulDestNamespace, &api.QueryOptions{}) require.NoError(t, err) require.NotNil(t, actNamespace) - require.Equal(t, c.Destination, actNamespace.Name) + require.Equal(t, consulDestNamespace, actNamespace.Name) require.Equal(t, "Auto-generated by consul-k8s", actNamespace.Description) require.NotNil(t, actNamespace.ACLs) require.Len(t, actNamespace.ACLs.PolicyDefaults, 1) @@ -171,7 +143,6 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { cases := map[string]struct { MirroringPrefix string ExtraFlags []string - V2BindingRule bool }{ "no prefix": { MirroringPrefix: "", @@ -187,16 +158,11 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { // effect. ExtraFlags: []string{"-consul-inject-destination-namespace=dest"}, }, - "no prefix w/ resource-apis": { - MirroringPrefix: "", - ExtraFlags: []string{"-enable-resource-apis=true"}, - V2BindingRule: true, - }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, false) + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -245,19 +211,13 @@ func TestRun_ConnectInject_NamespaceMirroring(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, nil) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, nil) require.NoError(t, err) - require.NotNil(t, aclRule) - if c.V2BindingRule { - require.Equal(t, api.BindingRuleBindTypeTemplatedPolicy, aclRule.BindType) - require.Equal(t, "builtin/workload-identity", aclRule.BindName) - require.Equal(t, "${serviceaccount.name}", aclRule.BindVars.Name) - } else { - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - } - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) }) } } @@ -315,7 +275,7 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { k8sNamespaceFlags := []string{"default", "other"} for _, k8sNamespaceFlag := range k8sNamespaceFlags { t.Run(k8sNamespaceFlag, func(t *testing.T) { - k8s, testAgent := completeSetup(t, false) + k8s, testAgent := completeSetup(t) setUpK8sServiceAccount(t, k8s, k8sNamespaceFlag) ui := cli.NewMockUi() @@ -375,7 +335,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "enterprise-license-token", "igw-policy", "anotherigw-policy", - "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "connect-inject-policy", @@ -392,14 +351,12 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range firstRunExpectedPolicies { - aclRule, ok := actualPolicies[expected] + actRules, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) // We assert that the policy doesn't have any namespace config // in it because later that's what we're using to test that it - // got updated. builtin/global-ready-only always has namespaces and partitions included - if expected != "builtin/global-read-only" { - require.NotContains(t, aclRule, "namespace", "policy", expected) - } + // got updated. + require.NotContains(t, actRules, "namespace") } // Re-run the command with namespace flags. The policies should be updated. @@ -424,7 +381,6 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { "cross-namespace-policy", "igw-policy", "anotherigw-policy", - "builtin/global-read-only", "tgw-policy", "anothertgw-policy", "partitions-token", @@ -441,27 +397,27 @@ func TestRun_ACLPolicyUpdates(t *testing.T) { actualPolicies[p.Name] = policy.Rules } for _, expected := range secondRunExpectedPolicies { - aclRule, ok := actualPolicies[expected] + actRules, ok := actualPolicies[expected] require.True(t, ok, "Did not find policy %s", expected) switch expected { case "connect-inject-policy": // The connect inject token doesn't have namespace config, // but does change to operator:write from an empty string. - require.Contains(t, aclRule, "policy = \"write\"") + require.Contains(t, actRules, "policy = \"write\"") case "snapshot-agent-policy", "enterprise-license-token": // The snapshot agent and enterprise license tokens shouldn't change. - require.NotContains(t, aclRule, "namespace") - require.Contains(t, aclRule, "acl = \"write\"") + require.NotContains(t, actRules, "namespace") + require.Contains(t, actRules, "acl = \"write\"") case "partitions-token": - require.Contains(t, aclRule, "operator = \"write\"") + require.Contains(t, actRules, "operator = \"write\"") case "anonymous-token-policy": // TODO: This needs to be revisted due to recent changes in how we update the anonymous policy (NET-5174) default: // Assert that the policies have the word namespace in them. This // tests that they were updated. The actual contents are tested // in rules_test.go. - require.Contains(t, aclRule, "namespace") + require.Contains(t, actRules, "namespace") } } }) @@ -489,8 +445,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig string // Expected namespace for the binding rule. BindingRuleExpectedNS string - // UseV2API, tests the bindingrule is compatible with workloadIdentites. - UseV2API bool }{ "no ns => mirroring ns, no prefix": { FirstRunArgs: nil, @@ -620,148 +574,11 @@ func TestRun_ConnectInject_Updates(t *testing.T) { AuthMethodExpectedNamespacePrefixConfig: "", BindingRuleExpectedNS: "default", }, - "(v2) no ns => mirroring ns, no prefix": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) no ns => mirroring ns, prefix": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) no ns => single dest ns": { - FirstRunArgs: nil, - SecondRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - AuthMethodExpectedNS: "dest", - AuthMethodExpectMapNamespacesConfig: false, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "dest", - UseV2API: true, - }, - "(v2) mirroring ns => single dest ns": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - AuthMethodExpectedNS: "dest", - AuthMethodExpectMapNamespacesConfig: false, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "dest", - UseV2API: true, - }, - "(v2) single dest ns => mirroring ns": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-consul-inject-destination-namespace=dest", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (no prefix) => mirroring ns (no prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns => mirroring ns (same prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (no prefix) => mirroring ns (prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "prefix-", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, - "(v2) mirroring ns (prefix) => mirroring ns (no prefix)": { - FirstRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=prefix-", - }, - SecondRunArgs: []string{ - "-enable-namespaces", - "-enable-inject-k8s-namespace-mirroring", - "-inject-k8s-namespace-mirroring-prefix=", - }, - AuthMethodExpectedNS: "default", - AuthMethodExpectMapNamespacesConfig: true, - AuthMethodExpectedNamespacePrefixConfig: "", - BindingRuleExpectedNS: "default", - UseV2API: true, - }, } for name, c := range cases { t.Run(name, func(tt *testing.T) { - k8s, testAgent := completeSetup(tt, c.UseV2API) + k8s, testAgent := completeSetup(tt) setUpK8sServiceAccount(tt, k8s, ns) ui := cli.NewMockUi() @@ -775,10 +592,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { "-connect-inject", } - if c.UseV2API { - defaultArgs = append(defaultArgs, "-enable-resource-apis=true") - } - // First run. NOTE: we don't assert anything here since we've // tested these results in other tests. What we care about here // is the result after the second run. @@ -830,11 +643,6 @@ func TestRun_ConnectInject_Updates(t *testing.T) { }) require.NoError(t, err) require.Len(t, rules, 1) - if c.UseV2API { - require.Equal(tt, api.BindingRuleBindTypeTemplatedPolicy, rules[0].BindType) - } else { - require.Equal(tt, api.BindingRuleBindTypeService, rules[0].BindType) - } }) } } @@ -874,7 +682,7 @@ func TestRun_TokensWithNamespacesEnabled(t *testing.T) { } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -1128,7 +936,7 @@ partition "default" { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -1215,7 +1023,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_PrimaryDatacenter(t *testing.T) authMethodName := fmt.Sprintf("%s-%s", resourcePrefix, componentAuthMethod) serviceAccountName := fmt.Sprintf("%s-%s", resourcePrefix, c.ComponentName) - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) _, jwtToken := setUpK8sServiceAccount(t, k8s, c.Namespace) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -1375,7 +1183,7 @@ func TestRun_NamespaceEnabled_ValidateLoginToken_SecondaryDatacenter(t *testing. func TestRun_PartitionTokenDefaultPartition_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testSvr := completeSetup(t, false) + k8s, testSvr := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) partitionToken := "123e4567-e89b-12d3-a456-426614174000" diff --git a/control-plane/subcommand/server-acl-init/command_test.go b/control-plane/subcommand/server-acl-init/command_test.go index 928a50e285..68ce4c1e02 100644 --- a/control-plane/subcommand/server-acl-init/command_test.go +++ b/control-plane/subcommand/server-acl-init/command_test.go @@ -18,6 +18,9 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -29,10 +32,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/helper/test" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) var ns = "default" @@ -103,7 +102,7 @@ func TestRun_FlagValidation(t *testing.T) { func TestRun_Defaults(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -178,7 +177,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -243,7 +242,7 @@ func TestRun_TokensPrimaryDC(t *testing.T) { func TestRun_ReplicationTokenPrimaryDC_WithProvidedSecretID(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) replicationToken := "123e4567-e89b-12d3-a456-426614174000" @@ -505,7 +504,7 @@ func TestRun_AnonymousTokenPolicy(t *testing.T) { flags = append(flags, "-acl-replication-token-file", tmp.Name()) } else { var testClient *test.TestServerClient - k8s, testClient = completeSetup(t, false) + k8s, testClient = completeSetup(t) consulHTTPAddr = testClient.TestServer.HTTPAddr consulGRPCAddr = testClient.TestServer.GRPCAddr } @@ -580,9 +579,8 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { t.Parallel() cases := map[string]struct { - flags []string - expectedHost string - v2BindingRule bool + flags []string + expectedHost string }{ "-connect-inject flag": { flags: []string{"-connect-inject"}, @@ -595,16 +593,11 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { }, expectedHost: "https://my-kube.com", }, - "-enable-resource-apis flag": { - flags: []string{"-connect-inject", "-enable-resource-apis=true"}, - expectedHost: "https://kubernetes.default.svc", - v2BindingRule: true, - }, } for testName, c := range cases { t.Run(testName, func(t *testing.T) { - k8s, testClient := completeSetup(t, c.v2BindingRule) + k8s, testClient := completeSetup(t) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -647,15 +640,8 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, &api.QueryOptions{Token: bootToken}) require.NoError(t, err) require.Len(t, rules, 1) - - if c.v2BindingRule { - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - } else { - require.Equal(t, "service", string(rules[0].BindType)) - require.Equal(t, "${serviceaccount.name}", rules[0].BindName) - } + require.Equal(t, "service", string(rules[0].BindType)) + require.Equal(t, "${serviceaccount.name}", rules[0].BindName) require.Equal(t, bindingRuleSelector, rules[0].Selector) // Test that if the same command is re-run it doesn't error. @@ -678,7 +664,7 @@ func TestRun_ConnectInjectAuthMethod(t *testing.T) { func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) caCert, jwtToken := setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -759,86 +745,7 @@ func TestRun_ConnectInjectAuthMethodUpdates(t *testing.T) { // Test that ACL binding rules are updated if the rule selector changes. func TestRun_BindingRuleUpdates(t *testing.T) { - k8s, testClient := completeSetup(t, false) - setUpK8sServiceAccount(t, k8s, ns) - - consul, err := api.NewClient(&api.Config{ - Address: testClient.TestServer.HTTPAddr, - }) - require.NoError(t, err) - - ui := cli.NewMockUi() - commonArgs := []string{ - "-resource-prefix=" + resourcePrefix, - "-k8s-namespace=" + ns, - "-addresses", strings.Split(testClient.TestServer.HTTPAddr, ":")[0], - "-http-port", strings.Split(testClient.TestServer.HTTPAddr, ":")[1], - "-grpc-port", strings.Split(testClient.TestServer.GRPCAddr, ":")[1], - "-connect-inject", - } - firstRunArgs := append(commonArgs, - "-acl-binding-rule-selector=serviceaccount.name!=default", - ) - // On the second run, we change the binding rule selector. - secondRunArgs := append(commonArgs, - "-acl-binding-rule-selector=serviceaccount.name!=changed", - ) - - // Run the command first to populate the binding rule. - cmd := Command{ - UI: ui, - clientset: k8s, - } - responseCode := cmd.Run(firstRunArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - - // Validate the binding rule. - { - queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} - authMethodName := resourcePrefix + "-k8s-auth-method" - rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) - require.NoError(t, err) - require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) - require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) - } - - // Re-run the command with namespace flags. The policies should be updated. - // NOTE: We're redefining the command so that the old flag values are - // reset. - cmd = Command{ - UI: ui, - clientset: k8s, - } - responseCode = cmd.Run(secondRunArgs) - require.Equal(t, 0, responseCode, ui.ErrorWriter.String()) - - // Check the binding rule is changed expected. - { - queryOpts := &api.QueryOptions{Token: getBootToken(t, k8s, resourcePrefix, ns)} - authMethodName := resourcePrefix + "-k8s-auth-method" - rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) - require.NoError(t, err) - require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) - require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, api.BindingRuleBindTypeService, aclRule.BindType) - require.Equal(t, "${serviceaccount.name}", aclRule.BindName) - require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) - } -} - -// Test that the ACL binding template is updated if the rule selector changes. -// V2 only. -func TestRun_TemplateBindingRuleUpdates(t *testing.T) { - k8s, testClient := completeSetup(t, true) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) consul, err := api.NewClient(&api.Config{ @@ -853,7 +760,6 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { "-addresses", strings.Split(testClient.TestServer.HTTPAddr, ":")[0], "-http-port", strings.Split(testClient.TestServer.HTTPAddr, ":")[1], "-grpc-port", strings.Split(testClient.TestServer.GRPCAddr, ":")[1], - "-enable-resource-apis=true", "-connect-inject", } firstRunArgs := append(commonArgs, @@ -879,14 +785,13 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - require.Equal(t, "serviceaccount.name!=default", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=default", actRule.Selector) } // Re-run the command with namespace flags. The policies should be updated. @@ -906,21 +811,20 @@ func TestRun_TemplateBindingRuleUpdates(t *testing.T) { rules, _, err := consul.ACL().BindingRuleList(authMethodName, queryOpts) require.NoError(t, err) require.Len(t, rules, 1) - aclRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) + actRule, _, err := consul.ACL().BindingRuleRead(rules[0].ID, queryOpts) require.NoError(t, err) - require.NotNil(t, aclRule) - require.Equal(t, "Kubernetes binding rule", aclRule.Description) - require.Equal(t, "templated-policy", string(rules[0].BindType)) - require.Equal(t, "builtin/workload-identity", rules[0].BindName) - require.Equal(t, "${serviceaccount.name}", rules[0].BindVars.Name) - require.Equal(t, "serviceaccount.name!=changed", aclRule.Selector) + require.NotNil(t, actRule) + require.Equal(t, "Kubernetes binding rule", actRule.Description) + require.Equal(t, api.BindingRuleBindTypeService, actRule.BindType) + require.Equal(t, "${serviceaccount.name}", actRule.BindName) + require.Equal(t, "serviceaccount.name!=changed", actRule.Selector) } } // Test that the catalog sync policy is updated if the Consul node name changes. func TestRun_SyncPolicyUpdates(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) ui := cli.NewMockUi() @@ -1015,12 +919,6 @@ func TestRun_ErrorsOnDuplicateACLPolicy(t *testing.T) { }) require.NoError(t, err) - // Make sure the ACL system is bootstrapped first - require.Eventually(t, func() bool { - _, _, err := consul.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - // Create the policy manually. description := "not the expected description" policy, _, err := consul.ACL().PolicyCreate(&api.ACLPolicy{ @@ -1066,7 +964,7 @@ func TestRun_DelayedServers(t *testing.T) { t.Parallel() k8s := fake.NewSimpleClientset() setUpK8sServiceAccount(t, k8s, ns) - randomPorts := freeport.GetN(t, 8) + randomPorts := freeport.GetN(t, 7) ui := cli.NewMockUi() cmd := Command{ @@ -1107,11 +1005,10 @@ func TestRun_DelayedServers(t *testing.T) { DNS: randomPorts[0], HTTP: randomPorts[1], GRPC: randomPorts[2], - GRPCTLS: randomPorts[3], - HTTPS: randomPorts[4], - SerfLan: randomPorts[5], - SerfWan: randomPorts[6], - Server: randomPorts[7], + HTTPS: randomPorts[3], + SerfLan: randomPorts[4], + SerfWan: randomPorts[5], + Server: randomPorts[6], } }) require.NoError(t, err) @@ -1222,7 +1119,7 @@ func TestRun_NoLeader(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } done := make(chan bool) @@ -1478,7 +1375,7 @@ func TestRun_ClientPolicyAndBindingRuleRetry(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } responseCode := cmd.Run([]string{ "-timeout=1m", @@ -1627,7 +1524,7 @@ func TestRun_AlreadyBootstrapped(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), } responseCode := cmd.Run(cmdArgs) @@ -1812,7 +1709,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, serverURL.Hostname(), port, false), + watcher: test.MockConnMgrForIPAndPort(serverURL.Hostname(), port), backend: &FakeSecretsBackend{bootstrapToken: bootToken}, } responseCode := cmd.Run([]string{ @@ -1844,7 +1741,7 @@ func TestRun_SkipBootstrapping_WhenServersAreDisabled(t *testing.T) { // Test that we exit after timeout. func TestRun_Timeout(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) _, err := api.NewClient(&api.Config{ @@ -1856,7 +1753,7 @@ func TestRun_Timeout(t *testing.T) { cmd := Command{ UI: ui, clientset: k8s, - watcher: test.MockConnMgrForIPAndPort(t, "localhost", 12345, false), + watcher: test.MockConnMgrForIPAndPort("localhost", 12345), } responseCode := cmd.Run([]string{ @@ -1996,7 +1893,7 @@ func TestRun_GatewayErrors(t *testing.T) { for testName, c := range cases { t.Run(testName, func(tt *testing.T) { - k8s, testClient := completeSetup(tt, false) + k8s, testClient := completeSetup(tt) setUpK8sServiceAccount(t, k8s, ns) require := require.New(tt) @@ -2098,7 +1995,7 @@ func TestRun_PoliciesAndBindingRulesForACLLogin_PrimaryDatacenter(t *testing.T) } for _, c := range cases { t.Run(c.TestName, func(t *testing.T) { - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2412,7 +2309,7 @@ func TestRun_ValidateLoginToken_PrimaryDatacenter(t *testing.T) { serviceAccountName = c.ServiceAccountName } - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) _, jwtToken := setUpK8sServiceAccount(t, k8s, ns) k8sMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -2628,7 +2525,7 @@ func TestRun_ValidateLoginToken_SecondaryDatacenter(t *testing.T) { func TestRun_PrimaryDatacenter_ComponentAuthMethod(t *testing.T) { t.Parallel() - k8s, testClient := completeSetup(t, false) + k8s, testClient := completeSetup(t) setUpK8sServiceAccount(t, k8s, ns) // Run the command. @@ -2704,20 +2601,14 @@ func TestRun_SecondaryDatacenter_ComponentAuthMethod(t *testing.T) { } // Set up test consul agent and kubernetes cluster. -func completeSetup(t *testing.T, useResourceAPI bool) (*fake.Clientset, *test.TestServerClient) { +func completeSetup(t *testing.T) (*fake.Clientset, *test.TestServerClient) { k8s := fake.NewSimpleClientset() - testServerClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { + testClient := test.TestServerWithMockConnMgrWatcher(t, func(c *testutil.TestServerConfig) { c.ACL.Enabled = true - - if useResourceAPI { - c.Experiments = []string{"resource-apis"} - } }) - testServerClient.TestServer.WaitForActiveCARoot(t) - - return k8s, testServerClient + return k8s, testClient } // Set up test consul agent and kubernetes cluster. diff --git a/control-plane/subcommand/server-acl-init/connect_inject.go b/control-plane/subcommand/server-acl-init/connect_inject.go index f853144a2c..58a36b988f 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject.go +++ b/control-plane/subcommand/server-acl-init/connect_inject.go @@ -6,11 +6,10 @@ package serveraclinit import ( "fmt" + "github.com/hashicorp/consul-k8s/control-plane/namespaces" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/namespaces" ) // We use the default Kubernetes service as the default host @@ -69,29 +68,15 @@ func (c *Command) configureConnectInjectAuthMethod(consulClient *api.Client, aut return err } - var abr api.ACLBindingRule - if c.flagResourceAPIs { - c.log.Info("creating consul binding rule for WorkloadIdentityName") - abr = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: authMethodName, - BindType: api.BindingRuleBindTypeTemplatedPolicy, - BindName: api.ACLTemplatedPolicyWorkloadIdentityName, - BindVars: &api.ACLTemplatedPolicyVariables{ - Name: "${serviceaccount.name}", - }, - Selector: c.flagBindingRuleSelector, - } - } else { - abr = api.ACLBindingRule{ - Description: "Kubernetes binding rule", - AuthMethod: authMethodName, - BindType: api.BindingRuleBindTypeService, - BindName: "${serviceaccount.name}", - Selector: c.flagBindingRuleSelector, - } + c.log.Info("creating inject binding rule") + // Create the binding rule. + abr := api.ACLBindingRule{ + Description: "Kubernetes binding rule", + AuthMethod: authMethodName, + BindType: api.BindingRuleBindTypeService, + BindName: "${serviceaccount.name}", + Selector: c.flagBindingRuleSelector, } - return c.createConnectBindingRule(consulClient, authMethodName, &abr) } diff --git a/control-plane/subcommand/server-acl-init/connect_inject_test.go b/control-plane/subcommand/server-acl-init/connect_inject_test.go index 2714bfa3f7..03e47c8ba6 100644 --- a/control-plane/subcommand/server-acl-init/connect_inject_test.go +++ b/control-plane/subcommand/server-acl-init/connect_inject_test.go @@ -7,13 +7,12 @@ import ( "context" "testing" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // Test that createAuthMethodTmpl returns an error when diff --git a/control-plane/subcommand/server-acl-init/create_or_update.go b/control-plane/subcommand/server-acl-init/create_or_update.go index 8099e6e9c6..50f215eacb 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update.go +++ b/control-plane/subcommand/server-acl-init/create_or_update.go @@ -7,11 +7,10 @@ import ( "fmt" "strings" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul/api" apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) // createACLPolicyRoleAndBindingRule will create the ACL Policy for the component diff --git a/control-plane/subcommand/server-acl-init/create_or_update_test.go b/control-plane/subcommand/server-acl-init/create_or_update_test.go index 96d3945617..84ccdc1635 100644 --- a/control-plane/subcommand/server-acl-init/create_or_update_test.go +++ b/control-plane/subcommand/server-acl-init/create_or_update_test.go @@ -5,7 +5,6 @@ package serveraclinit import ( "testing" - "time" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" @@ -97,13 +96,6 @@ func TestCreateOrUpdateACLPolicy(t *testing.T) { Address: svr.HTTPAddr, Token: bootToken, }) - - // Make sure the ACL system is bootstrapped first - require.Eventually(func() bool { - _, _, err := consul.ACL().PolicyList(nil) - return err == nil - }, 5*time.Second, 500*time.Millisecond) - require.NoError(err) connectInjectRule, err := cmd.injectRules() require.NoError(err) diff --git a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go index e4c5d78e38..93d9d0d2d8 100644 --- a/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go +++ b/control-plane/subcommand/server-acl-init/k8s_secrets_backend.go @@ -7,12 +7,11 @@ import ( "context" "fmt" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" apiv1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" ) const SecretsBackendTypeKubernetes SecretsBackendType = "kubernetes" diff --git a/control-plane/subcommand/server-acl-init/rules.go b/control-plane/subcommand/server-acl-init/rules.go index 2732188305..5f65b6c75c 100644 --- a/control-plane/subcommand/server-acl-init/rules.go +++ b/control-plane/subcommand/server-acl-init/rules.go @@ -321,9 +321,9 @@ partition "{{ .PartitionName }}" { func (c *Command) injectRules() (string, error) { // The Connect injector needs permissions to create namespaces when namespaces are enabled. // It must also create/update service health checks via the endpoints controller. - // When ACLs are enabled, the endpoints controller (V1) or pod controller (v2) - // needs "acl:write" permissions to delete ACL tokens created via "consul login". - // policy = "write" is required when creating namespaces within a partition. + // When ACLs are enabled, the endpoints controller needs "acl:write" permissions + // to delete ACL tokens created via "consul login". policy = "write" is required when + // creating namespaces within a partition. injectRulesTpl := ` {{- if .EnablePartitions }} partition "{{ .PartitionName }}" { @@ -351,10 +351,6 @@ partition "{{ .PartitionName }}" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } {{- if .EnableNamespaces }} } {{- end }} diff --git a/control-plane/subcommand/server-acl-init/rules_test.go b/control-plane/subcommand/server-acl-init/rules_test.go index c1a02a2218..a45af33c11 100644 --- a/control-plane/subcommand/server-acl-init/rules_test.go +++ b/control-plane/subcommand/server-acl-init/rules_test.go @@ -8,9 +8,8 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/stretchr/testify/require" ) func TestAgentRules(t *testing.T) { @@ -964,10 +963,6 @@ func TestInjectRules(t *testing.T) { service_prefix "" { policy = "write" intentions = "write" - } - identity_prefix "" { - policy = "write" - intentions = "write" }`, }, { @@ -987,10 +982,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -1011,10 +1002,6 @@ func TestInjectRules(t *testing.T) { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } }`, }, { @@ -1036,10 +1023,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, @@ -1063,10 +1046,6 @@ partition "part-1" { policy = "write" intentions = "write" } - identity_prefix "" { - policy = "write" - intentions = "write" - } } }`, }, diff --git a/control-plane/subcommand/sync-catalog/command.go b/control-plane/subcommand/sync-catalog/command.go index e461121f3d..2dadf6e039 100644 --- a/control-plane/subcommand/sync-catalog/command.go +++ b/control-plane/subcommand/sync-catalog/command.go @@ -16,13 +16,6 @@ import ( "time" mapset "github.com/deckarep/golang-set" - "github.com/hashicorp/consul-server-connection-manager/discovery" - "github.com/hashicorp/go-hclog" - "github.com/mitchellh/cli" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - catalogtoconsul "github.com/hashicorp/consul-k8s/control-plane/catalog/to-consul" catalogtok8s "github.com/hashicorp/consul-k8s/control-plane/catalog/to-k8s" "github.com/hashicorp/consul-k8s/control-plane/consul" @@ -30,6 +23,12 @@ import ( "github.com/hashicorp/consul-k8s/control-plane/subcommand" "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" + "github.com/hashicorp/consul-server-connection-manager/discovery" + "github.com/hashicorp/go-hclog" + "github.com/mitchellh/cli" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ) // Command is the command for syncing the K8S and Consul service diff --git a/control-plane/subcommand/sync-catalog/command_ent_test.go b/control-plane/subcommand/sync-catalog/command_ent_test.go index 8af712dcbe..fb6c6c4347 100644 --- a/control-plane/subcommand/sync-catalog/command_ent_test.go +++ b/control-plane/subcommand/sync-catalog/command_ent_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -22,8 +23,6 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test syncing to a single destination consul namespace. diff --git a/control-plane/subcommand/sync-catalog/command_test.go b/control-plane/subcommand/sync-catalog/command_test.go index fdabb957f7..0223931cc1 100644 --- a/control-plane/subcommand/sync-catalog/command_test.go +++ b/control-plane/subcommand/sync-catalog/command_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/test" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -18,8 +19,6 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/helper/test" ) // Test flag validation. diff --git a/control-plane/subcommand/tls-init/command.go b/control-plane/subcommand/tls-init/command.go index 467e4cf89a..c2498a3125 100644 --- a/control-plane/subcommand/tls-init/command.go +++ b/control-plane/subcommand/tls-init/command.go @@ -13,17 +13,16 @@ import ( "sync" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) type Command struct { diff --git a/control-plane/subcommand/webhook-cert-manager/command.go b/control-plane/subcommand/webhook-cert-manager/command.go index ae9d75d29e..4d85565b62 100644 --- a/control-plane/subcommand/webhook-cert-manager/command.go +++ b/control-plane/subcommand/webhook-cert-manager/command.go @@ -17,6 +17,11 @@ import ( "syscall" "time" + "github.com/hashicorp/consul-k8s/control-plane/helper/cert" + mutatingwebhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/mutating-webhook-configuration" + "github.com/hashicorp/consul-k8s/control-plane/subcommand" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/mitchellh/cli" @@ -24,12 +29,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - - "github.com/hashicorp/consul-k8s/control-plane/helper/cert" - webhookconfiguration "github.com/hashicorp/consul-k8s/control-plane/helper/webhook-configuration" - "github.com/hashicorp/consul-k8s/control-plane/subcommand" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/flags" ) const ( @@ -267,7 +266,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration") - err = webhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, c.clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration") return err @@ -310,7 +309,7 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete } iterLog.Info("Updating webhook configuration with new CA") - err = webhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) + err = mutatingwebhookconfiguration.UpdateWithCABundle(ctx, clientset, bundle.WebhookConfigName, bundle.CACert) if err != nil { iterLog.Error("Error updating webhook configuration", "err", err) return err @@ -321,21 +320,11 @@ func (c *Command) reconcileCertificates(ctx context.Context, clientset kubernete // webhookUpdated verifies if every caBundle on the specified webhook configuration matches the desired CA certificate. // It returns true if the CA is up-to date and false if it needs to be updated. func (c *Command) webhookUpdated(ctx context.Context, bundle cert.MetaBundle, clientset kubernetes.Interface) bool { - mutatingWebhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) + webhookCfg, err := clientset.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) if err != nil { return false } - for _, webhook := range mutatingWebhookCfg.Webhooks { - if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { - return false - } - } - - validatingWebhookCfg, err := clientset.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, bundle.WebhookConfigName, metav1.GetOptions{}) - if err != nil { - return false - } - for _, webhook := range validatingWebhookCfg.Webhooks { + for _, webhook := range webhookCfg.Webhooks { if !bytes.Equal(webhook.ClientConfig.CABundle, bundle.CACert) { return false } @@ -355,12 +344,8 @@ func (c webhookConfig) validate(ctx context.Context, client kubernetes.Interface if c.Name == "" { err = multierror.Append(err, errors.New(`config.Name cannot be ""`)) } else { - _, mutHookErr := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) - - _, validatingHookErr := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}) - - if (mutHookErr != nil && k8serrors.IsNotFound(mutHookErr)) && (validatingHookErr != nil && k8serrors.IsNotFound(validatingHookErr)) { - err = multierror.Append(err, fmt.Errorf("ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) + if _, err2 := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, c.Name, metav1.GetOptions{}); err2 != nil && k8serrors.IsNotFound(err2) { + err = multierror.Append(err, fmt.Errorf("MutatingWebhookConfiguration with name \"%s\" must exist in cluster", c.Name)) } } if c.SecretName == "" { @@ -402,12 +387,10 @@ func (c *Command) sendSignal(sig os.Signal) { c.sigCh <- sig } -const ( - synopsis = "Starts the Consul Kubernetes webhook-cert-manager" - help = ` +const synopsis = "Starts the Consul Kubernetes webhook-cert-manager" +const help = ` Usage: consul-k8s-control-plane webhook-cert-manager [options] Starts the Consul Kubernetes webhook-cert-manager that manages the lifecycle for webhook TLS certificates. ` -) diff --git a/control-plane/subcommand/webhook-cert-manager/command_test.go b/control-plane/subcommand/webhook-cert-manager/command_test.go index dd4b6504c0..31c98b0ebe 100644 --- a/control-plane/subcommand/webhook-cert-manager/command_test.go +++ b/control-plane/subcommand/webhook-cert-manager/command_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" + "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/mitchellh/cli" "github.com/stretchr/testify/require" @@ -20,9 +22,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" - - "github.com/hashicorp/consul-k8s/control-plane/subcommand/common" - "github.com/hashicorp/consul-k8s/control-plane/subcommand/webhook-cert-manager/mocks" ) func TestRun_ExitsCleanlyOnSignals(t *testing.T) { @@ -702,7 +701,7 @@ func TestValidate(t *testing.T) { SecretNamespace: "default", }, clientset: fake.NewSimpleClientset(), - expErr: `ValidatingWebhookConfiguration or MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, + expErr: `MutatingWebhookConfiguration with name "webhook-config-name" must exist in cluster`, }, "secretName": { config: webhookConfig{ diff --git a/control-plane/version/version.go b/control-plane/version/version.go index da2c79a1b4..01bc4acfbb 100644 --- a/control-plane/version/version.go +++ b/control-plane/version/version.go @@ -17,7 +17,7 @@ var ( // // Version must conform to the format expected by // github.com/hashicorp/go-version for tests to work. - Version = "1.4.0" + Version = "1.2.4" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/hack/aws-acceptance-test-cleanup/main.go b/hack/aws-acceptance-test-cleanup/main.go index e4094ec47e..d62e5e4405 100644 --- a/hack/aws-acceptance-test-cleanup/main.go +++ b/hack/aws-acceptance-test-cleanup/main.go @@ -25,7 +25,6 @@ import ( "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/eks" "github.com/aws/aws-sdk-go/service/elb" - "github.com/aws/aws-sdk-go/service/iam" "github.com/cenkalti/backoff/v4" ) @@ -39,11 +38,6 @@ var ( errNotDestroyed = errors.New("not yet destroyed") ) -type oidc struct { - arn string - buildUrl string -} - func main() { flag.BoolVar(&flagAutoApprove, "auto-approve", false, "Skip interactive approval before destroying.") flag.Parse() @@ -74,106 +68,6 @@ func realMain(ctx context.Context) error { eksClient := eks.New(clientSession, awsCfg) ec2Client := ec2.New(clientSession, awsCfg) elbClient := elb.New(clientSession, awsCfg) - iamClient := iam.New(clientSession, awsCfg) - - // Find OIDC providers to delete. - oidcProvidersOutput, err := iamClient.ListOpenIDConnectProvidersWithContext(ctx, &iam.ListOpenIDConnectProvidersInput{}) - if err != nil { - return err - } else if oidcProvidersOutput == nil { - return fmt.Errorf("nil output for OIDC Providers") - } - - toDeleteOidcArns := []*oidc{} - for _, providerEntry := range oidcProvidersOutput.OpenIDConnectProviderList { - arnString := "" - if providerEntry.Arn != nil { - arnString = *providerEntry.Arn - } - // Check if it's older than 8 hours. - older, err := oidcOlderThanEightHours(ctx, iamClient, providerEntry.Arn) - if err != nil { - return err - } - // Only add to delete list if it's older than 8 hours and has a buildURL tag. - if older { - output, err := iamClient.ListOpenIDConnectProviderTags(&iam.ListOpenIDConnectProviderTagsInput{OpenIDConnectProviderArn: providerEntry.Arn}) - if err != nil { - return err - } - for _, tag := range output.Tags { - if tag.Key != nil && *tag.Key == buildURLTag { - var buildUrl string - if tag.Value != nil { - buildUrl = *tag.Value - } - toDeleteOidcArns = append(toDeleteOidcArns, &oidc{arn: arnString, buildUrl: buildUrl}) - } - } - } else { - fmt.Printf("Skipping OIDC provider: %s because it's not over 8 hours old\n", arnString) - } - } - - oidcProvidersExist := true - if len(toDeleteOidcArns) == 0 { - fmt.Println("Found no OIDC Providers to clean up") - oidcProvidersExist = false - } else { - // Print out the OIDC Provider arns and the build tags. - var oidcPrint string - for _, oidcProvider := range toDeleteOidcArns { - oidcPrint += fmt.Sprintf("- %s (%s)\n", oidcProvider.arn, oidcProvider.buildUrl) - } - - fmt.Printf("Found OIDC Providers:\n%s", oidcPrint) - } - - // Check for approval. - if !flagAutoApprove && oidcProvidersExist { - type input struct { - text string - err error - } - inputCh := make(chan input) - - // Read input in a goroutine so we can also exit if we get a Ctrl-C - // (see select{} below). - go func() { - reader := bufio.NewReader(os.Stdin) - fmt.Println("\nDo you want to delete these OIDC Providers (y/n)?") - inputStr, err := reader.ReadString('\n') - if err != nil { - inputCh <- input{err: err} - return - } - inputCh <- input{text: inputStr} - }() - - select { - case in := <-inputCh: - if in.err != nil { - return in.err - } - inputTrimmed := strings.TrimSpace(in.text) - if inputTrimmed != "y" && inputTrimmed != "yes" { - return errors.New("exiting after negative") - } - case <-ctx.Done(): - return errors.New("context cancelled") - } - } - - // Actually delete the OIDC providers. - for _, oidcArn := range toDeleteOidcArns { - fmt.Printf("Deleting OIDC provider: %s\n", oidcArn.arn) - _, err := iamClient.DeleteOpenIDConnectProviderWithContext(ctx, &iam.DeleteOpenIDConnectProviderInput{ - OpenIDConnectProviderArn: &oidcArn.arn, - }) - if err != nil { - return err - } - } // Find VPCs to delete. Most resources we create belong to a VPC, except // for IAM resources, and so if there are no VPCs, that means all leftover resources have been deleted. @@ -643,25 +537,6 @@ func realMain(ctx context.Context) error { return nil } -// oidcOlderThanEightHours checks if the oidc provider is older than 8 hours. -func oidcOlderThanEightHours(ctx context.Context, iamClient *iam.IAM, oidcArn *string) (bool, error) { - fullOidc, err := iamClient.GetOpenIDConnectProviderWithContext(ctx, &iam.GetOpenIDConnectProviderInput{ - OpenIDConnectProviderArn: oidcArn, - }) - if err != nil { - return false, err - } - if fullOidc != nil { - if fullOidc.CreateDate != nil { - d := time.Since(*fullOidc.CreateDate) - if d.Hours() > 8 { - return true, nil - } - } - } - return false, nil -} - func vpcNameAndBuildURL(vpc *ec2.Vpc) (string, string) { var vpcName string var buildURL string diff --git a/hack/camel-crds/go.mod b/hack/camel-crds/go.mod deleted file mode 100644 index 0262e70483..0000000000 --- a/hack/camel-crds/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/hashicorp/consul-k8s/hack/copy-crds-to-chart - -go 1.20 - -require ( - github.com/iancoleman/strcase v0.3.0 - sigs.k8s.io/yaml v1.3.0 -) - -require ( - github.com/kr/pretty v0.3.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) diff --git a/hack/camel-crds/go.sum b/hack/camel-crds/go.sum deleted file mode 100644 index 21efe55d32..0000000000 --- a/hack/camel-crds/go.sum +++ /dev/null @@ -1,25 +0,0 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/camel-crds/main.go b/hack/camel-crds/main.go deleted file mode 100644 index dde2ac3de2..0000000000 --- a/hack/camel-crds/main.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Script to parse a YAML CRD file and change all the -// snake_case keys to camelCase and rewrite the file in-situ -package main - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/iancoleman/strcase" - "sigs.k8s.io/yaml" -) - -func main() { - if len(os.Args) != 1 { - fmt.Println("Usage: go run ./...") - os.Exit(1) - } - - if err := realMain(); err != nil { - fmt.Printf("Error: %s\n", err) - os.Exit(1) - } - os.Exit(0) -} - -func realMain() error { - root := "../../control-plane/config/crd/" - // explicitly ignore the `external` folder since we only want this to apply to CRDs that we have built-in this project. - dirs := []string{"bases"} - - for _, dir := range dirs { - err := filepath.Walk(root+dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() || filepath.Ext(path) != ".yaml" || filepath.Base(path) == "kustomization.yaml" { - return nil - } - printf("processing %s", filepath.Base(path)) - - contentBytes, err := os.ReadFile(path) - if err != nil { - return err - } - - jsonBytes, err := yaml.YAMLToJSON(contentBytes) - if err != nil { - return err - } - fixedJsonBytes := convertKeys(jsonBytes) - contentsCamel, err := yaml.JSONToYAML(fixedJsonBytes) - return os.WriteFile(path, contentsCamel, os.ModePerm) - }) - if err != nil { - return err - } - } - return nil -} - -func convertKeys(j json.RawMessage) json.RawMessage { - m := make(map[string]json.RawMessage) - n := make([]json.RawMessage, 0) - array := false - if err := json.Unmarshal(j, &m); err != nil { - // Not a JSON object - errArray := json.Unmarshal(j, &n) - if errArray != nil { - return j - } else { - array = true - } - } - if !array { - for k, v := range m { - if k == "annotations" { - continue - } - var fixed string - if !strings.Contains(k, "_") { - fixed = k - } else { - fixed = strcase.ToLowerCamel(k) - } - delete(m, k) - m[fixed] = convertKeys(v) - } - - b, err := json.Marshal(m) - if err != nil { - fmt.Printf("something went wrong", err) - return j - } - return b - } else { - for i, message := range n { - fixed := convertKeys(message) - n[i] = fixed - } - b, err := json.Marshal(n) - if err != nil { - fmt.Printf("something went wrong", err) - return j - } - return b - } -} - -func printf(format string, args ...interface{}) { - fmt.Println(fmt.Sprintf(format, args...)) -} diff --git a/hack/copy-crds-to-chart/main.go b/hack/copy-crds-to-chart/main.go index 435918c53d..149126b392 100644 --- a/hack/copy-crds-to-chart/main.go +++ b/hack/copy-crds-to-chart/main.go @@ -79,13 +79,7 @@ func realMain(helmPath string) error { ` release: {{ .Release.Name }}`, ` component: crd`, } - var split int - if dir == "bases" { - split = 6 - } else { - split = 9 - } - withLabels := append(splitOnNewlines[0:split], append(labelLines, splitOnNewlines[split:]...)...) + withLabels := append(splitOnNewlines[0:9], append(labelLines, splitOnNewlines[9:]...)...) contents = strings.Join(withLabels, "\n") var crdName string @@ -95,7 +89,7 @@ func realMain(helmPath string) error { crdName = filenameSplit[1] } else if dir == "external" { filenameSplit := strings.Split(info.Name(), ".") - crdName = filenameSplit[0] + "-external.yaml" + crdName = filenameSplit[0] + ".yaml" } destinationPath := filepath.Join(helmPath, "templates", fmt.Sprintf("crd-%s", crdName))