diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a86eb257635..9008aeb6adb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,9 @@ updates: schedule: interval: "weekly" day: "sunday" + cooldown: + default-days: 3 + semver-major-days: 15 labels: - area/dependency - release-note/none-required @@ -18,89 +21,25 @@ updates: patterns: - "k8s.io/*" - target-branch: main - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - day: "sunday" - labels: - - area/tooling - - release-note/none-required - groups: - artifact-actions: - patterns: - - "actions/upload-artifact" - - "actions/download-artifact" - -# release branch N targets -- target-branch: release-1.33 package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" - day: "sunday" - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-major" - - "version-update:semver-minor" - labels: - - area/dependency - - release-note/none-required - groups: - k8s-dependencies: - patterns: - - "k8s.io/*" -- target-branch: release-1.33 - package-ecosystem: "github-actions" - directory: "/" + directory: "/tools" schedule: interval: "weekly" day: "sunday" - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-major" - - "version-update:semver-minor" + cooldown: + default-days: 3 + semver-major-days: 15 labels: - area/tooling - release-note/none-required - groups: - artifact-actions: - patterns: - - "actions/upload-artifact" - - "actions/download-artifact" - -# release branch N-1 targets -- target-branch: release-1.32 - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "weekly" - day: "sunday" - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-major" - - "version-update:semver-minor" - labels: - - area/dependency - - release-note/none-required - groups: - k8s-dependencies: - patterns: - - "k8s.io/*" -- target-branch: release-1.32 +- target-branch: main package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" day: "sunday" - ignore: - - dependency-name: "*" - update-types: - - "version-update:semver-major" - - "version-update:semver-minor" + cooldown: + default-days: 3 labels: - area/tooling - release-note/none-required @@ -110,13 +49,15 @@ updates: - "actions/upload-artifact" - "actions/download-artifact" -# release branch N-2 targets -- target-branch: release-1.31 +# release branch targets +- target-branch: release-1.33 package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" day: "sunday" + cooldown: + default-days: 3 ignore: - dependency-name: "*" update-types: @@ -129,12 +70,14 @@ updates: k8s-dependencies: patterns: - "k8s.io/*" -- target-branch: release-1.31 +- target-branch: release-1.33 package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" day: "sunday" + cooldown: + default-days: 3 ignore: - dependency-name: "*" update-types: @@ -148,3 +91,5 @@ updates: patterns: - "actions/upload-artifact" - "actions/download-artifact" + + diff --git a/.github/reviewers.yaml b/.github/reviewers.yaml deleted file mode 100644 index a42a3554aea..00000000000 --- a/.github/reviewers.yaml +++ /dev/null @@ -1,7 +0,0 @@ -reviewers: - defaults: - - team:contour-reviewers - -options: - ignore_draft: true - number_of_reviewers: 1 diff --git a/.github/workflows/build_daily.yaml b/.github/workflows/build_daily.yaml index 420969d49c8..270037c9dcc 100644 --- a/.github/workflows/build_daily.yaml +++ b/.github/workflows/build_daily.yaml @@ -12,16 +12,16 @@ permissions: env: GOPROXY: https://proxy.golang.org/ - GO_VERSION: 1.25.1 + GO_VERSION: 1.26.4 jobs: e2e-envoy-deployment: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -31,7 +31,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -48,10 +48,10 @@ jobs: e2e-ipv6: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -61,7 +61,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false diff --git a/.github/workflows/build_main.yaml b/.github/workflows/build_main.yaml index 5eb825d5b89..6737a7407d4 100644 --- a/.github/workflows/build_main.yaml +++ b/.github/workflows/build_main.yaml @@ -14,15 +14,15 @@ jobs: permissions: packages: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 with: version: latest - name: Log in to GHCR - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/build_tag.yaml b/.github/workflows/build_tag.yaml index f4c228fa8a1..39dbc2efb3c 100644 --- a/.github/workflows/build_tag.yaml +++ b/.github/workflows/build_tag.yaml @@ -18,7 +18,7 @@ permissions: env: GOPROXY: https://proxy.golang.org/ - GO_VERSION: 1.25.1 + GO_VERSION: 1.26.4 jobs: build: @@ -26,15 +26,15 @@ jobs: permissions: packages: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 with: version: latest - name: Log in to GHCR - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -49,10 +49,10 @@ jobs: runs-on: ubuntu-latest needs: [build] steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -62,7 +62,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -77,7 +77,7 @@ jobs: export CONTOUR_E2E_IMAGE="ghcr.io/projectcontour/contour:$(git describe --tags)" make setup-kind-cluster run-gateway-conformance cleanup-kind - name: Upload gateway conformance report - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: gateway-conformance-report path: gateway-conformance-report/projectcontour-contour-*.yaml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5f5d8c9a96a..7cc88dcf385 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,9 +12,13 @@ on: permissions: contents: read +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: GOPROXY: https://proxy.golang.org/ - GO_VERSION: 1.25.1 + GO_VERSION: 1.26.4 jobs: CodeQL-Build: @@ -22,10 +26,10 @@ jobs: permissions: security-events: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -35,17 +39,36 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 with: languages: go # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - name: Autobuild - uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 + with: + category: /language:go + + CodeQL-for-Actions: + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 + with: + languages: actions + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 + with: + category: /language:actions diff --git a/.github/workflows/label_check.yaml b/.github/workflows/label_check.yaml index 5f51733c2c2..cd4b1dfc4da 100644 --- a/.github/workflows/label_check.yaml +++ b/.github/workflows/label_check.yaml @@ -23,17 +23,17 @@ jobs: name: Check release-note label set runs-on: ubuntu-latest steps: - - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 + - uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5 with: mode: minimum count: 1 labels: "release-note/major, release-note/minor, release-note/small, release-note/docs, release-note/infra, release-note/deprecation, release-note/none-required" - - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 + - uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5 with: mode: maximum count: 1 labels: "release-note/major, release-note/minor, release-note/small, release-note/docs, release-note/infra, release-note/none-required" - - uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5 + - uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5 with: mode: maximum count: 1 @@ -43,10 +43,10 @@ jobs: needs: [check-label] runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -56,7 +56,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: 'stable' cache: false diff --git a/.github/workflows/openssf-scorecard.yaml b/.github/workflows/openssf-scorecard.yaml index 4d01dbf07b7..31a7b037d48 100644 --- a/.github/workflows/openssf-scorecard.yaml +++ b/.github/workflows/openssf-scorecard.yaml @@ -21,21 +21,21 @@ jobs: security-events: write id-token: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/prbuild.yaml b/.github/workflows/prbuild.yaml index 5cf5472009d..ce648e30eba 100644 --- a/.github/workflows/prbuild.yaml +++ b/.github/workflows/prbuild.yaml @@ -13,34 +13,34 @@ permissions: env: GOPROXY: https://proxy.golang.org/ - GO_VERSION: 1.25.1 + GO_VERSION: 1.26.4 jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: golangci-lint - uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 + uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 with: - version: v2.4.0 + version: v2.9.0 args: --build-tags=e2e,conformance,gcp,oidc,none codespell: name: Codespell runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Codespell - uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 + uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 # v2.2 with: skip: .git,*.png,*.woff,*.woff2,*.eot,*.ttf,*.jpg,*.ico,*.svg,./site/themes/contour/static/fonts/README.md,./vendor,./site/public,./hack/actions/check-changefile-exists.go,go.mod,go.sum ignore_words_file: './.codespell.ignorewords' @@ -49,10 +49,10 @@ jobs: codegen: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -62,7 +62,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -81,11 +81,11 @@ jobs: - codegen runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 with: version: latest - name: Build image @@ -94,7 +94,7 @@ jobs: run: | make multiarch-build - name: Upload image - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: image path: image/contour-*.tar @@ -114,26 +114,26 @@ jobs: # image to use) for each kubernetes_version value. include: - kubernetes_version: "kubernetes:latest" - node_image: "docker.io/kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a" + node_image: "docker.io/kindest/node:v1.36.1@sha256:3489c7674813ba5d8b1a9977baea8a6e553784dab7b84759d1014dbd78f7ebd5" - kubernetes_version: "kubernetes:n-1" - node_image: "docker.io/kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2" + node_image: "docker.io/kindest/node:v1.35.5@sha256:ce977ae6d65918d0b58a5f8b5e940429c2ce42fa3a5619ec2bbc60b949c0ac95" - kubernetes_version: "kubernetes:n-2" - node_image: "docker.io/kindest/node:v1.32.8@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1" + node_image: "docker.io/kindest/node:v1.34.8@sha256:02722c2dedddcfc00febf5d27fbeb9b7b2c14294c82109ff4a85d89ac9ba3256" - config_type: "ConfigmapConfiguration" use_config_crd: "false" - config_type: "ContourConfiguration" use_config_crd: "true" steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Download image - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: image path: image - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -143,7 +143,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -173,24 +173,24 @@ jobs: # image to use) for each kubernetes_version value. include: - kubernetes_version: "kubernetes:latest" - node_image: "docker.io/kindest/node:v1.34.0@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a" + node_image: "docker.io/kindest/node:v1.36.1@sha256:3489c7674813ba5d8b1a9977baea8a6e553784dab7b84759d1014dbd78f7ebd5" - kubernetes_version: "kubernetes:n-1" - node_image: "docker.io/kindest/node:v1.33.4@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2" + node_image: "docker.io/kindest/node:v1.35.5@sha256:ce977ae6d65918d0b58a5f8b5e940429c2ce42fa3a5619ec2bbc60b949c0ac95" - kubernetes_version: "kubernetes:n-2" - node_image: "docker.io/kindest/node:v1.32.8@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1" + node_image: "docker.io/kindest/node:v1.34.8@sha256:02722c2dedddcfc00febf5d27fbeb9b7b2c14294c82109ff4a85d89ac9ba3256" steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false # Fetch history for all tags and branches so we can figure out most # recent release tag. fetch-depth: 0 - name: Download image - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: image path: image - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -200,7 +200,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -224,10 +224,10 @@ jobs: - codegen runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -237,7 +237,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -251,7 +251,7 @@ jobs: make check-coverage - name: codeCoverage if: ${{ success() }} - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.out @@ -262,10 +262,10 @@ jobs: - codegen runs-on: macos-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Windows) @@ -275,7 +275,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false @@ -291,15 +291,15 @@ jobs: runs-on: ubuntu-latest needs: [build-image] steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Download image - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: image path: image - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: # * Module download cache # * Build cache (Linux) @@ -309,7 +309,7 @@ jobs: key: ${{ runner.os }}-${{ github.job }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-${{ github.job }}-go- - - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: ${{ env.GO_VERSION }} cache: false diff --git a/.github/workflows/request-reviews.yaml b/.github/workflows/request-reviews.yaml deleted file mode 100644 index 467d23c7d73..00000000000 --- a/.github/workflows/request-reviews.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Request Reviews - -on: - pull_request_target: - types: [opened, ready_for_review, reopened] - -permissions: - contents: read - -jobs: - request-reviews: - runs-on: ubuntu-latest - steps: - - uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0 - with: - token: ${{ secrets.PAT_FOR_AUTO_REQUEST_REVIEW }} - config: .github/reviewers.yaml diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index f1bb1ec5a26..e8c08025c31 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -17,7 +17,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} exempt-all-milestones: true diff --git a/.github/workflows/trivy-scan.yaml b/.github/workflows/trivy-scan.yaml index f5c00861238..ad92831a752 100644 --- a/.github/workflows/trivy-scan.yaml +++ b/.github/workflows/trivy-scan.yaml @@ -17,17 +17,15 @@ jobs: branch: - main - release-1.33 - - release-1.32 - - release-1.31 runs-on: ubuntu-latest permissions: security-events: write steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false ref: ${{ matrix.branch }} - - uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # 0.36.0 with: scanners: vuln scan-type: 'fs' @@ -35,6 +33,6 @@ jobs: output: 'trivy-results.sarif' ignore-unfixed: true severity: 'HIGH,CRITICAL' - - uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.29.5 + - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v3.29.5 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/welcome-new-contributors.yaml b/.github/workflows/welcome-new-contributors.yaml deleted file mode 100644 index 4e9fb0d5c5c..00000000000 --- a/.github/workflows/welcome-new-contributors.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: 'Welcome New Contributors' - -on: - issues: - types: [opened] - # Workloads with pull_request_target and the GitHub Token secret should never include executing untrusted code - # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target - # And https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - pull_request_target: - types: [opened] - -permissions: - contents: read - -jobs: - welcome-new-contributor: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - name: 'Greet the contributor' - uses: garg3133/welcome-new-contributors@a38583ed8282e23d63d7bf919ca2d9fb95300ca6 # v1.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-message: > - Hey @contributor_name! Thanks for opening your first issue. We appreciate your contribution and welcome you to our community! - We are glad to have you here and to have your input on Contour. - You can also join us on [our mailing list](https://groups.google.com/g/project-contour) and [in our channel](https://kubernetes.slack.com/archives/C8XRH2R4J) - in the [Kubernetes Slack Workspace](https://communityinviter.com/apps/kubernetes/community) - pr-message: > - Hi @contributor_name! Welcome to our community and thank you for opening your first Pull Request. - Someone will review it soon. Thank you for committing to making Contour better. - You can also join us on [our mailing list](https://groups.google.com/g/project-contour) and [in our channel](https://kubernetes.slack.com/archives/C8XRH2R4J) - in the [Kubernetes Slack Workspace](https://communityinviter.com/apps/kubernetes/community) diff --git a/.golangci.yml b/.golangci.yml index 3079a8055da..c12cd9df96c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -107,6 +107,9 @@ linters: - testifylint path: test/e2e text: require must only be used in the goroutine running the test function + - linters: + - staticcheck + text: "SA1019.*SubjectName.*migrate to using the plural field subjectNames" paths: - third_party$ - builtin$ diff --git a/Makefile b/Makefile index fb612ff585f..14a0a3d528f 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif IMAGE_PLATFORMS ?= linux/amd64,linux/arm64 # Base build image to use. -BUILD_BASE_IMAGE ?= m.daocloud.io/docker.io/library/golang:1.25.1 +BUILD_BASE_IMAGE ?= m.daocloud.io/docker.io/library/golang:1.26.4@sha256:68cb6d68bed024785b69195b89af7ac7a444f27791435f98647edff595aa0479 # Enable build with CGO. BUILD_CGO_ENABLED ?= 0 @@ -134,9 +134,9 @@ multiarch-build: --build-arg "BUILD_CGO_ENABLED=$(BUILD_CGO_ENABLED)" \ --build-arg "BUILD_EXTRA_GO_LDFLAGS=$(BUILD_EXTRA_GO_LDFLAGS)" \ --build-arg "BUILD_GOEXPERIMENT=$(BUILD_GOEXPERIMENT)" \ - --label "commit.sync.upstream=3e57486" \ + --label "commit.sync.upstream=da87188" \ $(DOCKER_BUILD_LABELS) \ - -t release-ci.daocloud.io/skoala/contour:v1.30.0-3e57486 \ + -t release-ci.daocloud.io/skoala/contour:v1.33.5-da87188 \ $(shell pwd) \ --push @@ -223,9 +223,9 @@ lint-flags: .PHONY: format format: ## Run gofumpt to format the codebase. @echo Running gofumpt... - @go run mvdan.cc/gofumpt@v0.5.0 -l -w -extra . + @go tool -modfile=tools/go.mod mvdan.cc/gofumpt -l -w -extra . @echo Running gci... - @go run github.com/daixiang0/gci@v0.12.1 write . --skip-generated -s standard -s default -s "prefix(github.com/projectcontour/contour)" --custom-order + @go tool -modfile=tools/go.mod github.com/daixiang0/gci write . --skip-generated -s standard -s default -s "prefix(github.com/projectcontour/contour)" --custom-order .PHONY: generate generate: ## Re-generate generated code and documentation @@ -273,7 +273,7 @@ generate-metrics-docs: .PHONY: generate-go generate-go: @echo "Generating mocks..." - @go run github.com/vektra/mockery/v2 + @go tool -modfile=tools/go.mod github.com/vektra/mockery/v2 .PHONY: check-generate check-generate: generate @@ -328,7 +328,7 @@ e2e: | setup-kind-cluster load-contour-image-kind run-e2e cleanup-kind ## Run E2 run-e2e: CONTOUR_E2E_LOCAL_HOST=$(CONTOUR_E2E_LOCAL_HOST) \ CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \ - go run github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -skip-package=upgrade,bench -keep-going -randomize-suites -randomize-all -poll-progress-after=120s --focus '$(CONTOUR_E2E_TEST_FOCUS)' -r $(CONTOUR_E2E_PACKAGE_FOCUS) + go tool -modfile=tools/go.mod github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -skip-package=upgrade,bench -keep-going -randomize-suites -randomize-all -poll-progress-after=120s --focus '$(CONTOUR_E2E_TEST_FOCUS)' -r $(CONTOUR_E2E_PACKAGE_FOCUS) .PHONY: cleanup-kind cleanup-kind: @@ -352,7 +352,7 @@ upgrade: | setup-kind-cluster load-contour-image-kind run-upgrade cleanup-kind # run-upgrade: CONTOUR_UPGRADE_FROM_VERSION=$(CONTOUR_UPGRADE_FROM_VERSION) \ CONTOUR_E2E_IMAGE=$(CONTOUR_E2E_IMAGE) \ - go run github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -randomize-all -poll-progress-after=300s -v ./test/e2e/upgrade + go tool -modfile=tools/go.mod github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -randomize-all -poll-progress-after=300s -v ./test/e2e/upgrade .PHONY: check-ingress-conformance check-ingress-conformance: | install-contour-working run-ingress-conformance cleanup-kind ## Run Ingress controller conformance @@ -378,7 +378,7 @@ teardown-gcp-bench-cluster: .PHONY: run-bench run-bench: - go run github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -keep-going -randomize-suites -randomize-all -poll-progress-after=4h -timeout=5h -r -v ./test/e2e/bench + go tool -modfile=tools/go.mod github.com/onsi/ginkgo/v2/ginkgo -tags=e2e -mod=readonly -keep-going -randomize-suites -randomize-all -poll-progress-after=4h -timeout=5h -r -v ./test/e2e/bench .PHONY: bench bench: deploy-gcp-bench-cluster run-bench teardown-gcp-bench-cluster diff --git a/RELEASES.md b/RELEASES.md index c950030d2ab..5c5ff42cac9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,7 +13,7 @@ If for any reason this release cadence has to be adjusted (for example due to op Patch releases are based on the major/minor release branch. There is no specific release cadence for patch releases. However, the project will create patch releases to address critical community and security issues (for example to address high severity security issues in Contour or in Envoy).The project will issue patch releases for all supported versions of Contour. ### Release Support Matrix -Per the [Contour support policy](https://projectcontour.io/resources/support/), the project is in the process of transitioning to supporting three Contour releases. Please see the support policy page to see what versions are currently supported. +Per the [Contour support policy](https://projectcontour.io/resources/support/), the project supports a single release track at a time. Please see the support policy page for details. Also, please consult the [Contour Compatibility Matrix](https://projectcontour.io/resources/compatibility-matrix/) for details of what each version of Contour requires for each of its dependencies like Envoy, Kubernetes, and so on. diff --git a/SITE_CONTRIBUTION.md b/SITE_CONTRIBUTION.md index cd1105dd9ce..a36285808bb 100644 --- a/SITE_CONTRIBUTION.md +++ b/SITE_CONTRIBUTION.md @@ -58,8 +58,7 @@ A reference table, located at the end of the Markdown file, uses the following f [4]: https://httpbin.org/ [5]: https://github.com/projectcontour/community/wiki/Office-Hours [6]: {{< param slack_url >}} -[7]: https://github.com/bitnami/charts/tree/master/bitnami/contour -[8]: https://www.youtube.com/watch?v=xUJbTnN3Dmw +[7]: https://www.youtube.com/watch?v=xUJbTnN3Dmw ``` ## Using URL parameters diff --git a/apis/projectcontour/v1/helpers.go b/apis/projectcontour/v1/helpers.go index 63d83df146f..8b2bdcf8328 100644 --- a/apis/projectcontour/v1/helpers.go +++ b/apis/projectcontour/v1/helpers.go @@ -15,6 +15,7 @@ package v1 import ( "fmt" + "maps" ) // AuthorizationConfigured returns whether authorization is @@ -86,14 +87,10 @@ func (r *Route) GetPrefixReplacements() []ReplacePrefix { func (r *Route) AuthorizationContext(parent map[string]string) map[string]string { values := make(map[string]string, len(parent)) - for k, v := range parent { - values[k] = v - } + maps.Copy(values, parent) if r.AuthPolicy != nil { - for k, v := range r.AuthPolicy.Context { - values[k] = v - } + maps.Copy(values, r.AuthPolicy.Context) } if len(values) == 0 { diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go index 597213c98f8..bcc89daf0ee 100644 --- a/apis/projectcontour/v1/httpproxy.go +++ b/apis/projectcontour/v1/httpproxy.go @@ -239,15 +239,39 @@ type ExtensionServiceReference struct { Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` } +// AuthorizationServiceType indicates the protocol +// implemented by the external authorization server. +type AuthorizationServiceType string + +const ( + AuthorizationGRPCService AuthorizationServiceType = "grpc" + AuthorizationHTTPService AuthorizationServiceType = "http" +) + // AuthorizationServer configures an external server to authenticate // client requests. The external server must implement the v3 Envoy -// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). +// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto) +// or the HTTP authorization server protocol. +// +kubebuilder:validation:XValidation:message="httpSettings can only be set when serviceType is 'http'",rule="!has(self.httpSettings) || self.serviceType == 'http'" type AuthorizationServer struct { // ExtensionServiceRef specifies the extension resource that will authorize client requests. // // +optional ExtensionServiceRef ExtensionServiceReference `json:"extensionRef,omitempty"` + // ServiceType sets the protocol used to communicate with + // the external authorization server. + // + // +optional + // +kubebuilder:validation:Enum=http;grpc + // +kubebuilder:default=grpc + ServiceType AuthorizationServiceType `json:"serviceType,omitempty"` + + // HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. + // + // +optional + HTTPServerSettings *HTTPAuthorizationServerSettings `json:"httpSettings,omitempty"` + // AuthPolicy sets a default authorization policy for client requests. // This policy will be used unless overridden by individual routes. // @@ -276,6 +300,64 @@ type AuthorizationServer struct { WithRequestBody *AuthorizationServerBufferSettings `json:"withRequestBody,omitempty"` } +// HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. +type HTTPAuthorizationServerSettings struct { + // PathPrefix Sets a prefix to the value of authorization request header Path. + // + // +optional + PathPrefix string `json:"pathPrefix,omitempty"` + + // AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + // Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + // + // +optional + AllowedAuthorizationHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedAuthorizationHeaders,omitempty"` + + // AllowedUpstreamHeaders specifies response headers from the authorization server + // that may be added to the original client request before sending it to the upstream. + // + // +optional + AllowedUpstreamHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedUpstreamHeaders,omitempty"` +} + +// HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers +// in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user +// experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. +// +kubebuilder:validation:XValidation:message="only one of prefix, suffix, exact, and contains should be set in the allowedHeader",rule="(has(self.exact) ? 1 : 0) + (has(self.prefix) ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) ? 1 : 0) == 1" +type HTTPAuthorizationServerAllowedHeaders struct { + // Exact specifies a string that the header name must be equal to. + // + // +optional + Exact string `json:"exact,omitempty"` + + // Prefix defines a prefix match for the header name. + // + // +optional + Prefix string `json:"prefix,omitempty"` + + // Suffix defines a suffix match for a header name. + // + // +optional + Suffix string `json:"suffix,omitempty"` + + // To streamline user experience and mitigate potential issues, we do not support regex. + // Additionally, it's essential to ensure that any regex patterns adhere to the configured runtime key, re2.max_program_size.error_level + // by verifying that the program size is smaller than the specified value. + // This necessitates thorough validation of user input. + // + // Regex string `json:"regex,omitempty"` + + // Contains specifies a substring that must be present in the header name. + // + // +optional + Contains string `json:"contains,omitempty"` + + // IgnoreCase specifies whether string matching should be case-insensitive. + // + // +optional + IgnoreCase bool `json:"ignoreCase,omitempty"` +} + // AuthorizationServerBufferSettings enables ExtAuthz filter to buffer client request data and send it as part of authorization request type AuthorizationServerBufferSettings struct { // MaxRequestBytes sets the maximum size of message body ExtAuthz filter will hold in-memory. @@ -565,6 +647,7 @@ type VirtualHost struct { } // JWTProvider defines how to verify JWTs on requests. +// +kubebuilder:validation:XValidation:message="exactly one of remoteJWKS or localJWKS must be set",rule="(has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) && has(self.localJWKS))" type JWTProvider struct { // Unique name for the provider. // +kubebuilder:validation:Required @@ -590,9 +673,13 @@ type JWTProvider struct { // +optional Audiences []string `json:"audiences,omitempty"` - // Remote JWKS to use for verifying JWT signatures. - // +kubebuilder:validation:Required - RemoteJWKS RemoteJWKS `json:"remoteJWKS"` + // Remote JWKS fetches signing keys from an HTTP(S) endpoint. + // +optional + RemoteJWKS RemoteJWKS `json:"remoteJWKS,omitzero"` + + // Local JWKS loads signing keys from a Kubernetes Secret. + // +optional + LocalJWKS LocalJWKS `json:"localJWKS,omitzero"` // Whether the JWT should be forwarded to the backend // service after successful verification. By default, @@ -601,6 +688,19 @@ type JWTProvider struct { ForwardJWT bool `json:"forwardJWT,omitempty"` } +// LocalJWKS defines how to fetch a JWKS from a Kubernetes secret. +type LocalJWKS struct { + // The name of the secret that contains the JWKS. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + SecretName string `json:"secretName"` + + // The key of the secret that contains the JWKS. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + Key string `json:"key"` +} + // RemoteJWKS defines how to fetch a JWKS from an HTTP endpoint. type RemoteJWKS struct { // The URI for the JWKS. @@ -1619,6 +1719,7 @@ type UpstreamValidation struct { // +kubebuilder:validation:MaxLength=317 CACertificate string `json:"caSecret"` // Key which is expected to be present in the 'subjectAltName' of the presented certificate. + // // Deprecated: migrate to using the plural field subjectNames. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=250 diff --git a/apis/projectcontour/v1/zz_generated.deepcopy.go b/apis/projectcontour/v1/zz_generated.deepcopy.go index f80ad8ad792..a47969a33fb 100644 --- a/apis/projectcontour/v1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1/zz_generated.deepcopy.go @@ -50,6 +50,11 @@ func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy { func (in *AuthorizationServer) DeepCopyInto(out *AuthorizationServer) { *out = *in out.ExtensionServiceRef = in.ExtensionServiceRef + if in.HTTPServerSettings != nil { + in, out := &in.HTTPServerSettings, &out.HTTPServerSettings + *out = new(HTTPAuthorizationServerSettings) + (*in).DeepCopyInto(*out) + } if in.AuthPolicy != nil { in, out := &in.AuthPolicy, &out.AuthPolicy *out = new(AuthorizationPolicy) @@ -370,6 +375,46 @@ func (in *GlobalRateLimitPolicy) DeepCopy() *GlobalRateLimitPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPAuthorizationServerAllowedHeaders) DeepCopyInto(out *HTTPAuthorizationServerAllowedHeaders) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPAuthorizationServerAllowedHeaders. +func (in *HTTPAuthorizationServerAllowedHeaders) DeepCopy() *HTTPAuthorizationServerAllowedHeaders { + if in == nil { + return nil + } + out := new(HTTPAuthorizationServerAllowedHeaders) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPAuthorizationServerSettings) DeepCopyInto(out *HTTPAuthorizationServerSettings) { + *out = *in + if in.AllowedAuthorizationHeaders != nil { + in, out := &in.AllowedAuthorizationHeaders, &out.AllowedAuthorizationHeaders + *out = make([]HTTPAuthorizationServerAllowedHeaders, len(*in)) + copy(*out, *in) + } + if in.AllowedUpstreamHeaders != nil { + in, out := &in.AllowedUpstreamHeaders, &out.AllowedUpstreamHeaders + *out = make([]HTTPAuthorizationServerAllowedHeaders, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPAuthorizationServerSettings. +func (in *HTTPAuthorizationServerSettings) DeepCopy() *HTTPAuthorizationServerSettings { + if in == nil { + return nil + } + out := new(HTTPAuthorizationServerSettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPDirectResponsePolicy) DeepCopyInto(out *HTTPDirectResponsePolicy) { *out = *in @@ -737,6 +782,7 @@ func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { copy(*out, *in) } in.RemoteJWKS.DeepCopyInto(&out.RemoteJWKS) + out.LocalJWKS = in.LocalJWKS } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProvider. @@ -786,6 +832,21 @@ func (in *LoadBalancerPolicy) DeepCopy() *LoadBalancerPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalJWKS) DeepCopyInto(out *LocalJWKS) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalJWKS. +func (in *LocalJWKS) DeepCopy() *LocalJWKS { + if in == nil { + return nil + } + out := new(LocalJWKS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalRateLimitPolicy) DeepCopyInto(out *LocalRateLimitPolicy) { *out = *in diff --git a/apis/projectcontour/v1alpha1/accesslog.go b/apis/projectcontour/v1alpha1/accesslog.go index ab65f7e9651..65a22a2990d 100644 --- a/apis/projectcontour/v1alpha1/accesslog.go +++ b/apis/projectcontour/v1alpha1/accesslog.go @@ -63,6 +63,8 @@ var jsonFields = map[string]string{ "user_agent": "%REQ(USER-AGENT)%", "x_forwarded_for": "%REQ(X-FORWARDED-FOR)%", "x_trace_id": "%REQ(X-TRACE-ID)%", + "tls_ja3_fingerprint": "%TLS_JA3_FINGERPRINT%", + "tls_ja4_fingerprint": "%TLS_JA4_FINGERPRINT%", "contour_config_kind": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.kind)%", "contour_config_namespace": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.namespace)%", "contour_config_name": "%METADATA(ROUTE:envoy.access_loggers.file:io.projectcontour.name)%", @@ -122,6 +124,8 @@ var envoySimpleOperators = map[string]struct{}{ "RESPONSE_TX_DURATION": {}, "ROUTE_NAME": {}, "START_TIME": {}, + "TLS_JA3_FINGERPRINT": {}, + "TLS_JA4_FINGERPRINT": {}, "UPSTREAM_CLUSTER": {}, "UPSTREAM_FILTER_STATE": {}, "UPSTREAM_HEADER_BYTES_RECEIVED": {}, @@ -289,7 +293,7 @@ func (s AccessLogFormatString) Validate() error { // 2. Operator Name: "START_TIME" // 3. Arguments: "(%s)" // 4. Truncation length: ":3" -var commandOperatorRegexp = regexp.MustCompile(`%(([A-Z_]+)(\([^)]+\)(:[0-9]+)?)?%)?`) +var commandOperatorRegexp = regexp.MustCompile(`%(([A-Z0-9_]+)(\([^)]+\)(:[0-9]+)?)?%)?`) func parseAccessLogFormatString(format string) error { // FindAllStringSubmatch will always return a slice with matches where every slice is a slice diff --git a/apis/projectcontour/v1alpha1/accesslog_test.go b/apis/projectcontour/v1alpha1/accesslog_test.go index eed731970cf..98e5aa297f6 100644 --- a/apis/projectcontour/v1alpha1/accesslog_test.go +++ b/apis/projectcontour/v1alpha1/accesslog_test.go @@ -80,6 +80,10 @@ func TestValidateAccessLogJSONFields(t *testing.T) { {"dog=pug", "cat=black"}, {"grpc_status"}, {"grpc_status_number"}, + {"tls_ja3_fingerprint"}, + {"tls_ja4_fingerprint"}, + {"@timestamp", "ja3=%TLS_JA3_FINGERPRINT%"}, + {"@timestamp", "ja4=%TLS_JA4_FINGERPRINT%"}, } for _, c := range successCases { @@ -133,6 +137,9 @@ func TestAccessLogFormatString(t *testing.T) { "%UPSTREAM_PEER_CERT_V_END%\n", "%UPSTREAM_PEER_CERT%\n", "%UPSTREAM_FILTER_STATE%\n", + "%TLS_JA3_FINGERPRINT%\n", + "%TLS_JA4_FINGERPRINT%\n", + "ja3=%TLS_JA3_FINGERPRINT% ja4=%TLS_JA4_FINGERPRINT%\n", } for _, c := range successCases { diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index e8070f3a46e..2d4f64e5dcc 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -410,7 +410,7 @@ type EnvoyListenerConfig struct { // TLS holds various configurable Envoy TLS listener values. // +optional - TLS *EnvoyTLS `json:"tls,omitempty"` + TLS *EnvoyListenerTLS `json:"tls,omitempty"` // SocketOptions defines configurable socket options for the listeners. // Single set of options are applied to all listeners. @@ -465,7 +465,8 @@ type SocketOptions struct { TrafficClass int32 `json:"trafficClass,omitempty"` } -// EnvoyTLS describes tls parameters for Envoy listneners. +// EnvoyTLS describes TLS protocol parameters shared between +// listener and upstream TLS contexts. type EnvoyTLS struct { // MinimumProtocolVersion is the minimum TLS version this vhost should // negotiate. @@ -524,6 +525,46 @@ type EnvoyTLS struct { CipherSuites []string `json:"cipherSuites,omitempty"` } +// EnvoyListenerTLS describes TLS parameters for Envoy listeners. +// It extends EnvoyTLS with listener-specific settings like TLS fingerprinting. +type EnvoyListenerTLS struct { + EnvoyTLS `json:",inline"` + + // Fingerprint defines TLS fingerprinting configuration + // for the TLS Inspector listener filter. + // +optional + Fingerprint *TLSFingerprint `json:"fingerprint,omitempty"` +} + +// TLSFingerprint defines TLS fingerprinting configuration for the TLS Inspector. +type TLSFingerprint struct { + // JA3 enables JA3 fingerprinting in the TLS Inspector. + // When true, populates JA3 hash in dynamic metadata. + // +optional + JA3 *bool `json:"ja3,omitempty"` + + // JA4 enables JA4 fingerprinting in the TLS Inspector. + // When true, populates JA4 hash in dynamic metadata. + // +optional + JA4 *bool `json:"ja4,omitempty"` +} + +// GetJA3 returns the JA3 fingerprinting setting, or nil if not configured. +func (t *EnvoyListenerTLS) GetJA3() *bool { + if t == nil || t.Fingerprint == nil { + return nil + } + return t.Fingerprint.JA3 +} + +// GetJA4 returns the JA4 fingerprinting setting, or nil if not configured. +func (t *EnvoyListenerTLS) GetJA4() *bool { + if t == nil || t.Fingerprint == nil { + return nil + } + return t.Fingerprint.JA4 +} + // EnvoyListener defines parameters for an Envoy Listener. type EnvoyListener struct { // Defines an Envoy Listener Address. @@ -830,6 +871,16 @@ type TracingConfig struct { // +optional OverallSampling *string `json:"overallSampling,omitempty"` + // ClientSampling defines the sampling rate when x-client-trace-id header is set. + // contour's default is 100. + // +optional + ClientSampling *string `json:"clientSampling,omitempty"` + + // RandomSampling defines the random sampling rate for all requests. + // contour's default is 100. + // +optional + RandomSampling *string `json:"randomSampling,omitempty"` + // MaxPathTagLength defines maximum length of the request path // to extract and include in the HttpUrl tag. // contour's default is 256. diff --git a/apis/projectcontour/v1alpha1/contourconfig_helpers.go b/apis/projectcontour/v1alpha1/contourconfig_helpers.go index 3e980ed38ca..4b04fc7c6c1 100644 --- a/apis/projectcontour/v1alpha1/contourconfig_helpers.go +++ b/apis/projectcontour/v1alpha1/contourconfig_helpers.go @@ -64,6 +64,20 @@ func (t *TracingConfig) Validate() error { } } + if t.ClientSampling != nil { + _, err := strconv.ParseFloat(*t.ClientSampling, 64) + if err != nil { + return fmt.Errorf("invalid tracing client sampling: %v", err) + } + } + + if t.RandomSampling != nil { + _, err := strconv.ParseFloat(*t.RandomSampling, 64) + if err != nil { + return fmt.Errorf("invalid tracing random sampling: %v", err) + } + } + var customTagNames []string for _, customTag := range t.CustomTags { @@ -179,7 +193,7 @@ func ValidateTLSProtocolVersions(minVersion, maxVersion string) error { func isValidTLSCipher(cipherSpec string) bool { // Equal-preference group: [cipher1|cipher2|...] if strings.HasPrefix(cipherSpec, "[") && strings.HasSuffix(cipherSpec, "]") { - for _, cipher := range strings.Split(strings.Trim(cipherSpec, "[]"), "|") { + for cipher := range strings.SplitSeq(strings.Trim(cipherSpec, "[]"), "|") { if _, ok := ValidTLSCiphers[cipher]; !ok { return false } diff --git a/apis/projectcontour/v1alpha1/contourconfig_helpers_test.go b/apis/projectcontour/v1alpha1/contourconfig_helpers_test.go index ff3afd005f5..6c09b69d2f9 100644 --- a/apis/projectcontour/v1alpha1/contourconfig_helpers_test.go +++ b/apis/projectcontour/v1alpha1/contourconfig_helpers_test.go @@ -93,7 +93,7 @@ func TestContourConfigurationSpecValidate(t *testing.T) { c = contour_v1alpha1.ContourConfigurationSpec{ Envoy: &contour_v1alpha1.EnvoyConfig{ Listener: &contour_v1alpha1.EnvoyListenerConfig{ - TLS: &contour_v1alpha1.EnvoyTLS{}, + TLS: &contour_v1alpha1.EnvoyListenerTLS{}, }, }, } @@ -200,6 +200,18 @@ func TestContourConfigurationSpecValidate(t *testing.T) { c.Tracing.OverallSampling = ptr.To("10") require.NoError(t, c.Validate()) + c.Tracing.ClientSampling = ptr.To("invalid") + require.Error(t, c.Validate()) + + c.Tracing.ClientSampling = ptr.To("20") + require.NoError(t, c.Validate()) + + c.Tracing.RandomSampling = ptr.To("not-a-number") + require.Error(t, c.Validate()) + + c.Tracing.RandomSampling = ptr.To("30") + require.NoError(t, c.Validate()) + customTags := []*contour_v1alpha1.CustomTag{ { TagName: "first tag", diff --git a/apis/projectcontour/v1alpha1/contourdeployment.go b/apis/projectcontour/v1alpha1/contourdeployment.go index d792ef37f09..38bf950e237 100644 --- a/apis/projectcontour/v1alpha1/contourdeployment.go +++ b/apis/projectcontour/v1alpha1/contourdeployment.go @@ -335,7 +335,7 @@ type NetworkPublishing struct { // If unset, defaults to "Local". // // +optional - ExternalTrafficPolicy core_v1.ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty"` + ExternalTrafficPolicy core_v1.ServiceExternalTrafficPolicy `json:"externalTrafficPolicy,omitempty"` // IPFamilyPolicy represents the dual-stack-ness requested or required by // this Service. If there is no value provided, then this field will be set diff --git a/apis/projectcontour/v1alpha1/extensionservice.go b/apis/projectcontour/v1alpha1/extensionservice.go index c074de546e7..c936f98c786 100644 --- a/apis/projectcontour/v1alpha1/extensionservice.go +++ b/apis/projectcontour/v1alpha1/extensionservice.go @@ -62,7 +62,7 @@ type ExtensionServiceTarget struct { // ExtensionServiceSpec defines the desired state of an ExtensionService resource. type ExtensionServiceSpec struct { // Services specifies the set of Kubernetes Service resources that - // receive GRPC extension API requests. + // receive extension API requests. // If no weights are specified for any of the entries in // this array, traffic will be spread evenly across all the // services. @@ -78,15 +78,15 @@ type ExtensionServiceSpec struct { UpstreamValidation *contour_v1.UpstreamValidation `json:"validation,omitempty"` // Protocol may be used to specify (or override) the protocol used to reach this Service. - // Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + // Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. // // +optional - // +kubebuilder:validation:Enum=h2;h2c + // +kubebuilder:validation:Enum=http/1.1;h2;h2c Protocol *string `json:"protocol,omitempty"` - // The policy for load balancing GRPC service requests. Note that the + // The policy for load balancing service requests. Note that the // `Cookie` and `RequestHash` load balancing strategies cannot be used - // here. + // here for GRPC service requests. // // +optional LoadBalancerPolicy *contour_v1.LoadBalancerPolicy `json:"loadBalancerPolicy,omitempty"` diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go index 76811476e39..b8b985cd8d2 100644 --- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go @@ -654,7 +654,7 @@ func (in *EnvoyListenerConfig) DeepCopyInto(out *EnvoyListenerConfig) { } if in.TLS != nil { in, out := &in.TLS, &out.TLS - *out = new(EnvoyTLS) + *out = new(EnvoyListenerTLS) (*in).DeepCopyInto(*out) } if in.SocketOptions != nil { @@ -689,6 +689,27 @@ func (in *EnvoyListenerConfig) DeepCopy() *EnvoyListenerConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnvoyListenerTLS) DeepCopyInto(out *EnvoyListenerTLS) { + *out = *in + in.EnvoyTLS.DeepCopyInto(&out.EnvoyTLS) + if in.Fingerprint != nil { + in, out := &in.Fingerprint, &out.Fingerprint + *out = new(TLSFingerprint) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyListenerTLS. +func (in *EnvoyListenerTLS) DeepCopy() *EnvoyListenerTLS { + if in == nil { + return nil + } + out := new(EnvoyListenerTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyLogging) DeepCopyInto(out *EnvoyLogging) { *out = *in @@ -1293,6 +1314,31 @@ func (in *TLS) DeepCopy() *TLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSFingerprint) DeepCopyInto(out *TLSFingerprint) { + *out = *in + if in.JA3 != nil { + in, out := &in.JA3, &out.JA3 + *out = new(bool) + **out = **in + } + if in.JA4 != nil { + in, out := &in.JA4, &out.JA4 + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSFingerprint. +func (in *TLSFingerprint) DeepCopy() *TLSFingerprint { + if in == nil { + return nil + } + out := new(TLSFingerprint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TimeoutParameters) DeepCopyInto(out *TimeoutParameters) { *out = *in @@ -1361,6 +1407,16 @@ func (in *TracingConfig) DeepCopyInto(out *TracingConfig) { *out = new(string) **out = **in } + if in.ClientSampling != nil { + in, out := &in.ClientSampling, &out.ClientSampling + *out = new(string) + **out = **in + } + if in.RandomSampling != nil { + in, out := &in.RandomSampling, &out.RandomSampling + *out = new(string) + **out = **in + } if in.MaxPathTagLength != nil { in, out := &in.MaxPathTagLength, &out.MaxPathTagLength *out = new(uint32) diff --git a/changelogs/CHANGELOG-v1.31.3.md b/changelogs/CHANGELOG-v1.31.3.md new file mode 100644 index 00000000000..65c1eddd654 --- /dev/null +++ b/changelogs/CHANGELOG-v1.31.3.md @@ -0,0 +1,26 @@ +We are delighted to present version v1.31.3 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Envoy to v1.34.12. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.34.12/version_history/v1.34/v1.34) for more information about the content of the release. +- Updates Go to v1.24.11. See the [Go release notes](https://go.dev/doc/devel/release#go1.24.minor) for more information about the content of the release. + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.31.3 is tested against Kubernetes 1.30 through 1.32. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.31.4.md b/changelogs/CHANGELOG-v1.31.4.md new file mode 100644 index 00000000000..84504843c19 --- /dev/null +++ b/changelogs/CHANGELOG-v1.31.4.md @@ -0,0 +1,26 @@ +We are delighted to present version v1.31.4 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Go to v1.24.13. See the [Go release notes](https://go.dev/doc/devel/release#go1.24.minor) for more information about the content of the release. +- Fixes load balancer status update failures caused by `HTTPProxy` CRD schema incorrectly marking `status.loadBalancer.ingress[].ports[].error` as a required field. (#7408) + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.31.4 is tested against Kubernetes 1.30 through 1.32. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.31.5.md b/changelogs/CHANGELOG-v1.31.5.md new file mode 100644 index 00000000000..874ede762b9 --- /dev/null +++ b/changelogs/CHANGELOG-v1.31.5.md @@ -0,0 +1,24 @@ +We are delighted to present version v1.31.5 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Bumps to Envoy [v1.34.13](https://github.com/envoyproxy/envoy/releases/tag/v1.34.13) to address security vulnerabilities and improve stability. +- Updates `google.golang.org/grpc` to [v1.79.3](https://github.com/grpc/grpc-go/releases/tag/v1.79.3), which addresses [CVE-2026-33186](https://github.com/grpc/grpc-go/security/advisories/GHSA-p77j-4mvh-x3m3) (Contour is not affected). +- Removes Envoy metrics `hostPort: 8002` from example manifests. (#7476) + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.31.5 is tested against Kubernetes 1.30 through 1.32. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.31.6.md b/changelogs/CHANGELOG-v1.31.6.md new file mode 100644 index 00000000000..0e717428e44 --- /dev/null +++ b/changelogs/CHANGELOG-v1.31.6.md @@ -0,0 +1,32 @@ +We are delighted to present version v1.31.6 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +## Security fix for CVE-2026-41246 + +This release fixes [CVE-2026-41246](https://github.com/projectcontour/contour/security/advisories/GHSA-x4mj-7f9g-29h4), a Lua code injection vulnerability in Contour's [Cookie Rewriting](https://projectcontour.io/docs/1.31/config/cookie-rewriting/) feature. + +An attacker with RBAC permissions to create or modify HTTPProxy resources could craft a malicious `cookieRewritePolicies[].pathRewrite.value` that results in arbitrary code execution in the Envoy proxy. Since Envoy runs as shared infrastructure, the injected code could read Envoy's xDS client credentials from the filesystem or cause denial of service for other tenants sharing the Envoy instance. + +The fix escapes user-provided values before interpolation into Lua code. + +## Other Changes + +- Bumps to Envoy [v1.34.14](https://github.com/envoyproxy/envoy/releases/tag/v1.34.14). + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.31.6 is tested against Kubernetes 1.30 through 1.32. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.32.2.md b/changelogs/CHANGELOG-v1.32.2.md new file mode 100644 index 00000000000..7d5ead0fe0e --- /dev/null +++ b/changelogs/CHANGELOG-v1.32.2.md @@ -0,0 +1,26 @@ +We are delighted to present version v1.32.2 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Envoy to v1.34.12. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.34.12/version_history/v1.34/v1.34) for more information about the content of the release. +- Updates Go to v1.24.11. See the [Go release notes](https://go.dev/doc/devel/release#go1.24.minor) for more information about the content of the release. + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.32.2 is tested against Kubernetes 1.31 through 1.33. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.32.3.md b/changelogs/CHANGELOG-v1.32.3.md new file mode 100644 index 00000000000..7ab213174bf --- /dev/null +++ b/changelogs/CHANGELOG-v1.32.3.md @@ -0,0 +1,26 @@ +We are delighted to present version v1.32.3 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Go to v1.24.13. See the [Go release notes](https://go.dev/doc/devel/release#go1.24.minor) for more information about the content of the release. +- Fixes load balancer status update failures caused by `HTTPProxy` CRD schema incorrectly marking `status.loadBalancer.ingress[].ports[].error` as a required field. (#7408) + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.32.3 is tested against Kubernetes 1.31 through 1.33. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.32.4.md b/changelogs/CHANGELOG-v1.32.4.md new file mode 100644 index 00000000000..badf7bece08 --- /dev/null +++ b/changelogs/CHANGELOG-v1.32.4.md @@ -0,0 +1,24 @@ +We are delighted to present version v1.32.4 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Bumps to Envoy [v1.34.13](https://github.com/envoyproxy/envoy/releases/tag/v1.34.13) to address security vulnerabilities and improve stability. +- Updates `google.golang.org/grpc` to [v1.79.3](https://github.com/grpc/grpc-go/releases/tag/v1.79.3), which addresses [CVE-2026-33186](https://github.com/grpc/grpc-go/security/advisories/GHSA-p77j-4mvh-x3m3) (Contour is not affected). +- Removes Envoy metrics `hostPort: 8002` from example manifests. (#7476) + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.32.4 is tested against Kubernetes 1.31 through 1.33. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.32.5.md b/changelogs/CHANGELOG-v1.32.5.md new file mode 100644 index 00000000000..84c6440d1d6 --- /dev/null +++ b/changelogs/CHANGELOG-v1.32.5.md @@ -0,0 +1,32 @@ +We are delighted to present version v1.32.5 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +## Security fix for CVE-2026-41246 + +This release fixes [CVE-2026-41246](https://github.com/projectcontour/contour/security/advisories/GHSA-x4mj-7f9g-29h4), a Lua code injection vulnerability in Contour's [Cookie Rewriting](https://projectcontour.io/docs/1.32/config/cookie-rewriting/) feature. + +An attacker with RBAC permissions to create or modify HTTPProxy resources could craft a malicious `cookieRewritePolicies[].pathRewrite.value` that results in arbitrary code execution in the Envoy proxy. Since Envoy runs as shared infrastructure, the injected code could read Envoy's xDS client credentials from the filesystem or cause denial of service for other tenants sharing the Envoy instance. + +The fix escapes user-provided values before interpolation into Lua code. + +## Other Changes + +- Bumps to Envoy [v1.34.14](https://github.com/envoyproxy/envoy/releases/tag/v1.34.14). + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.32.5 is tested against Kubernetes 1.31 through 1.33. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.33.1.md b/changelogs/CHANGELOG-v1.33.1.md new file mode 100644 index 00000000000..a847d06fae2 --- /dev/null +++ b/changelogs/CHANGELOG-v1.33.1.md @@ -0,0 +1,26 @@ +We are delighted to present version v1.33.1 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Envoy to v1.35.8. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.35.8/version_history/v1.35/v1.35) for more information about the content of the release. +- Updates Go to v1.25.5. See the [Go release notes](https://go.dev/doc/devel/release#go1.25.minor) for more information about the content of the release. + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.33.1 is tested against Kubernetes 1.32 through 1.34. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.33.2.md b/changelogs/CHANGELOG-v1.33.2.md new file mode 100644 index 00000000000..7b5c5e84886 --- /dev/null +++ b/changelogs/CHANGELOG-v1.33.2.md @@ -0,0 +1,27 @@ +We are delighted to present version v1.33.2 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Updates Go to v1.25.7. See the [Go release notes](https://go.dev/doc/devel/release#go1.25.minor) for more information about the content of the release. +- Fixes load balancer status update failures caused by `HTTPProxy` CRD schema incorrectly marking `status.loadBalancer.ingress[].ports[].error` as a required field. (#7408) +- Increases CPU limit for the `shutdown-manager` container from `50m` to `200m` when using the Contour Gateway Provisioner, to prevent CPU throttling. (#7382) + + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + + +# Compatible Kubernetes Versions + +Contour v1.33.2 is tested against Kubernetes 1.32 through 1.34. + + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.33.3.md b/changelogs/CHANGELOG-v1.33.3.md new file mode 100644 index 00000000000..e42b8cab71d --- /dev/null +++ b/changelogs/CHANGELOG-v1.33.3.md @@ -0,0 +1,24 @@ +We are delighted to present version v1.33.3 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +- Bumps to Envoy [v1.35.9](https://github.com/envoyproxy/envoy/releases/tag/v1.35.9) to address security vulnerabilities. +- Updates `google.golang.org/grpc` to [v1.79.3](https://github.com/grpc/grpc-go/releases/tag/v1.79.3), which addresses [CVE-2026-33186](https://github.com/grpc/grpc-go/security/advisories/GHSA-p77j-4mvh-x3m3) (Contour is not affected). +- Removes Envoy metrics `hostPort: 8002` from example manifests. (#7476) + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.33.3 is tested against Kubernetes 1.32 through 1.34. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.33.4.md b/changelogs/CHANGELOG-v1.33.4.md new file mode 100644 index 00000000000..9a453eebe3c --- /dev/null +++ b/changelogs/CHANGELOG-v1.33.4.md @@ -0,0 +1,34 @@ +We are delighted to present version v1.33.4 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +## Security fix for CVE-2026-41246 + +This release fixes [CVE-2026-41246](https://github.com/projectcontour/contour/security/advisories/GHSA-x4mj-7f9g-29h4), a Lua code injection vulnerability in Contour's [Cookie Rewriting](https://projectcontour.io/docs/1.33/config/cookie-rewriting/) feature. + +An attacker with RBAC permissions to create or modify HTTPProxy resources could craft a malicious `cookieRewritePolicies[].pathRewrite.value` that results in arbitrary code execution in the Envoy proxy. Since Envoy runs as shared infrastructure, the injected code could read Envoy's xDS client credentials from the filesystem or cause denial of service for other tenants sharing the Envoy instance. + +The fix removes the use of `text/template` for generating Lua code entirely. User-provided values are now passed as structured data via Envoy's `filterContext` and read by a static Lua script at runtime. + +*Note: This release requires Envoy 1.35.0 or later.* + +## Other Changes + +- Bumps to Envoy [v1.35.10](https://github.com/envoyproxy/envoy/releases/tag/v1.35.10). + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.33.4 is tested against Kubernetes 1.32 through 1.34. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/CHANGELOG-v1.33.5.md b/changelogs/CHANGELOG-v1.33.5.md new file mode 100644 index 00000000000..3a2262b4207 --- /dev/null +++ b/changelogs/CHANGELOG-v1.33.5.md @@ -0,0 +1,29 @@ +We are delighted to present version v1.33.5 of Contour, our layer 7 HTTP reverse proxy for Kubernetes clusters. + +- [All Changes](#all-changes) +- [Installing/Upgrading](#installing-and-upgrading) +- [Compatible Kubernetes Versions](#compatible-kubernetes-versions) + +# All Changes + +## Security fix for [GHSA-g3xr-5w5j-w4q4](https://github.com/projectcontour/contour/security/advisories/GHSA-g3xr-5w5j-w4q4) + +Fixes a bug where configuring fallback certificate with JWT verification in `HTTPProxy` allowed requests without TLS SNI or with unrecognized SNI to bypass JWT verification. Contour now rejects this invalid configuration. + +## Other Changes + +- Bumps Go to 1.25.10. +- Bumps golang.org/x/net to v0.55.0. + +# Installing and Upgrading + +For a fresh install of Contour, consult the [getting started documentation](https://projectcontour.io/getting-started/). + +To upgrade an existing Contour installation, please consult the [upgrade documentation](https://projectcontour.io/resources/upgrading/). + +# Compatible Kubernetes Versions + +Contour v1.33.5 is tested against Kubernetes 1.32 through 1.34. + +# Are you a Contour user? We would love to know! +If you're using Contour and want to add your organization to our adopters list, please visit this [page](https://projectcontour.io/resources/adopters/). If you prefer to keep your organization name anonymous but still give us feedback into your usage and scenarios for Contour, please post on this [GitHub thread](https://github.com/projectcontour/contour/issues/1269). diff --git a/changelogs/unreleased/7279-tsaarni-small.md b/changelogs/unreleased/7279-tsaarni-small.md new file mode 100644 index 00000000000..d140b61b702 --- /dev/null +++ b/changelogs/unreleased/7279-tsaarni-small.md @@ -0,0 +1 @@ +Updates Envoy to v1.35.6. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.35.6/version_history/v1.35/v1.35.6) for more information about the content of the release. diff --git a/changelogs/unreleased/7283-ShivamJha2436-small.md b/changelogs/unreleased/7283-ShivamJha2436-small.md new file mode 100644 index 00000000000..3eea1ee720b --- /dev/null +++ b/changelogs/unreleased/7283-ShivamJha2436-small.md @@ -0,0 +1 @@ +Removed deprecated preserveUnknownFields field from CRDs. diff --git a/changelogs/unreleased/7372-WUMUXIAN-minor.md b/changelogs/unreleased/7372-WUMUXIAN-minor.md new file mode 100644 index 00000000000..c44ac26d8e9 --- /dev/null +++ b/changelogs/unreleased/7372-WUMUXIAN-minor.md @@ -0,0 +1,5 @@ +Make it possible to enable TLS fingerprinting in Envoy's TLS Inspector Listener filter, useful for security monitoring, analytics, and bot detection. Provides independent control over JA3 and JA4 fingerprinting methods. + +Fingerprints can be consumed by: +- Logging in access logs using `%TLS_JA3_FINGERPRINT%` / `%TLS_JA4_FINGERPRINT%` format operators or the `tls_ja3_fingerprint` / `tls_ja4_fingerprint` JSON log fields. +- Setting dynamic request headers to forward fingerprints to backend services (e.g. `%TLS_JA3_FINGERPRINT%` / `%TLS_JA4_FINGERPRINT%` in header policy values). diff --git a/changelogs/unreleased/7373-WUMUXIAN-minor.md b/changelogs/unreleased/7373-WUMUXIAN-minor.md new file mode 100644 index 00000000000..16f8c821232 --- /dev/null +++ b/changelogs/unreleased/7373-WUMUXIAN-minor.md @@ -0,0 +1,3 @@ +## Allow configurable client_sampling and random_sampling for tracing + +Added support for configuring client_sampling and random_sampling in Contour's tracing configuration, allowing more granular control over trace sampling rates as per Envoy's tracing documentation. diff --git a/changelogs/unreleased/7382-tsaarni-small.md b/changelogs/unreleased/7382-tsaarni-small.md new file mode 100644 index 00000000000..ea151c817d2 --- /dev/null +++ b/changelogs/unreleased/7382-tsaarni-small.md @@ -0,0 +1 @@ +Gateway Provisioner: Increase the `shutdown-manager` CPU limit to 200m to reduce throttling during Envoy shutdown. diff --git a/changelogs/unreleased/7408-tsaarni-small.md b/changelogs/unreleased/7408-tsaarni-small.md new file mode 100644 index 00000000000..d2e6ee3b53f --- /dev/null +++ b/changelogs/unreleased/7408-tsaarni-small.md @@ -0,0 +1 @@ +Fix `HTTPProxy` CRD schema incorrectly marking `status.loadBalancer.ingress[].ports[].error` as required, causing load balancer status update failures. diff --git a/changelogs/unreleased/7418-therealak12-minor.md b/changelogs/unreleased/7418-therealak12-minor.md new file mode 100644 index 00000000000..8b33df50489 --- /dev/null +++ b/changelogs/unreleased/7418-therealak12-minor.md @@ -0,0 +1,5 @@ +## Contour now supports HTTP external authorization services + +With this change, Contour supports HTTP external authorization services in addition to gRPC. +This expands compatibility with a broader range of authorization providers and +allows operators to choose the protocol that best fits their environment. diff --git a/changelogs/unreleased/7459-tsaarni-small.md b/changelogs/unreleased/7459-tsaarni-small.md new file mode 100644 index 00000000000..f23d91f25dc --- /dev/null +++ b/changelogs/unreleased/7459-tsaarni-small.md @@ -0,0 +1 @@ +Updates Envoy to v1.37.1 (from the 1.35 track). See the [Envoy 1.36 release notes](https://www.envoyproxy.io/docs/envoy/v1.36.5/version_history/v1.36/v1.36) and [Envoy 1.37 release notes](https://www.envoyproxy.io/docs/envoy/v1.37.1/version_history/v1.37/v1.37.1) for more information about the content of the releases. diff --git a/changelogs/unreleased/7476-tsaarni-small.md b/changelogs/unreleased/7476-tsaarni-small.md new file mode 100644 index 00000000000..437037b0c1e --- /dev/null +++ b/changelogs/unreleased/7476-tsaarni-small.md @@ -0,0 +1 @@ +Remove the Envoy stats `hostPort` from the example manifests. This port is typically needed only for in-cluster access and should not be exposed on the host network. diff --git a/changelogs/unreleased/7502-nissy-dev-minor.md b/changelogs/unreleased/7502-nissy-dev-minor.md new file mode 100644 index 00000000000..0bac5e6cc38 --- /dev/null +++ b/changelogs/unreleased/7502-nissy-dev-minor.md @@ -0,0 +1,5 @@ +## HTTPProxy JWT providers can load JWKS from a Kubernetes Secret + +HTTPProxy JWT providers now support loading JWKS from a Kubernetes Secret via `localJWKS`, in addition to fetching keys from a remote endpoint with `remoteJWKS`. +Each provider must specify exactly one of these sources. +See the [JWT verification documentation](https://projectcontour.io/docs/main/config/jwt-verification) for configuration details. diff --git a/changelogs/unreleased/7522-tsaarni-small.md b/changelogs/unreleased/7522-tsaarni-small.md new file mode 100644 index 00000000000..2d58b38bc78 --- /dev/null +++ b/changelogs/unreleased/7522-tsaarni-small.md @@ -0,0 +1 @@ +Updates Envoy to v1.37.2. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.37.2/version_history/v1.37/v1.37.2) for more information about the content of the release. diff --git a/changelogs/unreleased/7525-SAY-5-small.md b/changelogs/unreleased/7525-SAY-5-small.md new file mode 100644 index 00000000000..3aa3299a5ad --- /dev/null +++ b/changelogs/unreleased/7525-SAY-5-small.md @@ -0,0 +1 @@ +Fix RetryPolicy numRetries=0 being silently coerced to 1 by populating num_retries explicitly when set, so HTTPProxy retry policies that explicitly request zero retries are honored. diff --git a/changelogs/unreleased/7538-alliasgher-docs.md b/changelogs/unreleased/7538-alliasgher-docs.md new file mode 100644 index 00000000000..1d7b23dc20b --- /dev/null +++ b/changelogs/unreleased/7538-alliasgher-docs.md @@ -0,0 +1 @@ +Clarify HTTPProxy match condition precedence and query parameter name case sensitivity in the request routing docs. diff --git a/changelogs/unreleased/7575-tsaarni-small.md b/changelogs/unreleased/7575-tsaarni-small.md new file mode 100644 index 00000000000..940658d6663 --- /dev/null +++ b/changelogs/unreleased/7575-tsaarni-small.md @@ -0,0 +1 @@ +Updates kind node image for e2e tests to Kubernetes 1.36. Supported/tested Kubernetes versions are now 1.36, 1.35 and 1.34. diff --git a/changelogs/unreleased/7576-tsaarni-small.md b/changelogs/unreleased/7576-tsaarni-small.md new file mode 100644 index 00000000000..c9225b9b969 --- /dev/null +++ b/changelogs/unreleased/7576-tsaarni-small.md @@ -0,0 +1 @@ +Updates Go to go1.26.4. See the [Go release notes](https://go.dev/doc/devel/release#go1.26.0) for more information about the content of the release. diff --git a/changelogs/unreleased/7583-tsaarni-small.md b/changelogs/unreleased/7583-tsaarni-small.md new file mode 100644 index 00000000000..f3f201b45c7 --- /dev/null +++ b/changelogs/unreleased/7583-tsaarni-small.md @@ -0,0 +1 @@ +Updates Envoy to v1.38.1. See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.38.1/version_history/v1.38/v1.38.1) for more information about the content of the release. diff --git a/cmd/contour/gatewayprovisioner.go b/cmd/contour/gatewayprovisioner.go index 34eccbc505c..7dfd729f816 100644 --- a/cmd/contour/gatewayprovisioner.go +++ b/cmd/contour/gatewayprovisioner.go @@ -36,7 +36,7 @@ func registerGatewayProvisioner(app *kingpin.Application) (*kingpin.CmdClause, * provisionerConfig := &gatewayProvisionerConfig{ contourImage: "ghcr.io/projectcontour/contour:main", - envoyImage: "docker.io/envoyproxy/envoy:distroless-v1.35.2", + envoyImage: "docker.io/envoyproxy/envoy:distroless-v1.38.2", metricsBindAddress: ":8080", leaderElection: false, leaderElectionID: "0d879e31.projectcontour.io", diff --git a/cmd/contour/ingressstatus.go b/cmd/contour/ingressstatus.go index 3571b5087bc..0fd002a934b 100644 --- a/cmd/contour/ingressstatus.go +++ b/cmd/contour/ingressstatus.go @@ -189,7 +189,7 @@ func parseStatusFlag(status string) core_v1.LoadBalancerStatus { // Support ','-separated lists. var ingresses []core_v1.LoadBalancerIngress - for _, item := range strings.Split(status, ",") { + for item := range strings.SplitSeq(status, ",") { item = strings.TrimSpace(item) if len(item) == 0 { continue diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index b06828df5cc..a2ce0432789 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -16,6 +16,7 @@ package main import ( "context" "fmt" + "maps" "net" "net/http" "os" @@ -454,6 +455,8 @@ func (s *Server) doServe() error { MinimumTLSVersion: annotation.TLSVersion(contourConfiguration.Envoy.Listener.TLS.MinimumProtocolVersion, "1.2"), MaximumTLSVersion: annotation.TLSVersion(contourConfiguration.Envoy.Listener.TLS.MaximumProtocolVersion, "1.3"), CipherSuites: contourConfiguration.Envoy.Listener.TLS.SanitizedCipherSuites(), + EnableJA3Fingerprinting: contourConfiguration.Envoy.Listener.TLS.GetJA3(), + EnableJA4Fingerprinting: contourConfiguration.Envoy.Listener.TLS.GetJA4(), Timeouts: timeouts, DefaultHTTPVersions: parseDefaultHTTPVersions(contourConfiguration.Envoy.DefaultHTTPVersions), AllowChunkedLength: !*contourConfiguration.Envoy.Listener.DisableAllowChunkedLength, @@ -755,6 +758,19 @@ func (s *Server) getExtensionSvcConfig(name, namespace string) (xdscache_v3.Exte return extensionSvcConfig, nil } +// parseSamplingRate parses a sampling rate string and returns the float value, +// defaulting to 100.0 for invalid values or zero values. +func parseSamplingRate(rateStr *string) float64 { + if rateStr == nil { + return 100.0 + } + rate, err := strconv.ParseFloat(*rateStr, 64) + if err != nil || rate == 0 { + return 100.0 + } + return rate +} + func (s *Server) setupTracingService(tracingConfig *contour_v1alpha1.TracingConfig) (*xdscache_v3.TracingConfig, error) { if tracingConfig == nil { return nil, nil @@ -786,15 +802,16 @@ func (s *Server) setupTracingService(tracingConfig *contour_v1alpha1.TracingConf }) } - overallSampling, err := strconv.ParseFloat(ptr.Deref(tracingConfig.OverallSampling, "100"), 64) - if err != nil || overallSampling == 0 { - overallSampling = 100.0 - } + overallSampling := parseSamplingRate(tracingConfig.OverallSampling) + clientSampling := parseSamplingRate(tracingConfig.ClientSampling) + randomSampling := parseSamplingRate(tracingConfig.RandomSampling) return &xdscache_v3.TracingConfig{ ServiceName: ptr.Deref(tracingConfig.ServiceName, "contour"), ExtensionServiceConfig: extensionSvcConfig, OverallSampling: overallSampling, + ClientSampling: clientSampling, + RandomSampling: randomSampling, MaxPathTagLength: ptr.Deref(tracingConfig.MaxPathTagLength, 256), CustomTags: customTags, }, nil @@ -837,20 +854,23 @@ func (s *Server) setupGlobalExternalAuthentication(contourConfiguration contour_ context = contourConfiguration.GlobalExternalAuthorization.AuthPolicy.Context } - globalExternalAuthConfig := &xdscache_v3.GlobalExternalAuthConfig{ - ExtensionServiceConfig: extensionSvcConfig, - FailOpen: contourConfiguration.GlobalExternalAuthorization.FailOpen, - Context: context, + var validCond contour_v1.DetailedCondition + extAuth := dag.NewExternalAuthorization(contourConfiguration.GlobalExternalAuthorization, &validCond) + if len(validCond.Errors) > 0 { + return nil, fmt.Errorf("%s", validCond.Errors[0].Message) } - if contourConfiguration.GlobalExternalAuthorization.WithRequestBody != nil { - globalExternalAuthConfig.WithRequestBody = &dag.AuthorizationServerBufferSettings{ - PackAsBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.PackAsBytes, - AllowPartialMessage: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.AllowPartialMessage, - MaxRequestBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.MaxRequestBytes, - } + // If ContourConfiguration.spec.globalExtAuth.responseTimeout is not set, + // fall back to ExtensionService.spec.timeoutPolicy.response. + if extAuth.AuthorizationResponseTimeout.UseDefault() { + extAuth.AuthorizationResponseTimeout = extensionSvcConfig.Timeout } - return globalExternalAuthConfig, nil + + return &xdscache_v3.GlobalExternalAuthConfig{ + ExtensionServiceConfig: extensionSvcConfig, + ExternalAuthorization: *extAuth, + Context: context, + }, nil } func (s *Server) setupGlobalExtProc(contourCfg contour_v1alpha1.ContourConfigurationSpec) (*xdscache_v3.GlobalExtProcConfig, error) { @@ -1061,9 +1081,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { if dbc.headersPolicy.RequestHeadersPolicy != nil { if dbc.headersPolicy.RequestHeadersPolicy.Set != nil { requestHeadersPolicy.Set = make(map[string]string) - for k, v := range dbc.headersPolicy.RequestHeadersPolicy.Set { - requestHeadersPolicy.Set[k] = v - } + maps.Copy(requestHeadersPolicy.Set, dbc.headersPolicy.RequestHeadersPolicy.Set) } if dbc.headersPolicy.RequestHeadersPolicy.Remove != nil { requestHeadersPolicy.Remove = make([]string, 0, len(dbc.headersPolicy.RequestHeadersPolicy.Remove)) @@ -1074,9 +1092,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { if dbc.headersPolicy.ResponseHeadersPolicy != nil { if dbc.headersPolicy.ResponseHeadersPolicy.Set != nil { responseHeadersPolicy.Set = make(map[string]string) - for k, v := range dbc.headersPolicy.ResponseHeadersPolicy.Set { - responseHeadersPolicy.Set[k] = v - } + maps.Copy(responseHeadersPolicy.Set, dbc.headersPolicy.ResponseHeadersPolicy.Set) } if dbc.headersPolicy.ResponseHeadersPolicy.Remove != nil { responseHeadersPolicy.Remove = make([]string, 0, len(dbc.headersPolicy.ResponseHeadersPolicy.Remove)) diff --git a/cmd/contour/serve_test.go b/cmd/contour/serve_test.go index 1cf5439a337..4a60f15dd1d 100644 --- a/cmd/contour/serve_test.go +++ b/cmd/contour/serve_test.go @@ -218,6 +218,57 @@ func TestGetDAGBuilder(t *testing.T) { // TODO(3453): test additional properties of the DAG builder (processor fields, cache fields, Gateway tests (requires a client fake)) } +func TestParseSamplingRate(t *testing.T) { + tests := map[string]struct { + input *string + want float64 + }{ + "nil input": { + input: nil, + want: 100.0, + }, + "empty string": { + input: ptr.To(""), + want: 100.0, + }, + "valid number": { + input: ptr.To("50.5"), + want: 50.5, + }, + "zero value": { + input: ptr.To("0"), + want: 100.0, + }, + "negative number": { + input: ptr.To("-10"), + want: -10.0, + }, + "invalid string": { + input: ptr.To("invalid"), + want: 100.0, + }, + "non-numeric string": { + input: ptr.To("not-a-number"), + want: 100.0, + }, + "decimal zero": { + input: ptr.To("0.0"), + want: 100.0, + }, + "large number": { + input: ptr.To("999.99"), + want: 999.99, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := parseSamplingRate(tc.input) + assert.InDelta(t, tc.want, got, 0.001) + }) + } +} + func mustGetGatewayAPIProcessor(t *testing.T, builder *dag.Builder) *dag.GatewayAPIProcessor { t.Helper() for i := range builder.Processors { diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 870bf7eeab6..4c8b2e73e40 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -249,7 +249,7 @@ func (ctx *serveContext) proxyRootNamespaces() []string { return nil } var ns []string - for _, s := range strings.Split(ctx.rootNamespaces, ",") { + for s := range strings.SplitSeq(ctx.rootNamespaces, ",") { ns = append(ns, strings.TrimSpace(s)) } return ns @@ -260,7 +260,7 @@ func (ctx *serveContext) watchedNamespaces() []string { return nil } var ns []string - for _, s := range strings.Split(ctx.watchNamespaces, ",") { + for s := range strings.SplitSeq(ctx.watchNamespaces, ",") { ns = append(ns, strings.TrimSpace(s)) } return ns @@ -413,6 +413,8 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co IncludePodDetail: ctx.Config.Tracing.IncludePodDetail, ServiceName: ctx.Config.Tracing.ServiceName, OverallSampling: ctx.Config.Tracing.OverallSampling, + ClientSampling: ctx.Config.Tracing.ClientSampling, + RandomSampling: ctx.Config.Tracing.RandomSampling, MaxPathTagLength: ctx.Config.Tracing.MaxPathTagLength, CustomTags: customTags, ExtensionService: &contour_v1alpha1.NamespacedName{ @@ -457,10 +459,15 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co Name: nsedName.Name, Namespace: nsedName.Namespace, }, + ServiceType: ctx.Config.GlobalExternalAuthorization.ServiceType, ResponseTimeout: ctx.Config.GlobalExternalAuthorization.ResponseTimeout, FailOpen: ctx.Config.GlobalExternalAuthorization.FailOpen, } + if ctx.Config.GlobalExternalAuthorization.HTTPServerSettings != nil { + globalExtAuth.HTTPServerSettings = ctx.Config.GlobalExternalAuthorization.HTTPServerSettings + } + if ctx.Config.GlobalExternalAuthorization.AuthPolicy != nil { globalExtAuth.AuthPolicy = &contour_v1.AuthorizationPolicy{ Disabled: ctx.Config.GlobalExternalAuthorization.AuthPolicy.Disabled, @@ -528,6 +535,14 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co } } + var fingerprint *contour_v1alpha1.TLSFingerprint + if ctx.Config.TLS.Fingerprint != nil { + fingerprint = &contour_v1alpha1.TLSFingerprint{ + JA3: ctx.Config.TLS.Fingerprint.JA3, + JA4: ctx.Config.TLS.Fingerprint.JA4, + } + } + contourMetrics := contour_v1alpha1.MetricsConfig{ Address: ctx.metricsAddr, Port: ctx.metricsPort, @@ -580,10 +595,13 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co MaxRequestsPerIOCycle: ctx.Config.Listener.MaxRequestsPerIOCycle, HTTP2MaxConcurrentStreams: ctx.Config.Listener.HTTP2MaxConcurrentStreams, MaxConnectionsPerListener: ctx.Config.Listener.MaxConnectionsPerListener, - TLS: &contour_v1alpha1.EnvoyTLS{ - MinimumProtocolVersion: ctx.Config.TLS.MinimumProtocolVersion, - MaximumProtocolVersion: ctx.Config.TLS.MaximumProtocolVersion, - CipherSuites: cipherSuites, + TLS: &contour_v1alpha1.EnvoyListenerTLS{ + EnvoyTLS: contour_v1alpha1.EnvoyTLS{ + MinimumProtocolVersion: ctx.Config.TLS.MinimumProtocolVersion, + MaximumProtocolVersion: ctx.Config.TLS.MaximumProtocolVersion, + CipherSuites: cipherSuites, + }, + Fingerprint: fingerprint, }, SocketOptions: &contour_v1alpha1.SocketOptions{ TOS: ctx.Config.Listener.SocketOptions.TOS, diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index b3dfee1f816..d9565d8b735 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -21,7 +21,7 @@ import ( "os" "path/filepath" "reflect" - "sort" + "slices" "testing" "time" @@ -349,15 +349,12 @@ func TestParseHTTPVersions(t *testing.T) { } for name, testcase := range cases { - testcase := testcase t.Run(name, func(t *testing.T) { vers := parseDefaultHTTPVersions(testcase.versions) // parseDefaultHTTPVersions doesn't guarantee a stable result, but the order doesn't matter. - sort.Slice(vers, - func(i, j int) bool { return vers[i] < vers[j] }) - sort.Slice(testcase.parseVersions, - func(i, j int) bool { return testcase.parseVersions[i] < testcase.parseVersions[j] }) + slices.Sort(vers) + slices.Sort(testcase.parseVersions) assert.Equal(t, testcase.parseVersions, vers) }) @@ -410,9 +407,11 @@ func defaultContourConfiguration() contour_v1alpha1.ContourConfigurationSpec { DisableAllowChunkedLength: ptr.To(false), DisableMergeSlashes: ptr.To(false), ServerHeaderTransformation: contour_v1alpha1.OverwriteServerHeader, - TLS: &contour_v1alpha1.EnvoyTLS{ - MinimumProtocolVersion: "", - MaximumProtocolVersion: "", + TLS: &contour_v1alpha1.EnvoyListenerTLS{ + EnvoyTLS: contour_v1alpha1.EnvoyTLS{ + MinimumProtocolVersion: "", + MaximumProtocolVersion: "", + }, }, SocketOptions: &contour_v1alpha1.SocketOptions{ TOS: 0, @@ -833,6 +832,8 @@ func TestConvertServeContext(t *testing.T) { IncludePodDetail: ptr.To(false), ServiceName: ptr.To("contour"), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: []config.CustomTag{ { @@ -853,6 +854,8 @@ func TestConvertServeContext(t *testing.T) { IncludePodDetail: ptr.To(false), ServiceName: ptr.To("contour"), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: []*contour_v1alpha1.CustomTag{ { @@ -919,6 +922,22 @@ func TestConvertServeContext(t *testing.T) { return cfg }, }, + "tls fingerprinting": { + getServeContext: func(ctx *serveContext) *serveContext { + ctx.Config.TLS.Fingerprint = &config.TLSFingerprint{ + JA3: ptr.To(true), + JA4: ptr.To(true), + } + return ctx + }, + getContourConfiguration: func(cfg contour_v1alpha1.ContourConfigurationSpec) contour_v1alpha1.ContourConfigurationSpec { + cfg.Envoy.Listener.TLS.Fingerprint = &contour_v1alpha1.TLSFingerprint{ + JA3: ptr.To(true), + JA4: ptr.To(true), + } + return cfg + }, + }, } for name, tc := range cases { diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index eda5b9e5146..0a00d6cfdac 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -3,10 +3,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourconfigurations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourConfiguration @@ -442,6 +441,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -731,6 +746,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -739,6 +839,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -760,6 +869,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -1344,6 +1456,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -1401,6 +1518,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -1652,10 +1774,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourdeployments.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourDeployment @@ -1832,9 +1953,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -2740,7 +2862,7 @@ spec: resources: description: |- resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + Users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources @@ -3070,7 +3192,7 @@ spec: A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - The volume will be mounted read-only (ro) and non-executable files (noexec). + The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: @@ -3242,8 +3364,7 @@ spec: description: |- portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - is on. + are redirected to the pxd.portworx.com CSI driver. properties: fsType: description: |- @@ -3601,6 +3722,21 @@ spec: description: Kubelet's generated CSRs will be addressed to this signer. type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object required: - keyType - signerName @@ -4120,9 +4256,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4661,6 +4798,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -4951,6 +5104,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4959,6 +5197,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4980,6 +5227,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -5565,6 +5815,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -5623,6 +5878,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -5742,10 +6002,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: extensionservices.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ExtensionService @@ -5822,9 +6081,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5898,8 +6157,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5915,7 +6175,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6222,10 +6482,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: httpproxies.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: HTTPProxy @@ -8496,6 +8755,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -8504,6 +8848,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -8525,6 +8878,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. @@ -8864,12 +9220,31 @@ spec: Issuer that JWTs are required to have in the "iss" field. If not provided, JWT issuers are not checked. type: string + localJWKS: + description: Local JWKS loads signing keys from a Kubernetes + Secret. + properties: + key: + description: The key of the secret that contains the + JWKS. + minLength: 1 + type: string + secretName: + description: The name of the secret that contains the + JWKS. + minLength: 1 + type: string + required: + - key + - secretName + type: object name: description: Unique name for the provider. minLength: 1 type: string remoteJWKS: - description: Remote JWKS to use for verifying JWT signatures. + description: Remote JWKS fetches signing keys from an HTTP(S) + endpoint. properties: cacheDuration: description: |- @@ -8950,8 +9325,11 @@ spec: type: object required: - name - - remoteJWKS type: object + x-kubernetes-validations: + - message: exactly one of remoteJWKS or localJWKS must be set + rule: (has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) + && has(self.localJWKS)) type: array rateLimitPolicy: description: The policy for rate limiting on the virtual host. @@ -9594,7 +9972,6 @@ spec: The supported values are: "TCP", "UDP", "SCTP" type: string required: - - error - port - protocol type: object @@ -9618,10 +9995,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: tlscertificatedelegations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: TLSCertificateDelegation diff --git a/examples/contour/03-envoy.yaml b/examples/contour/03-envoy.yaml index d1432d0e986..01c8c6caf5b 100644 --- a/examples/contour/03-envoy.yaml +++ b/examples/contour/03-envoy.yaml @@ -46,7 +46,7 @@ spec: - --log-level info command: - envoy - image: docker.io/envoyproxy/envoy:distroless-v1.35.2 + image: docker.io/envoyproxy/envoy:distroless-v1.38.2 imagePullPolicy: IfNotPresent name: envoy env: @@ -70,7 +70,6 @@ spec: name: https protocol: TCP - containerPort: 8002 - hostPort: 8002 name: metrics protocol: TCP readinessProbe: diff --git a/examples/deployment/03-envoy-deployment.yaml b/examples/deployment/03-envoy-deployment.yaml index da6f6838301..ad7ff84d478 100644 --- a/examples/deployment/03-envoy-deployment.yaml +++ b/examples/deployment/03-envoy-deployment.yaml @@ -58,7 +58,7 @@ spec: - --log-level info command: - envoy - image: docker.io/envoyproxy/envoy:distroless-v1.35.2 + image: docker.io/envoyproxy/envoy:distroless-v1.38.2 imagePullPolicy: IfNotPresent name: envoy env: @@ -82,7 +82,6 @@ spec: name: https protocol: TCP - containerPort: 8002 - hostPort: 8002 name: metrics protocol: TCP readinessProbe: diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 3c8cb8522d0..b54865da519 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -222,10 +222,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourconfigurations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourConfiguration @@ -661,6 +660,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -950,6 +965,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -958,6 +1058,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -979,6 +1088,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -1563,6 +1675,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -1620,6 +1737,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -1871,10 +1993,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourdeployments.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourDeployment @@ -2051,9 +2172,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -2959,7 +3081,7 @@ spec: resources: description: |- resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + Users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources @@ -3289,7 +3411,7 @@ spec: A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - The volume will be mounted read-only (ro) and non-executable files (noexec). + The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: @@ -3461,8 +3583,7 @@ spec: description: |- portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - is on. + are redirected to the pxd.portworx.com CSI driver. properties: fsType: description: |- @@ -3820,6 +3941,21 @@ spec: description: Kubelet's generated CSRs will be addressed to this signer. type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object required: - keyType - signerName @@ -4339,9 +4475,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4880,6 +5017,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -5170,6 +5323,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -5178,6 +5416,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5199,6 +5446,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -5784,6 +6034,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -5842,6 +6097,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -5961,10 +6221,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: extensionservices.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ExtensionService @@ -6041,9 +6300,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -6117,8 +6376,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -6134,7 +6394,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6441,10 +6701,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: httpproxies.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: HTTPProxy @@ -8715,6 +8974,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -8723,6 +9067,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -8744,6 +9097,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. @@ -9083,12 +9439,31 @@ spec: Issuer that JWTs are required to have in the "iss" field. If not provided, JWT issuers are not checked. type: string + localJWKS: + description: Local JWKS loads signing keys from a Kubernetes + Secret. + properties: + key: + description: The key of the secret that contains the + JWKS. + minLength: 1 + type: string + secretName: + description: The name of the secret that contains the + JWKS. + minLength: 1 + type: string + required: + - key + - secretName + type: object name: description: Unique name for the provider. minLength: 1 type: string remoteJWKS: - description: Remote JWKS to use for verifying JWT signatures. + description: Remote JWKS fetches signing keys from an HTTP(S) + endpoint. properties: cacheDuration: description: |- @@ -9169,8 +9544,11 @@ spec: type: object required: - name - - remoteJWKS type: object + x-kubernetes-validations: + - message: exactly one of remoteJWKS or localJWKS must be set + rule: (has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) + && has(self.localJWKS)) type: array rateLimitPolicy: description: The policy for rate limiting on the virtual host. @@ -9813,7 +10191,6 @@ spec: The supported values are: "TCP", "UDP", "SCTP" type: string required: - - error - port - protocol type: object @@ -9837,10 +10214,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: tlscertificatedelegations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: TLSCertificateDelegation @@ -10541,7 +10917,7 @@ spec: - --log-level info command: - envoy - image: docker.io/envoyproxy/envoy:distroless-v1.35.2 + image: docker.io/envoyproxy/envoy:distroless-v1.38.2 imagePullPolicy: IfNotPresent name: envoy env: @@ -10565,7 +10941,6 @@ spec: name: https protocol: TCP - containerPort: 8002 - hostPort: 8002 name: metrics protocol: TCP readinessProbe: diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 27243aa61dc..5bc996cba81 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -14,10 +14,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourconfigurations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourConfiguration @@ -453,6 +452,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -742,6 +757,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -750,6 +850,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -771,6 +880,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -1355,6 +1467,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -1412,6 +1529,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -1663,10 +1785,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourdeployments.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourDeployment @@ -1843,9 +1964,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -2751,7 +2873,7 @@ spec: resources: description: |- resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + Users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources @@ -3081,7 +3203,7 @@ spec: A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - The volume will be mounted read-only (ro) and non-executable files (noexec). + The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: @@ -3253,8 +3375,7 @@ spec: description: |- portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - is on. + are redirected to the pxd.portworx.com CSI driver. properties: fsType: description: |- @@ -3612,6 +3733,21 @@ spec: description: Kubelet's generated CSRs will be addressed to this signer. type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object required: - keyType - signerName @@ -4131,9 +4267,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4672,6 +4809,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -4962,6 +5115,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4970,6 +5208,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4991,6 +5238,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -5576,6 +5826,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -5634,6 +5889,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -5753,10 +6013,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: extensionservices.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ExtensionService @@ -5833,9 +6092,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5909,8 +6168,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5926,7 +6186,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6233,10 +6493,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: httpproxies.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: HTTPProxy @@ -8507,6 +8766,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -8515,6 +8859,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -8536,6 +8889,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. @@ -8875,12 +9231,31 @@ spec: Issuer that JWTs are required to have in the "iss" field. If not provided, JWT issuers are not checked. type: string + localJWKS: + description: Local JWKS loads signing keys from a Kubernetes + Secret. + properties: + key: + description: The key of the secret that contains the + JWKS. + minLength: 1 + type: string + secretName: + description: The name of the secret that contains the + JWKS. + minLength: 1 + type: string + required: + - key + - secretName + type: object name: description: Unique name for the provider. minLength: 1 type: string remoteJWKS: - description: Remote JWKS to use for verifying JWT signatures. + description: Remote JWKS fetches signing keys from an HTTP(S) + endpoint. properties: cacheDuration: description: |- @@ -8961,8 +9336,11 @@ spec: type: object required: - name - - remoteJWKS type: object + x-kubernetes-validations: + - message: exactly one of remoteJWKS or localJWKS must be set + rule: (has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) + && has(self.localJWKS)) type: array rateLimitPolicy: description: The policy for rate limiting on the virtual host. @@ -9605,7 +9983,6 @@ spec: The supported values are: "TCP", "UDP", "SCTP" type: string required: - - error - port - protocol type: object @@ -9629,10 +10006,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: tlscertificatedelegations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: TLSCertificateDelegation diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 9df8552dd31..e14dcae18d9 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -39,10 +39,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourconfigurations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourConfiguration @@ -478,6 +477,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -767,6 +782,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -775,6 +875,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -796,6 +905,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -1380,6 +1492,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -1437,6 +1554,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -1688,10 +1810,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourdeployments.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourDeployment @@ -1868,9 +1989,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -2776,7 +2898,7 @@ spec: resources: description: |- resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + Users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources @@ -3106,7 +3228,7 @@ spec: A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - The volume will be mounted read-only (ro) and non-executable files (noexec). + The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: @@ -3278,8 +3400,7 @@ spec: description: |- portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - is on. + are redirected to the pxd.portworx.com CSI driver. properties: fsType: description: |- @@ -3637,6 +3758,21 @@ spec: description: Kubelet's generated CSRs will be addressed to this signer. type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object required: - keyType - signerName @@ -4156,9 +4292,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4697,6 +4834,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -4987,6 +5140,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4995,6 +5233,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5016,6 +5263,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -5601,6 +5851,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -5659,6 +5914,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -5778,10 +6038,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: extensionservices.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ExtensionService @@ -5858,9 +6117,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5934,8 +6193,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5951,7 +6211,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6258,10 +6518,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: httpproxies.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: HTTPProxy @@ -8532,6 +8791,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -8540,6 +8884,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -8561,6 +8914,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. @@ -8900,12 +9256,31 @@ spec: Issuer that JWTs are required to have in the "iss" field. If not provided, JWT issuers are not checked. type: string + localJWKS: + description: Local JWKS loads signing keys from a Kubernetes + Secret. + properties: + key: + description: The key of the secret that contains the + JWKS. + minLength: 1 + type: string + secretName: + description: The name of the secret that contains the + JWKS. + minLength: 1 + type: string + required: + - key + - secretName + type: object name: description: Unique name for the provider. minLength: 1 type: string remoteJWKS: - description: Remote JWKS to use for verifying JWT signatures. + description: Remote JWKS fetches signing keys from an HTTP(S) + endpoint. properties: cacheDuration: description: |- @@ -8986,8 +9361,11 @@ spec: type: object required: - name - - remoteJWKS type: object + x-kubernetes-validations: + - message: exactly one of remoteJWKS or localJWKS must be set + rule: (has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) + && has(self.localJWKS)) type: array rateLimitPolicy: description: The policy for rate limiting on the virtual host. @@ -9630,7 +10008,6 @@ spec: The supported values are: "TCP", "UDP", "SCTP" type: string required: - - error - port - protocol type: object @@ -9654,10 +10031,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: tlscertificatedelegations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: TLSCertificateDelegation @@ -10346,7 +10722,7 @@ spec: - --log-level info command: - envoy - image: docker.io/envoyproxy/envoy:distroless-v1.35.2 + image: docker.io/envoyproxy/envoy:distroless-v1.38.2 imagePullPolicy: IfNotPresent name: envoy env: @@ -10370,7 +10746,6 @@ spec: name: https protocol: TCP - containerPort: 8002 - hostPort: 8002 name: metrics protocol: TCP readinessProbe: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 7f4734d0a65..2bd85db28ff 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -222,10 +222,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourconfigurations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourConfiguration @@ -661,6 +660,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -950,6 +965,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -958,6 +1058,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -979,6 +1088,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -1563,6 +1675,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -1620,6 +1737,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -1871,10 +1993,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: contourdeployments.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ContourDeployment @@ -2051,9 +2172,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -2959,7 +3081,7 @@ spec: resources: description: |- resources represents the minimum resources the volume should have. - If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + Users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources @@ -3289,7 +3411,7 @@ spec: A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. - The volume will be mounted read-only (ro) and non-executable files (noexec). + The volume will be mounted read-only (ro). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. properties: @@ -3461,8 +3583,7 @@ spec: description: |- portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type - are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate - is on. + are redirected to the pxd.portworx.com CSI driver. properties: fsType: description: |- @@ -3820,6 +3941,21 @@ spec: description: Kubelet's generated CSRs will be addressed to this signer. type: string + userAnnotations: + additionalProperties: + type: string + description: |- + userAnnotations allow pod authors to pass additional information to + the signer implementation. Kubernetes does not restrict or validate this + metadata in any way. + These values are copied verbatim into the `spec.unverifiedUserAnnotations` field of + the PodCertificateRequest objects that Kubelet creates. + Entries are subject to the same validation as object metadata annotations, + with the addition that all keys must be domain-prefixed. No restrictions + are placed on values, except an overall size limitation on the entire field. + Signers should document the keys and values they support. Signers should + deny requests that contain keys they do not recognize. + type: object required: - keyType - signerName @@ -4339,9 +4475,10 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string tolerationSeconds: description: |- @@ -4880,6 +5017,22 @@ spec: items: type: string type: array + fingerprint: + description: |- + Fingerprint defines TLS fingerprinting configuration + for the TLS Inspector listener filter. + properties: + ja3: + description: |- + JA3 enables JA3 fingerprinting in the TLS Inspector. + When true, populates JA3 hash in dynamic metadata. + type: boolean + ja4: + description: |- + JA4 enables JA4 fingerprinting in the TLS Inspector. + When true, populates JA4 hash in dynamic metadata. + type: boolean + type: object maximumProtocolVersion: description: |- MaximumProtocolVersion is the maximum TLS version this vhost should @@ -5170,6 +5323,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -5178,6 +5416,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5199,6 +5446,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' globalExternalProcessing: description: |- GlobalExternalProcessing allows envoys external processing filter @@ -5784,6 +6034,11 @@ spec: description: Tracing defines properties for exporting trace data to OpenTelemetry. properties: + clientSampling: + description: |- + ClientSampling defines the sampling rate when x-client-trace-id header is set. + contour's default is 100. + type: string customTags: description: CustomTags defines a list of custom tags with unique tag name. @@ -5842,6 +6097,11 @@ spec: OverallSampling defines the sampling rate of trace data. contour's default is 100. type: string + randomSampling: + description: |- + RandomSampling defines the random sampling rate for all requests. + contour's default is 100. + type: string serviceName: description: |- ServiceName defines the name for the service. @@ -5961,10 +6221,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: extensionservices.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: ExtensionService @@ -6041,9 +6300,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -6117,8 +6376,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -6134,7 +6394,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6441,10 +6701,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: httpproxies.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: HTTPProxy @@ -8715,6 +8974,91 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -8723,6 +9067,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceType: + default: grpc + description: |- + ServiceType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -8744,6 +9097,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. @@ -9083,12 +9439,31 @@ spec: Issuer that JWTs are required to have in the "iss" field. If not provided, JWT issuers are not checked. type: string + localJWKS: + description: Local JWKS loads signing keys from a Kubernetes + Secret. + properties: + key: + description: The key of the secret that contains the + JWKS. + minLength: 1 + type: string + secretName: + description: The name of the secret that contains the + JWKS. + minLength: 1 + type: string + required: + - key + - secretName + type: object name: description: Unique name for the provider. minLength: 1 type: string remoteJWKS: - description: Remote JWKS to use for verifying JWT signatures. + description: Remote JWKS fetches signing keys from an HTTP(S) + endpoint. properties: cacheDuration: description: |- @@ -9169,8 +9544,11 @@ spec: type: object required: - name - - remoteJWKS type: object + x-kubernetes-validations: + - message: exactly one of remoteJWKS or localJWKS must be set + rule: (has(self.remoteJWKS) && !has(self.localJWKS)) || (!has(self.remoteJWKS) + && has(self.localJWKS)) type: array rateLimitPolicy: description: The policy for rate limiting on the virtual host. @@ -9813,7 +10191,6 @@ spec: The supported values are: "TCP", "UDP", "SCTP" type: string required: - - error - port - protocol type: object @@ -9837,10 +10214,9 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.19.0 + controller-gen.kubebuilder.io/version: v0.21.0 name: tlscertificatedelegations.projectcontour.io spec: - preserveUnknownFields: false group: projectcontour.io names: kind: TLSCertificateDelegation @@ -10529,7 +10905,7 @@ spec: - --log-level info command: - envoy - image: docker.io/envoyproxy/envoy:distroless-v1.35.2 + image: docker.io/envoyproxy/envoy:distroless-v1.38.2 imagePullPolicy: IfNotPresent name: envoy env: @@ -10553,7 +10929,6 @@ spec: name: https protocol: TCP - containerPort: 8002 - hostPort: 8002 name: metrics protocol: TCP readinessProbe: diff --git a/go.mod b/go.mod index f5d8d308798..985d4d57101 100644 --- a/go.mod +++ b/go.mod @@ -1,148 +1,124 @@ module github.com/projectcontour/contour -go 1.24.0 +go 1.26.0 require ( dario.cat/mergo v1.0.2 - github.com/Masterminds/semver/v3 v3.4.0 - github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20241111191808-71fefeed8910 + github.com/Masterminds/semver/v3 v3.5.0 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/bombsimon/logrusr/v4 v4.1.0 - github.com/cert-manager/cert-manager v1.18.2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/distribution/reference v0.6.0 - github.com/envoyproxy/go-control-plane v0.13.4 - github.com/envoyproxy/go-control-plane/envoy v1.32.5-0.20250722125442-5321204dac14 + github.com/envoyproxy/go-control-plane v0.14.0 + github.com/envoyproxy/go-control-plane/envoy v1.37.0 github.com/go-logr/logr v1.4.3 github.com/google/go-cmp v0.7.0 github.com/google/go-github/v48 v48.2.0 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/onsi/ginkgo/v2 v2.25.3 - github.com/onsi/gomega v1.38.2 + github.com/onsi/ginkgo/v2 v2.30.0 + github.com/onsi/gomega v1.41.0 github.com/pkg/errors v0.9.1 github.com/projectcontour/yages v0.1.0 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.66.1 - github.com/sirupsen/logrus v1.9.3 + github.com/prometheus/common v0.68.1 + github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 - github.com/tsaarni/certyaml v0.10.0 - github.com/vektra/mockery/v2 v2.53.5 + github.com/tsaarni/certyaml v0.11.0 go.uber.org/automaxprocs v1.6.0 - golang.org/x/net v0.44.0 - golang.org/x/oauth2 v0.31.0 - gonum.org/v1/plot v0.16.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 - google.golang.org/grpc v1.75.1 - google.golang.org/protobuf v1.36.9 + golang.org/x/net v0.56.0 + golang.org/x/oauth2 v0.36.0 + gonum.org/v1/plot v0.17.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 + google.golang.org/grpc v1.81.1 + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.34.1 - k8s.io/apiextensions-apiserver v0.34.1 - k8s.io/apimachinery v0.34.1 - k8s.io/client-go v0.34.1 - k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.22.1 - sigs.k8s.io/controller-tools v0.19.0 + k8s.io/api v0.36.1 + k8s.io/apiextensions-apiserver v0.36.1 + k8s.io/apimachinery v0.36.1 + k8s.io/client-go v0.36.1 + k8s.io/klog/v2 v2.140.0 + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 + sigs.k8s.io/controller-runtime v0.24.0 sigs.k8s.io/gateway-api v1.3.0 - sigs.k8s.io/kustomize/kyaml v0.20.1 + sigs.k8s.io/kustomize/kyaml v0.21.1 ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect codeberg.org/go-fonts/liberation v0.5.0 // indirect - codeberg.org/go-latex/latex v0.1.0 // indirect - codeberg.org/go-pdf/fpdf v0.10.0 // indirect - git.sr.ht/~sbinet/gg v0.6.0 // indirect + codeberg.org/go-latex/latex v0.2.0 // indirect + codeberg.org/go-pdf/fpdf v0.11.1 // indirect + git.sr.ht/~sbinet/gg v0.7.0 // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/campoy/embedmd v1.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chigopher/pathlib v0.19.1 // indirect - github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.6 // indirect + github.com/fsnotify/fsnotify v1.10.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/gobuffalo/flect v1.0.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/huandu/xstrings v1.4.0 // indirect - github.com/iancoleman/strcase v0.3.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jinzhu/copier v0.4.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.65 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/spdystream v0.5.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/rs/zerolog v1.33.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/spf13/viper v1.20.0 // indirect - github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/tsaarni/x500dn v1.0.0 // indirect + github.com/prometheus/procfs v0.20.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/tsaarni/x500dn v1.1.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/image v0.25.0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/image v0.38.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.45.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/code-generator v0.34.1 // indirect - k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect - k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect + k8s.io/streaming v0.36.1 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 1ef7e70b1d1..2eb2e6614a7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I= codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg= @@ -7,93 +7,77 @@ codeberg.org/go-fonts/latin-modern v0.4.0 h1:vkRCc1y3whKA7iL9Ep0fSGVuJfqjix0ica9 codeberg.org/go-fonts/latin-modern v0.4.0/go.mod h1:BF68mZznJ9QHn+hic9ks2DaFl4sR5YhfM6xTYaP9vNw= codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= -codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= -codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= -codeberg.org/go-pdf/fpdf v0.10.0 h1:u+w669foDDx5Ds43mpiiayp40Ov6sZalgcPMDBcZRd4= -codeberg.org/go-pdf/fpdf v0.10.0/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= +codeberg.org/go-latex/latex v0.2.0 h1:Ol/a6VHY06N+5gPfewswymoRb5ZcKDXWVaVegcx4hbI= +codeberg.org/go-latex/latex v0.2.0/go.mod h1:VJAwQir7/T8LZxj7xAPivISKiVOwkMpQ8bTuPQ31X0Y= +codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= +codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= -git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= -git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= +git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM= +git.sr.ht/~sbinet/gg v0.7.0/go.mod h1:VYeli15tpMM4EvqlivlVbbyvWZlOU+EZn4XZmfBGUdM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20241111191808-71fefeed8910 h1:750te9HMlt//faGVj6qi/bbPsZZiif96Zxu9laAFsRc= -github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20241111191808-71fefeed8910/go.mod h1:XH7UFcXiBwpjFOhyHgTTmkTTA6rvn9oUlbnlIp9fRKI= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bombsimon/logrusr/v4 v4.1.0 h1:uZNPbwusB0eUXlO8hIUwStE6Lr5bLN6IgYgG+75kuh4= github.com/bombsimon/logrusr/v4 v4.1.0/go.mod h1:pjfHC5e59CvjTBIU3V3sGhFWFAnsnhOR03TRc6im0l8= -github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= -github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cert-manager/cert-manager v1.18.2 h1:H2P75ycGcTMauV3gvpkDqLdS3RSXonWF2S49QGA1PZE= -github.com/cert-manager/cert-manager v1.18.2/go.mod h1:icDJx4kG9BCNpGjBvrmsFd99d+lXUvWdkkcrSSQdIiw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A= -github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.5-0.20250722125442-5321204dac14 h1:aRBlYpmBz1eSze05xYoEo4C2GuiHl5JTohlQhIfaJJw= -github.com/envoyproxy/go-control-plane/envoy v1.32.5-0.20250722125442-5321204dac14/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= -github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-asn1-ber/asn1-ber v1.5.6 h1:CYsqysemXfEaQbyrLJmdsCRuufHoLa3P/gGWGl5TDrM= -github.com/go-asn1-ber/asn1-ber v1.5.6/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M= +github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= +github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= +github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -104,21 +88,45 @@ 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.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= -github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -129,12 +137,8 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -146,8 +150,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= @@ -156,18 +160,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= -github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= -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= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -184,23 +178,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc= github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= +github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -209,21 +194,13 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= -github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/onsi/ginkgo/v2 v2.30.0 h1:zxM/9XneXFIy64j6/wAmBIX4zRC7Hu6U8XFNZvDnCQc= +github.com/onsi/ginkgo/v2 v2.30.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -241,86 +218,64 @@ github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= +github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tsaarni/certyaml v0.10.0 h1:8ZWHO4Zg4VHUf7YblZNju44PcG5M+YtlJawiArYUHRs= -github.com/tsaarni/certyaml v0.10.0/go.mod h1:rI1wDTE/VQIglHOyGbjfvqb+5mWTVT5uLFVDDcT1sq8= -github.com/tsaarni/x500dn v1.0.0 h1:LvaWTkqRpse4VHBhB5uwf3wytokK4vF9IOyNAEyiA+U= -github.com/tsaarni/x500dn v1.0.0/go.mod h1:QaHa3EcUKC4dfCAZmj8+ZRGLKukWgpGv9H3oOCsAbcE= -github.com/vektra/mockery/v2 v2.53.5 h1:iktAY68pNiMvLoHxKqlSNSv/1py0QF/17UGrrAMYDI8= -github.com/vektra/mockery/v2 v2.53.5/go.mod h1:hIFFb3CvzPdDJJiU7J4zLRblUMv7OuezWsHPmswriwo= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tsaarni/certyaml v0.11.0 h1:qpiXKPCGZvQaYf3ParnvLcxMZmWqBIeU4XDAywfQUHw= +github.com/tsaarni/certyaml v0.11.0/go.mod h1:AiWJjkISlmC8shtMWPxsY9vMFpu+VYB8arwWr8079f4= +github.com/tsaarni/x500dn v1.1.0 h1:+rgqGj7LQEkdIIRLsYJm5S6M2dDBscb6/xiEcGW678s= +github.com/tsaarni/x500dn v1.1.0/go.mod h1:vzfi5pu5wr1eeFf9/0rIr5Bc1kxeyes4jFMCcp0wfCk= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= @@ -331,30 +286,28 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE= +golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= 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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -363,18 +316,18 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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-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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -382,20 +335,16 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -406,54 +355,46 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= -golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -gonum.org/v1/plot v0.16.0 h1:dK28Qx/Ky4VmPUN/2zeW0ELyM6ucDnBAj5yun7M9n1g= -gonum.org/v1/plot v0.16.0/go.mod h1:Xz6U1yDMi6Ni6aaXILqmVIb6Vro8E+K7Q/GeeH+Pn0c= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +gonum.org/v1/plot v0.17.0 h1:d0DwPVBe9jnEGqQBoZGl/P2M9WciJbG2CnV59C9QBT4= +gonum.org/v1/plot v0.17.0/go.mod h1:ipt2GUN1oqzr2O7wCjLDtw1ShfIYYNBp4o0O1Ez5B3Y= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -461,45 +402,35 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= -k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= -k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= -k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY= +k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo= +k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks= +k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8= +k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA= +k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8= +k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0= +k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= +k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4= +k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= -sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= -sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= -sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= +sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= +sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= +sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/hack/actions/bump-envoy-version/main.go b/hack/actions/bump-envoy-version/main.go new file mode 100644 index 00000000000..188cb4e1b89 --- /dev/null +++ b/hack/actions/bump-envoy-version/main.go @@ -0,0 +1,171 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build none + +// Updates all references to the Envoy version in the repository and generates a template for changelog entry. +// +// Usage: +// +// go run ./hack/actions/bump-envoy-version/main.go +// +// By default, the script updates to the latest patch release for the current minor version. +// To target a specific major or minor version: +// +// go run ./hack/actions/bump-envoy-version/main.go distroless-v1.35 +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "os/user" + "regexp" + "strings" + + "github.com/sirupsen/logrus" +) + +var ( + log = logrus.StandardLogger() + + filesToPatch = []string{ + "Makefile", // Envoy version was in Makefile in older code. + "cmd/contour/gatewayprovisioner.go", + "examples/contour/03-envoy.yaml", + "examples/deployment/03-envoy-deployment.yaml", + } +) + +func main() { + log.SetFormatter(&logrus.TextFormatter{ForceColors: true}) + + currentVersion := getCurrentEnvoyVersion() + + // releaseTrack is the Envoy major or minor version to track, e.g. "v1.36". + var releaseTrack string + if len(os.Args) < 2 { + // If no argument is given, derive the release track from the current minor version. + releaseTrack = currentVersion[:strings.LastIndex(currentVersion, ".")] + } else { + releaseTrack = os.Args[1] + } + + log.Infof("Current Envoy version: %s", currentVersion) + + // Strip "distroless" prefix for GitHub API lookup but use it for file updates. + var imagePrefix string + if isDistroless := strings.HasPrefix(currentVersion, "distroless-"); isDistroless { + releaseTrack = strings.TrimPrefix(releaseTrack, "distroless-") + imagePrefix = "distroless-" + } + + latestVersion := getLatestEnvoyVersion(releaseTrack) + log.Infof("Latest version: %s", latestVersion) + + updateFiles(imagePrefix + latestVersion) + changelogFile := createChangelogTemplate(latestVersion) + + log.Info("Envoy version update completed") + log.Info("Update following files manually (in main branch):") + log.Info("- site/content/resources/compatibility-matrix.md") + log.Info("- versions.yaml") + log.Infof("- %s (only needed if doing bump in main branch)", changelogFile) +} + +func getCurrentEnvoyVersion() string { + content, err := os.ReadFile("examples/contour/03-envoy.yaml") + if err != nil { + log.WithError(err).Fatal("Failed to determine current version") + } + + envoyImageRe := regexp.MustCompile(`docker\.io/envoyproxy/envoy:(distroless-)?v([0-9]+\.[0-9]+\.[0-9]+)`) + matches := envoyImageRe.FindStringSubmatch(string(content)) + if len(matches) < 3 { + log.Fatal("Failed to match current version in examples/contour/03-envoy.yaml") + } + + return matches[1] + "v" + matches[2] +} + +func getLatestEnvoyVersion(track string) string { + resp, err := http.Get("https://api.github.com/repos/envoyproxy/envoy/releases") + if err != nil { + log.WithError(err).Fatal("Failed to fetch releases from GitHub API") + } + defer resp.Body.Close() + + var releases []struct { + TagName string `json:"tag_name"` + } + if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { + log.WithError(err).Fatal("Failed to parse releases in GitHub API response") + } + + prefix := track + "." + for _, r := range releases { + if strings.HasPrefix(r.TagName, prefix) { + return r.TagName + } + } + log.WithField("track", track).Fatal("No release found for track") + return "" +} + +func updateFiles(version string) { + envoyImageRe := regexp.MustCompile(`docker\.io/envoyproxy/envoy:(distroless-)?v[0-9]+\.[0-9]+\.[0-9]+`) + + for _, file := range filesToPatch { + content, err := os.ReadFile(file) + if err != nil { + log.WithError(err).WithField("file", file).Fatal("Failed to read file") + } + + updated := envoyImageRe.ReplaceAllString(string(content), "docker.io/envoyproxy/envoy:"+version) + + if updated != string(content) { + if err := os.WriteFile(file, []byte(updated), 0o600); err != nil { + log.WithError(err).WithField("file", file).Fatal("Failed to write file") + } + log.Infof("Updated file: %s", file) + } + } + + log.Info("Running 'make generate' to update generated files") + cmd := exec.Command("make", "generate") + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.WithError(err).Fatal("Failed to run 'make generate'") + } +} + +func createChangelogTemplate(version string) string { + u, err := user.Current() + if err != nil { + log.WithError(err).Fatal("Failed to get current user") + } + + file := fmt.Sprintf("changelogs/unreleased/dddd-%s-small.md", u.Username) + majorMinor := version[:strings.LastIndex(version, ".")] + url := fmt.Sprintf("https://www.envoyproxy.io/docs/envoy/%s/version_history/%s/%s", version, majorMinor, version) + content := fmt.Sprintf("Updates Envoy to %s. See the [Envoy release notes](%s) for more information about the content of the release.\n", version, url) + + if err := os.WriteFile(file, []byte(content), 0o600); err != nil { + log.WithError(err).Fatal("Failed to write changelog") + } + log.Infof("Created changelog template: %s", file) + + return file +} diff --git a/hack/actions/bump-go-version/main.go b/hack/actions/bump-go-version/main.go new file mode 100644 index 00000000000..52592a52aa1 --- /dev/null +++ b/hack/actions/bump-go-version/main.go @@ -0,0 +1,184 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build none + +// Updates all references to the Go version in the repository and creates a template for changelog entry. +// +// Usage: +// +// go run ./hack/actions/bump-go-version/main.go +// +// By default, the script updates to the latest patch release for the current minor version. +// To target a specific major or minor version: +// +// go run ./hack/actions/bump-go-version/main.go 1.25 +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "os/user" + "regexp" + "strings" + + "github.com/sirupsen/logrus" +) + +var ( + log = logrus.StandardLogger() + + filesToPatch = []string{ + "Makefile", + ".github/workflows/build_daily.yaml", + ".github/workflows/build_tag.yaml", + ".github/workflows/codeql-analysis.yml", + ".github/workflows/prbuild.yaml", + } +) + +func main() { + log.SetFormatter(&logrus.TextFormatter{ForceColors: true}) + + currentVersion := getCurrentGoVersion() + + // releaseTrack is the Go major or minor version to track, e.g. "1.25". + var releaseTrack string + if len(os.Args) < 2 { + // If no argument is given, derive the release track from the current minor version. + releaseTrack = currentVersion[:strings.LastIndex(currentVersion, ".")] + } else { + releaseTrack = os.Args[1] + } + + log.Infof("Current Go version: %s", currentVersion) + + latestVersion := getLatestGoVersionByReleaseTrack(releaseTrack) + log.Infof("Latest version: %s", latestVersion) + + latestImageHash := getGolangImageHash(latestVersion) + log.Infof("Image hash: %s", latestImageHash) + + updateFiles(latestVersion, latestImageHash) + createChangelogTemplate(latestVersion) + + log.Info("Go version update completed") +} + +func getCurrentGoVersion() string { + content, err := os.ReadFile("Makefile") + if err != nil { + log.WithError(err).Fatal("Failed to determine current version") + } + + buildImageRe := regexp.MustCompile(`BUILD_BASE_IMAGE\s*\?=\s*golang:([0-9.]+)`) + matches := buildImageRe.FindStringSubmatch(string(content)) + if len(matches) < 2 { + log.Fatal("Failed to match current version in Makefile") + } + + return matches[1] +} + +func getLatestGoVersionByReleaseTrack(track string) string { + resp, err := http.Get("https://go.dev/dl/?mode=json&include=all") + if err != nil { + log.WithError(err).Fatal("Failed to fetch releases from go.dev API") + } + defer resp.Body.Close() + + var releases []struct{ Version string } + if err := json.NewDecoder(resp.Body).Decode(&releases); err != nil { + log.WithError(err).Fatal("Failed to parse releases in go.dev API response") + } + + prefix := "go" + track + for _, r := range releases { + if strings.HasPrefix(r.Version, prefix) { + return r.Version + } + } + log.WithField("track", track).Fatal("No release found for track") + return "" +} + +func getGolangImageHash(version string) string { + tag := strings.TrimPrefix(version, "go") + url := fmt.Sprintf("https://registry.hub.docker.com/v2/repositories/library/golang/tags/%s", tag) + + resp, err := http.Get(url) // #nosec G107: Potential HTTP request made with variable url + if err != nil { + log.WithError(err).Fatal("Failed to fetch image hash from Docker Hub") + } + defer resp.Body.Close() + + var info struct{ Digest string } + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + log.WithError(err).Fatal("Failed to parse tag info from Docker Hub API response") + } + + if info.Digest == "" { + log.WithField("version", version).Fatal("No image found for version") + } + return info.Digest +} + +func updateFiles(version, hash string) { + ver := strings.TrimPrefix(version, "go") + buildImageRegexp := regexp.MustCompile(`(BUILD_BASE_IMAGE\s*\?=\s*golang:)[0-9.]+(@sha256:[a-f0-9]{64})?`) + goVersionRegexp := regexp.MustCompile(`(GO_VERSION:\s*)[0-9.]+`) + + for _, file := range filesToPatch { + content, err := os.ReadFile(file) + if err != nil { + log.WithError(err).WithField("file", file).Fatal("Failed to read file") + } + + updated := buildImageRegexp.ReplaceAllString(string(content), fmt.Sprintf("${1}%s@%s", ver, hash)) + updated = goVersionRegexp.ReplaceAllString(updated, "${1}"+ver) + + if updated != string(content) { + if err := os.WriteFile(file, []byte(updated), 0o600); err != nil { + log.WithError(err).WithField("file", file).Fatal("Failed to write file") + } + log.Infof("Updated file: %s", file) + } + } + + log.Info("Running 'go mod tidy' to update module files") + cmd := exec.Command("go", "mod", "tidy") + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.WithError(err).Fatal("Failed to run 'go mod tidy'") + } +} + +func createChangelogTemplate(version string) { + u, err := user.Current() + if err != nil { + log.WithError(err).Fatal("Failed to get current user") + } + + file := fmt.Sprintf("changelogs/unreleased/nnnn-%s-small.md", u.Username) + parts := strings.SplitN(strings.TrimPrefix(version, "go"), ".", 3) + url := fmt.Sprintf("https://go.dev/doc/devel/release#go%s.%s.0", parts[0], parts[1]) + content := fmt.Sprintf("Updates Go to %s. See the [Go release notes](%s) for more information about the content of the release.\n", version, url) + + if err := os.WriteFile(file, []byte(content), 0o600); err != nil { + log.WithError(err).Fatal("Failed to write changelog") + } + log.Infof("Created changelog template: %s", file) +} diff --git a/hack/actions/install-kubernetes-toolchain.sh b/hack/actions/install-kubernetes-toolchain.sh index e949c9ebac2..c02474b4859 100755 --- a/hack/actions/install-kubernetes-toolchain.sh +++ b/hack/actions/install-kubernetes-toolchain.sh @@ -4,8 +4,8 @@ set -o errexit set -o nounset set -o pipefail -readonly KUBECTL_VERS="v1.34.0" -readonly KIND_VERS="v0.30.0" +readonly KUBECTL_VERS="v1.36.1" +readonly KIND_VERS="v0.32.0" readonly PROGNAME=$(basename $0) readonly CURL=${CURL:-curl} diff --git a/hack/generate-api-docs.sh b/hack/generate-api-docs.sh index 404bf0d2fbf..a385b98d02e 100755 --- a/hack/generate-api-docs.sh +++ b/hack/generate-api-docs.sh @@ -17,7 +17,7 @@ readonly PKGROOT="${1:-github.com/projectcontour/contour/apis/projectcontour}" gendoc::exec() { local -r confdir="${REPO}/hack/api-docs-config/refdocs" - go run github.com/ahmetb/gen-crd-api-reference-docs \ + go tool -modfile="${REPO}/tools/go.mod" github.com/ahmetb/gen-crd-api-reference-docs \ -template-dir "${confdir}" \ -config "${confdir}/config.json" \ "$@" diff --git a/hack/generate-crd-deepcopy.sh b/hack/generate-crd-deepcopy.sh index 1d18e5ed6f1..31a2a4c61f3 100755 --- a/hack/generate-crd-deepcopy.sh +++ b/hack/generate-crd-deepcopy.sh @@ -39,8 +39,8 @@ readonly HEADER=$(mktemp) boilerplate > "${HEADER}" echo "controller-gen version: " -go run sigs.k8s.io/controller-tools/cmd/controller-gen --version +go tool -modfile="${REPO}/tools/go.mod" sigs.k8s.io/controller-tools/cmd/controller-gen --version -exec go run sigs.k8s.io/controller-tools/cmd/controller-gen \ +exec go tool -modfile="${REPO}/tools/go.mod" sigs.k8s.io/controller-tools/cmd/controller-gen \ "object:headerFile=${HEADER}" \ "paths=${PATHS}" diff --git a/hack/generate-crd-yaml.sh b/hack/generate-crd-yaml.sh index c7737363c05..56ea2c30982 100755 --- a/hack/generate-crd-yaml.sh +++ b/hack/generate-crd-yaml.sh @@ -16,19 +16,31 @@ trap 'rm -rf "$TEMPDIR"; exit' 0 1 2 15 cd "${REPO}" echo "controller-gen version: " -go run sigs.k8s.io/controller-tools/cmd/controller-gen --version +go tool -modfile="${REPO}/tools/go.mod" sigs.k8s.io/controller-tools/cmd/controller-gen --version # Controller-gen seems to use an unstable sort for the order of output of the CRDs # so, output them to separate files, then concatenate those files. # That should give a stable sort. -go run sigs.k8s.io/controller-tools/cmd/controller-gen \ +go tool -modfile="${REPO}/tools/go.mod" sigs.k8s.io/controller-tools/cmd/controller-gen \ crd:crdVersions=v1 "paths=${PATHS}" "output:dir=${TEMPDIR}" -# Explicitly add "preserveUnknownFields: false" to CRD specs since any CRDs created -# as v1beta1 will have this field set to true, which we don't want going forward, and -# it needs to be explicitly specified in order to be updated/removed. After enough time -# has passed and we're not concerned about folks upgrading from v1beta1 CRDs, we can -# remove the awk call that adds this field to the spec, and rely on the v1 default. -ls "${TEMPDIR}"/*.yaml | xargs cat | sed '/^$/d' \ - | awk '/group: projectcontour.io/{print " preserveUnknownFields: false"}1' \ - > "${REPO}/examples/contour/01-crds.yaml" +# Remove "error" from required fields in load balancer status. +# For details, see: +# - https://github.com/projectcontour/contour/issues/7391 +# - https://github.com/kubernetes-sigs/controller-tools/pull/944#issuecomment-3314629362 +# This workaround can be removed if the upstream Kubernetes type resolves the conflicting markers. +readonly HTTPPROXY_CRD="${TEMPDIR}/projectcontour.io_httpproxies.yaml" +readonly PATCH_PATH="/spec/versions/0/schema/openAPIV3Schema/properties/status/properties/loadBalancer/properties/ingress/items/properties/ports/items/required/0" + +kubectl::patch() { + kubectl patch -f "$HTTPPROXY_CRD" --local --type=json -p "$1" "${@:2}" +} + +kubectl::patch "[{\"op\": \"test\", \"path\": \"$PATCH_PATH\", \"value\": \"error\"}]" --dry-run=client > /dev/null || { + echo "Error: CRD structure has changed. The workaround for issue #7391 may no longer be needed or needs updating." + exit 1 +} + +kubectl::patch "[{\"op\": \"remove\", \"path\": \"$PATCH_PATH\"}]" -o yaml > "$HTTPPROXY_CRD.tmp" && { echo "---"; cat "$HTTPPROXY_CRD.tmp"; } > "$HTTPPROXY_CRD" + +ls "${TEMPDIR}"/*.yaml | xargs cat | sed '/^$/d' > "${REPO}/examples/contour/01-crds.yaml" diff --git a/hack/generate-rbac.sh b/hack/generate-rbac.sh index 3160afa6775..7a65fbcd974 100755 --- a/hack/generate-rbac.sh +++ b/hack/generate-rbac.sh @@ -18,9 +18,9 @@ cat > "${REPO}/examples/contour/02-role-contour.yaml" < "${REPO}/examples/gateway-provisioner/01-roles.yaml" < 1 { + return errors.New("only one of prefix, suffix, exact, and contains should be set in the allowedHeader") + } + } + + return nil +} + // ValidateRegex returns an error if the supplied // RE2 regex syntax is invalid. func ValidateRegex(regex string) error { diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 89f5c782e6e..5120b9d6cad 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -163,6 +163,35 @@ func (hc *HeaderMatchCondition) String() string { return "header: " + details } +// AuthorizationServiceType defines whether the external authorization server +// uses HTTP or gRPC protocol. +type AuthorizationServiceType int + +const ( + // AuthorizationServiceGRPC indicates the server implements the gRPC ext_authz protocol. + AuthorizationServiceGRPC AuthorizationServiceType = iota + // AuthorizationServiceHTTP indicates the server implements the raw HTTP ext_authz protocol. + AuthorizationServiceHTTP +) + +const ( + // HeaderNameMatchTypeExact matches a header name exactly. + HeaderNameMatchTypeExact = "exact" + // HeaderNameMatchTypePrefix matches a header name by prefix. + HeaderNameMatchTypePrefix = "prefix" + // HeaderNameMatchTypeSuffix matches a header name by suffix. + HeaderNameMatchTypeSuffix = "suffix" + // HeaderNameMatchTypeContains matches a header name if it contains the provided value. + HeaderNameMatchTypeContains = "contains" +) + +// HeaderNameMatchCondition matches an HTTP header name by MatchType. +type HeaderNameMatchCondition struct { + MatchType string + Value string + IgnoreCase bool +} + const ( // QueryParamMatchTypeExact matches a querystring parameter value exactly. QueryParamMatchTypeExact = "exact" @@ -495,13 +524,10 @@ type HeadersPolicy struct { // CookieRewritePolicy defines how attributes of an HTTP Set-Cookie header // can be rewritten. type CookieRewritePolicy struct { - Name string - Path *string - Domain *string - // Using an uint since pointer to boolean gets dereferenced in golang - // text templates so we have no way of distinguishing if unset or set to false. - // 0 means unset, 1 means false, 2 means true - Secure uint + Name string + Path *string + Domain *string + Secure *bool SameSite *string } @@ -849,7 +875,8 @@ type JWTProvider struct { Name string Issuer string Audiences []string - RemoteJWKS RemoteJWKS + RemoteJWKS *RemoteJWKS + LocalJWKS *LocalJWKS ForwardJWT bool } @@ -860,6 +887,10 @@ type RemoteJWKS struct { CacheDuration *time.Duration } +type LocalJWKS struct { + JWKS []byte +} + // DNSNameCluster is a cluster that routes directly to a DNS // name (i.e. not a Kubernetes service). type DNSNameCluster struct { @@ -890,6 +921,28 @@ type IPFilterRule struct { // ExternalAuthorization contains the configuration for enabling // the ExtAuthz filter. type ExternalAuthorization struct { + // ServiceAPIType defines the external authorization service API type. + // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server + // or a gRPC authorization server. + ServiceAPIType AuthorizationServiceType + + // Note that in addition to the user’s supplied matchers, Host, Method, Path, Content-Length, and Authorization are additionally included in the list. + HTTPAllowedAuthorizationHeaders []HeaderNameMatchCondition + + // HTTPAllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + // Note that coexistent headers will be overridden. + HTTPAllowedUpstreamHeaders []HeaderNameMatchCondition + + // HTTPPathPrefix Sets a prefix to the value of authorization request header Path. + HTTPPathPrefix string + + // Note: This field is not used by Envoy + // https://github.com/envoyproxy/envoy/issues/5357 + // + // HttpServerURI sets the URI of the external HTTP authorization server to which authorization requests must be sent. + // Only required for http services. + // HttpServerURI string + // AuthorizationService points to the extension that client // requests are forwarded to for authorization. If nil, no // authorization is enabled for this host. @@ -1213,13 +1266,14 @@ func (s *ServiceCluster) Rebalance() { } } -// Secret represents a K8s Secret for TLS usage as a DAG Vertex. A Secret is +// Secret represents a K8s Secret as a DAG Vertex. A Secret is // a leaf in the DAG. type Secret struct { - Object *core_v1.Secret - ValidTLSSecret *SecretValidationStatus - ValidCASecret *SecretValidationStatus - ValidCRLSecret *SecretValidationStatus + Object *core_v1.Secret + ValidTLSSecret *SecretValidationStatus + ValidCASecret *SecretValidationStatus + ValidCRLSecret *SecretValidationStatus + ValidJWKSSecret map[string]*SecretValidationStatus } func (s *Secret) Name() string { return s.Object.Name } diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index 4ef7824592f..541e97e22bd 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -17,6 +17,7 @@ import ( "fmt" "net/http" "regexp" + "slices" "sort" "strings" "time" @@ -403,13 +404,7 @@ type listenerInfo struct { } func (l *listenerInfo) AllowsKind(kind gatewayapi_v1.Kind) bool { - for _, allowedKind := range l.allowedKinds { - if allowedKind == kind { - return true - } - } - - return false + return slices.Contains(l.allowedKinds, kind) } // isAddressAssigned returns true if either there are no addresses requested in specAddresses, diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index d0f60ccf8cb..80ddf4e819b 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" "regexp" + "slices" "sort" "strconv" "strings" @@ -311,6 +312,17 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_v1.HTTPProxy) { return } + // Fallback certificates and JWT verification are + // incompatible because fallback aggregates routes from + // multiple vhosts onto a single HTTPConnectionManager, + // and different vhosts may have different JWT providers + // that cannot all be installed on one filter chain. + if tls.EnableFallbackCertificate && len(proxy.Spec.VirtualHost.JWTProviders) > 0 { + validCond.AddError(contour_v1.ConditionTypeTLSError, "TLSIncompatibleFeatures", + "Spec.Virtualhost.TLS fallback & JWT providers are incompatible") + return + } + // If FallbackCertificate is enabled, but no cert passed, set error if tls.EnableFallbackCertificate { if p.FallbackCertificate == nil { @@ -416,120 +428,154 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_v1.HTTPProxy) { defaultJWTProvider = jwtProvider.Name } - jwksURL, err := url.Parse(jwtProvider.RemoteJWKS.URI) - if err != nil { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSURIInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI is invalid: %s", err) + if jwtProvider.RemoteJWKS.URI != "" && jwtProvider.LocalJWKS.SecretName != "" { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "JWKSSourceConflict", + "Spec.VirtualHost.JWTProviders for provider %q is invalid: at most one of remoteJWKS or localJWKS may be set", jwtProvider.Name) return } - - if jwksURL.Scheme != "http" && jwksURL.Scheme != "https" { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSSchemeInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI has invalid scheme %q, must be http or https", jwksURL.Scheme) + if jwtProvider.RemoteJWKS.URI == "" && jwtProvider.LocalJWKS.SecretName == "" { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "JWKSSourceMissing", + "Spec.VirtualHost.JWTProviders for provider %q is invalid: exactly one of remoteJWKS or localJWKS must be set", jwtProvider.Name) return } - var uv *PeerValidationContext + switch { + case jwtProvider.LocalJWKS.SecretName != "": + jwksSecretNamespacedName := types.NamespacedName{ + Name: jwtProvider.LocalJWKS.SecretName, + Namespace: proxy.Namespace, + } + jwksData, err := p.source.LookupJWKSFromSecret(jwksSecretNamespacedName, jwtProvider.LocalJWKS.Key) + if err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "LocalJWKSInvalid", + "Spec.VirtualHost.JWTProviders.LocalJWKS for provider %q is invalid: %s", jwtProvider.Name, err) + return + } + svhost.JWTProviders = append(svhost.JWTProviders, JWTProvider{ + Name: jwtProvider.Name, + Issuer: jwtProvider.Issuer, + Audiences: jwtProvider.Audiences, + LocalJWKS: &LocalJWKS{ + JWKS: jwksData, + }, + ForwardJWT: jwtProvider.ForwardJWT, + }) + case jwtProvider.RemoteJWKS.URI != "": + jwksURL, err := url.Parse(jwtProvider.RemoteJWKS.URI) + if err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSURIInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI is invalid: %s", err) + return + } - if jwtProvider.RemoteJWKS.UpstreamValidation != nil { - if jwksURL.Scheme == "http" { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSUpstreamValidationInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation must not be specified when URI scheme is http.") + if jwksURL.Scheme != "http" && jwksURL.Scheme != "https" { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSSchemeInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI has invalid scheme %q, must be http or https", jwksURL.Scheme) return } - caCertNamespacedName := k8s.NamespacedNameFrom(jwtProvider.RemoteJWKS.UpstreamValidation.CACertificate, k8s.DefaultNamespace(proxy.Namespace)) - uv, err = p.source.LookupUpstreamValidation(jwtProvider.RemoteJWKS.UpstreamValidation, caCertNamespacedName, proxy.Namespace) - if err != nil { - if _, ok := err.(DelegationNotPermittedError); ok { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSCACertificateNotDelegated", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation.CACertificate Secret %q is not configured for certificate delegation", caCertNamespacedName) - } else { + var uv *PeerValidationContext + + if jwtProvider.RemoteJWKS.UpstreamValidation != nil { + if jwksURL.Scheme == "http" { validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSUpstreamValidationInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation is invalid: %s", err) + "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation must not be specified when URI scheme is http.") + return + } + + caCertNamespacedName := k8s.NamespacedNameFrom(jwtProvider.RemoteJWKS.UpstreamValidation.CACertificate, k8s.DefaultNamespace(proxy.Namespace)) + uv, err = p.source.LookupUpstreamValidation(jwtProvider.RemoteJWKS.UpstreamValidation, caCertNamespacedName, proxy.Namespace) + if err != nil { + if _, ok := err.(DelegationNotPermittedError); ok { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSCACertificateNotDelegated", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation.CACertificate Secret %q is not configured for certificate delegation", caCertNamespacedName) + } else { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSUpstreamValidationInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.UpstreamValidation is invalid: %s", err) + } + return } - return } - } - jwksTimeout := time.Second - if len(jwtProvider.RemoteJWKS.Timeout) > 0 { - res, err := time.ParseDuration(jwtProvider.RemoteJWKS.Timeout) - if err != nil { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSTimeoutInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.Timeout is invalid: %s", err) - return + jwksTimeout := time.Second + if len(jwtProvider.RemoteJWKS.Timeout) > 0 { + res, err := time.ParseDuration(jwtProvider.RemoteJWKS.Timeout) + if err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSTimeoutInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.Timeout is invalid: %s", err) + return + } + + jwksTimeout = res } - jwksTimeout = res - } + var cacheDuration *time.Duration + if len(jwtProvider.RemoteJWKS.CacheDuration) > 0 { + res, err := time.ParseDuration(jwtProvider.RemoteJWKS.CacheDuration) + if err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSCacheDurationInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.CacheDuration is invalid: %s", err) + return + } - var cacheDuration *time.Duration - if len(jwtProvider.RemoteJWKS.CacheDuration) > 0 { - res, err := time.ParseDuration(jwtProvider.RemoteJWKS.CacheDuration) - if err != nil { - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSCacheDurationInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.CacheDuration is invalid: %s", err) - return + cacheDuration = &res } - cacheDuration = &res - } + // Check for a specified port and use it, else use the + // standard ports by scheme. + var port int + switch { + case len(jwksURL.Port()) > 0: + p, err := strconv.Atoi(jwksURL.Port()) + if err != nil { + // This theoretically shouldn't be possible as jwksURL.Port() will + // only return a value if it's numeric, but we need to convert to + // int anyway so handle the error. + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSPortInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI has an invalid port: %s", err) + return + } + port = p + case jwksURL.Scheme == "http": + port = 80 + case jwksURL.Scheme == "https": + port = 443 + } - // Check for a specified port and use it, else use the - // standard ports by scheme. - var port int - switch { - case len(jwksURL.Port()) > 0: - p, err := strconv.Atoi(jwksURL.Port()) - if err != nil { - // This theoretically shouldn't be possible as jwksURL.Port() will - // only return a value if it's numeric, but we need to convert to - // int anyway so handle the error. - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSPortInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.URI has an invalid port: %s", err) + // Get the DNS lookup family if specified, otherwise + // default to the Contour-wide setting. + dnsLookupFamily := "" + switch jwtProvider.RemoteJWKS.DNSLookupFamily { + case "auto", "v4", "v6", "all": + dnsLookupFamily = jwtProvider.RemoteJWKS.DNSLookupFamily + case "": + dnsLookupFamily = string(p.DNSLookupFamily) + default: + validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSDNSLookupFamilyInvalid", + "Spec.VirtualHost.JWTProviders.RemoteJWKS.DNSLookupFamily has an invalid value %q, must be auto, all, v4 or v6", jwtProvider.RemoteJWKS.DNSLookupFamily) return } - port = p - case jwksURL.Scheme == "http": - port = 80 - case jwksURL.Scheme == "https": - port = 443 - } - - // Get the DNS lookup family if specified, otherwise - // default to the Contour-wide setting. - dnsLookupFamily := "" - switch jwtProvider.RemoteJWKS.DNSLookupFamily { - case "auto", "v4", "v6", "all": - dnsLookupFamily = jwtProvider.RemoteJWKS.DNSLookupFamily - case "": - dnsLookupFamily = string(p.DNSLookupFamily) - default: - validCond.AddErrorf(contour_v1.ConditionTypeJWTVerificationError, "RemoteJWKSDNSLookupFamilyInvalid", - "Spec.VirtualHost.JWTProviders.RemoteJWKS.DNSLookupFamily has an invalid value %q, must be auto, all, v4 or v6", jwtProvider.RemoteJWKS.DNSLookupFamily) - return - } - svhost.JWTProviders = append(svhost.JWTProviders, JWTProvider{ - Name: jwtProvider.Name, - Issuer: jwtProvider.Issuer, - Audiences: jwtProvider.Audiences, - RemoteJWKS: RemoteJWKS{ - URI: jwtProvider.RemoteJWKS.URI, - Timeout: jwksTimeout, - Cluster: DNSNameCluster{ - Address: jwksURL.Hostname(), - Scheme: jwksURL.Scheme, - Port: port, - DNSLookupFamily: dnsLookupFamily, - UpstreamValidation: uv, - UpstreamTLS: p.UpstreamTLS, + svhost.JWTProviders = append(svhost.JWTProviders, JWTProvider{ + Name: jwtProvider.Name, + Issuer: jwtProvider.Issuer, + Audiences: jwtProvider.Audiences, + RemoteJWKS: &RemoteJWKS{ + URI: jwtProvider.RemoteJWKS.URI, + Timeout: jwksTimeout, + Cluster: DNSNameCluster{ + Address: jwksURL.Hostname(), + Scheme: jwksURL.Scheme, + Port: port, + DNSLookupFamily: dnsLookupFamily, + UpstreamValidation: uv, + UpstreamTLS: p.UpstreamTLS, + }, + CacheDuration: cacheDuration, }, - CacheDuration: cacheDuration, - }, - ForwardJWT: jwtProvider.ForwardJWT, - }) + ForwardJWT: jwtProvider.ForwardJWT, + }) + } } } } @@ -1203,7 +1249,8 @@ func toExtProcOverrides( validCond, defaultNamespace, contour_v1.ConditionTypeExtProcError, - extClusterGetter) + extClusterGetter, + ) if !ok { return nil } @@ -1211,7 +1258,8 @@ func toExtProcOverrides( contour_v1.ConditionTypeExtProcError, override.ResponseTimeout, validCond, - extSvc) + extSvc, + ) if !ok { return nil } @@ -1232,7 +1280,7 @@ func toIPFilterRules(allowPolicy, denyPolicy []contour_v1.IPFilterPolicy, validC validCond.AddError(contour_v1.ConditionTypeIPFilterError, "IncompatibleIPAddressFilters", "cannot specify both `ipAllowPolicy` and `ipDenyPolicy`") err = fmt.Errorf("invalid ip filter") - return + return allow, filters, err case len(allowPolicy) > 0: allow = true ipPolicies = allowPolicy @@ -1241,7 +1289,7 @@ func toIPFilterRules(allowPolicy, denyPolicy []contour_v1.IPFilterPolicy, validC ipPolicies = denyPolicy } if ipPolicies == nil { - return + return allow, filters, err } filters = make([]IPFilterRule, 0, len(ipPolicies)) for _, p := range ipPolicies { @@ -1270,7 +1318,7 @@ func toIPFilterRules(allowPolicy, denyPolicy []contour_v1.IPFilterPolicy, validC allow = false filters = nil } - return + return allow, filters, err } // processHTTPProxyTCPProxy processes the spec.tcpproxy stanza in a HTTPProxy document @@ -1479,12 +1527,7 @@ func (p *HTTPProxyProcessor) rootAllowed(namespace string) bool { if len(p.source.RootNamespaces) == 0 { return true } - for _, ns := range p.source.RootNamespaces { - if ns == namespace { - return true - } - } - return false + return slices.Contains(p.source.RootNamespaces, namespace) } func (p *HTTPProxyProcessor) computeVirtualHostAuthorization( @@ -1497,20 +1540,99 @@ func (p *HTTPProxyProcessor) computeVirtualHostAuthorization( validCond, httpproxy.Namespace, contour_v1.ConditionTypeAuthError, - p.dag.GetExtensionCluster) + p.dag.GetExtensionCluster, + ) if !ok { return nil } - ok, respTimeout := determineExtensionServiceTimeout(contour_v1.ConditionTypeAuthError, auth.ResponseTimeout, validCond, extSvc) - if !ok { + extAuth := NewExternalAuthorization(auth, validCond) + if extAuth == nil { + return nil + } + + // If no explicit timeout was configured, fall back to the extension service's own timeout. + if extAuth.AuthorizationResponseTimeout.UseDefault() { + extAuth.AuthorizationResponseTimeout = extSvc.RouteTimeoutPolicy.ResponseTimeout + } + + extAuth.AuthorizationService = extSvc + return extAuth +} + +// convertHTTPAuthzAllowedHeaders converts API header match types to internal DAG types. +// Assumes headers have already been validated with ExternalAuthAllowedHeadersValid. +func convertHTTPAuthzAllowedHeaders(headers []contour_v1.HTTPAuthorizationServerAllowedHeaders) []HeaderNameMatchCondition { + var result []HeaderNameMatchCondition + for _, h := range headers { + var matchType, value string + switch { + case h.Exact != "": + matchType = HeaderNameMatchTypeExact + value = h.Exact + case h.Prefix != "": + matchType = HeaderNameMatchTypePrefix + value = h.Prefix + case h.Suffix != "": + matchType = HeaderNameMatchTypeSuffix + value = h.Suffix + case h.Contains != "": + matchType = HeaderNameMatchTypeContains + value = h.Contains + } + + result = append(result, HeaderNameMatchCondition{ + MatchType: matchType, + Value: value, + IgnoreCase: h.IgnoreCase, + }) + } + return result +} + +func NewExternalAuthorization(auth *contour_v1.AuthorizationServer, validCond *contour_v1.DetailedCondition) *ExternalAuthorization { + tout, err := timeout.Parse(auth.ResponseTimeout) + if err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthResponseTimeoutInvalid", + "Spec.Virtualhost.Authorization.ResponseTimeout is invalid: %s", err) return nil } - extAuth := &ExternalAuthorization{ - AuthorizationService: extSvc, + extAuthz := &ExternalAuthorization{ AuthorizationFailOpen: auth.FailOpen, - AuthorizationResponseTimeout: *respTimeout, + AuthorizationResponseTimeout: tout, + } + + switch auth.ServiceType { + case contour_v1.AuthorizationHTTPService: + extAuthz.ServiceAPIType = AuthorizationServiceHTTP + default: + extAuthz.ServiceAPIType = AuthorizationServiceGRPC + } + + // Validate Context is only used with gRPC. + if auth.AuthPolicy != nil && len(auth.AuthPolicy.Context) > 0 && extAuthz.ServiceAPIType == AuthorizationServiceHTTP { + validCond.AddError(contour_v1.ConditionTypeAuthError, "AuthContextForHTTP", + "Spec.Virtualhost.Authorization.AuthPolicy.Context are only applied to grpc service type") + return nil + } + + if auth.HTTPServerSettings != nil { + if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedAuthorizationHeaders); err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", + "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: %s", err) + return nil + } + extAuthz.HTTPAllowedAuthorizationHeaders = convertHTTPAuthzAllowedHeaders(auth.HTTPServerSettings.AllowedAuthorizationHeaders) + + if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedUpstreamHeaders); err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", + "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: %s", err) + return nil + } + extAuthz.HTTPAllowedUpstreamHeaders = convertHTTPAuthzAllowedHeaders(auth.HTTPServerSettings.AllowedUpstreamHeaders) + + extAuthz.HTTPPathPrefix = auth.HTTPServerSettings.PathPrefix } if auth.WithRequestBody != nil { @@ -1518,13 +1640,13 @@ func (p *HTTPProxyProcessor) computeVirtualHostAuthorization( if auth.WithRequestBody.MaxRequestBytes != 0 { maxRequestBytes = auth.WithRequestBody.MaxRequestBytes } - extAuth.AuthorizationServerWithRequestBody = &AuthorizationServerBufferSettings{ + extAuthz.AuthorizationServerWithRequestBody = &AuthorizationServerBufferSettings{ MaxRequestBytes: maxRequestBytes, AllowPartialMessage: auth.WithRequestBody.AllowPartialMessage, PackAsBytes: auth.WithRequestBody.PackAsBytes, } } - return extAuth + return extAuthz } func (p *HTTPProxyProcessor) computeVirtualHostExtProc( @@ -1537,7 +1659,8 @@ func (p *HTTPProxyProcessor) computeVirtualHostExtProc( validCond, httpproxy.Namespace, contour_v1.ConditionTypeExtProcError, - p.dag.GetExtensionCluster) + p.dag.GetExtensionCluster, + ) if !ok { return nil } diff --git a/internal/dag/httpproxy_processor_test.go b/internal/dag/httpproxy_processor_test.go index 1fb12fc4013..45a43194e45 100644 --- a/internal/dag/httpproxy_processor_test.go +++ b/internal/dag/httpproxy_processor_test.go @@ -1523,7 +1523,14 @@ func TestDetermineUpstreamTLS(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - got := (*UpstreamTLS)(tc.envoyTLS) + var got *UpstreamTLS + if tc.envoyTLS != nil { + got = &UpstreamTLS{ + MinimumProtocolVersion: tc.envoyTLS.MinimumProtocolVersion, + MaximumProtocolVersion: tc.envoyTLS.MaximumProtocolVersion, + CipherSuites: tc.envoyTLS.CipherSuites, + } + } assert.Equal(t, tc.want, got) }) } diff --git a/internal/dag/policy.go b/internal/dag/policy.go index f897118de21..61f58d04d98 100644 --- a/internal/dag/policy.go +++ b/internal/dag/policy.go @@ -335,6 +335,8 @@ func escapeHeaderValue(value string, dynamicHeaders map[string]string) string { "UPSTREAM_REMOTE_ADDRESS", "RESPONSE_FLAGS", "RESPONSE_CODE_DETAILS", + "TLS_JA3_FINGERPRINT", + "TLS_JA4_FINGERPRINT", } { escapedValue = strings.ReplaceAll(escapedValue, "%%"+envoyVar+"%%", "%"+envoyVar+"%") } @@ -363,18 +365,10 @@ func cookieRewritePolicies(policies []contour_v1.CookieRewritePolicy) ([]CookieR policiesSet++ domain = ptr.To(p.DomainRewrite.Value) } - // We use a uint here since a pointer to bool cannot be - // distingiuished when unset or false in golang text templates. - // 0 means unset. - secure := uint(0) + var secure *bool if p.Secure != nil { policiesSet++ - // Increment to indicate it has been set. - secure++ - if *p.Secure { - // Increment to indicate it is true. - secure++ - } + secure = p.Secure } if p.SameSite != nil { policiesSet++ diff --git a/internal/dag/policy_test.go b/internal/dag/policy_test.go index 8fddb193236..ba4a9787615 100644 --- a/internal/dag/policy_test.go +++ b/internal/dag/policy_test.go @@ -1621,6 +1621,26 @@ func TestHeadersPolicyRoute(t *testing.T) { Remove: []string{"Y-Header"}, }, }, + { + name: "ja3 fingerprint header passthrough", + policy: &contour_v1.HeadersPolicy{ + Set: []contour_v1.HeaderValue{{Name: "X-JA3-Fingerprint", Value: "%TLS_JA3_FINGERPRINT%"}}, + }, + expected: &HeadersPolicy{ + Set: map[string]string{"X-Ja3-Fingerprint": "%TLS_JA3_FINGERPRINT%"}, + Remove: nil, + }, + }, + { + name: "ja4 fingerprint header passthrough", + policy: &contour_v1.HeadersPolicy{ + Set: []contour_v1.HeaderValue{{Name: "X-JA4-Fingerprint", Value: "%TLS_JA4_FINGERPRINT%"}}, + }, + expected: &HeadersPolicy{ + Set: map[string]string{"X-Ja4-Fingerprint": "%TLS_JA4_FINGERPRINT%"}, + Remove: nil, + }, + }, } for _, tc := range tests { diff --git a/internal/dag/secret.go b/internal/dag/secret.go index 880671573a5..f4aec120f40 100644 --- a/internal/dag/secret.go +++ b/internal/dag/secret.go @@ -16,6 +16,7 @@ package dag import ( "bytes" "crypto/x509" + "encoding/json" "encoding/pem" "errors" "fmt" @@ -97,6 +98,44 @@ func validCRLSecret(secret *core_v1.Secret) error { return nil } +// validJWKSecret returns an error if the Secret is not of type Opaque or +// if it doesn't contain a valid JWKS in the data entry for the given key. +func validJWKSSecret(secret *core_v1.Secret, key string) error { + if secret.Type != core_v1.SecretTypeOpaque { + return fmt.Errorf("secret type is not %q", core_v1.SecretTypeOpaque) + } + + data, ok := secret.Data[key] + if !ok || len(data) == 0 { + return fmt.Errorf("missing %q key or empty value", key) + } + + return validJWKSData(data) +} + +// validJWKSData returns an error if data is not a JSON JWKS object (RFC 7517) +func validJWKSData(data []byte) error { + trim := bytes.TrimSpace(data) + if len(trim) == 0 { + return errors.New("empty JWKS") + } + var v any + if err := json.Unmarshal(trim, &v); err != nil { + return fmt.Errorf("not valid JSON: %w", err) + } + obj, ok := v.(map[string]any) + if !ok { + return errors.New("JWKS must be a JSON object") + } + if keys, hasKeys := obj["keys"]; hasKeys { + if _, ok := keys.([]any); !ok { + return errors.New("JWKS \"keys\" must be an array") + } + return nil + } + return errors.New("JWKS must contain a \"keys\" array") +} + // containsPEMHeader returns true if the given slice contains a string // that looks like a PEM header block. The problem is that pem.Decode // does not give us a way to distinguish between a missing PEM block diff --git a/internal/dag/secret_test.go b/internal/dag/secret_test.go index d3e629cb104..fe59dc7e94a 100644 --- a/internal/dag/secret_test.go +++ b/internal/dag/secret_test.go @@ -16,9 +16,11 @@ package dag import ( "errors" "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" core_v1 "k8s.io/api/core/v1" "github.com/projectcontour/contour/internal/fixture" @@ -259,33 +261,33 @@ func secretdata(cert, key string) map[string][]byte { // // See also: https://tools.ietf.org/html/rfc7468#section-5.2 func caBundleData(cert ...string) map[string][]byte { - var data string + var data strings.Builder - data += "start of CA bundle\n" + data.WriteString("start of CA bundle\n") for n, c := range cert { - data += fmt.Sprintf("certificate %d\n", n) - data += c - data += "\n" + data.WriteString(fmt.Sprintf("certificate %d\n", n)) + data.WriteString(c) + data.WriteString("\n") } - data += "end of CA bundle\n" + data.WriteString("end of CA bundle\n") return map[string][]byte{ - CACertificateKey: []byte(data), + CACertificateKey: []byte(data.String()), } } // pemBundle concatenates supplied PEM strings // into a valid PEM bundle (just add newline!) func pemBundle(cert ...string) string { - var data string + var data strings.Builder for _, c := range cert { - data += c - data += "\n" + data.WriteString(c) + data.WriteString("\n") } - return data + return data.String() } func makeTLSSecret(data map[string][]byte) *core_v1.Secret { @@ -295,3 +297,127 @@ func makeTLSSecret(data map[string][]byte) *core_v1.Secret { func makeOpaqueSecret(data map[string][]byte) *core_v1.Secret { return &core_v1.Secret{Type: core_v1.SecretTypeOpaque, Data: data} } + +func TestValidJWKS(t *testing.T) { + type test struct { + secret *core_v1.Secret + key string + want error + wantContains string // when set, assert Contains instead of Equal on Error() + } + + var ( + errJWKSNotOpaque = errors.New(`secret type is not "Opaque"`) + errJWKSMissingJWKS = errors.New(`missing "jwks" key or empty value`) + errJWKSMissingOther = errors.New(`missing "other" key or empty value`) + errJWKEmpty = errors.New("empty JWKS") + errJWKSMustBeObject = errors.New("JWKS must be a JSON object") + errJWKSNeedKeysArray = errors.New("JWKS must contain a \"keys\" array") + errJWKSKeysNotArray = errors.New("JWKS \"keys\" must be an array") + ) + + makeTest := func(secret *core_v1.Secret, key string, want error) *test { + return &test{secret: secret, key: key, want: want} + } + + tests := map[string]*test{ + "Opaque, valid JWKS empty keys": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{"keys":[]}`)}), + "jwks", + nil, + ), + + "Opaque, missing data key": makeTest( + makeOpaqueSecret(map[string][]byte{"other": []byte("x")}), + "jwks", + errJWKSMissingJWKS, + ), + + "Opaque, empty value at key": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte("")}), + "jwks", + errJWKSMissingJWKS, + ), + + "Opaque, whitespace-only value": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(" \n\t ")}), + "jwks", + errJWKEmpty, + ), + + "Opaque, invalid JSON": { + secret: makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{not`)}), + key: "jwks", + wantContains: "not valid JSON", + }, + + "Opaque, JSON not an object": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`"string"`)}), + "jwks", + errJWKSMustBeObject, + ), + + "Opaque, empty JSON object": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{}`)}), + "jwks", + errJWKSNeedKeysArray, + ), + + "Opaque, keys field not array": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{"keys":"x"}`)}), + "jwks", + errJWKSKeysNotArray, + ), + + "Opaque, single JWK without keys array": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{"kty":"RSA","n":"abc","e":"AQAB"}`)}), + "jwks", + errJWKSNeedKeysArray, + ), + + "Opaque, custom key name": makeTest( + makeOpaqueSecret(map[string][]byte{"custom": []byte(`{"keys":[]}`)}), + "custom", + nil, + ), + + "Opaque, wrong key for existing JWKS": makeTest( + makeOpaqueSecret(map[string][]byte{"jwks": []byte(`{"keys":[]}`)}), + "other", + errJWKSMissingOther, + ), + + "TLS Secret type rejected": makeTest( + makeTLSSecret(map[string][]byte{ + core_v1.TLSCertKey: []byte(fixture.CERTIFICATE), + core_v1.TLSPrivateKeyKey: []byte(fixture.RSA_PRIVATE_KEY), + "jwks": []byte(`{"keys":[]}`), + }), + "jwks", + errJWKSNotOpaque, + ), + + "kubernetes.io/dockercfg type rejected": { + secret: &core_v1.Secret{ + Type: core_v1.SecretTypeDockercfg, + Data: map[string][]byte{ + "jwks": []byte(`{"keys":[]}`), + }, + }, + key: "jwks", + want: errJWKSNotOpaque, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := validJWKSSecret(tc.secret, tc.key) + if tc.wantContains != "" { + require.Error(t, got) + assert.Contains(t, got.Error(), tc.wantContains) + return + } + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/dag/status.go b/internal/dag/status.go index 68125647825..4bb2cd0e05a 100644 --- a/internal/dag/status.go +++ b/internal/dag/status.go @@ -15,6 +15,7 @@ package dag import ( "fmt" + "maps" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -104,9 +105,7 @@ func (osw *ObjectStatusWriter) SetValid() { // the commit function to be called to write the final status of the object. func (osw *ObjectStatusWriter) WithObject(obj meta_v1.Object) (_ *ObjectStatusWriter, commit func()) { m := make(map[string]string) - for k, v := range osw.values { - m[k] = v - } + maps.Copy(m, osw.values) nosw := &ObjectStatusWriter{ sw: osw.sw, obj: obj, diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go index e521cf1dd0f..6b816670462 100644 --- a/internal/dag/status_test.go +++ b/internal/dag/status_test.go @@ -3196,6 +3196,36 @@ func TestDAGStatus(t *testing.T) { }, }) + proxyJWTFallback := fixture.NewProxy("roots/jwt-fallback-incompat"). + WithSpec(contour_v1.HTTPProxySpec{ + VirtualHost: &contour_v1.VirtualHost{ + Fqdn: "invalid.com", + TLS: &contour_v1.TLS{ + SecretName: "ssl-cert", + EnableFallbackCertificate: true, + }, + JWTProviders: []contour_v1.JWTProvider{ + { + Name: "provider-1", + RemoteJWKS: contour_v1.RemoteJWKS{ + URI: "https://jwt.example.com/jwks.json", + }, + }, + }, + }, + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{Name: "app-server", Port: 80}}, + }}, + }) + + run(t, "fallback and JWT providers are incompatible", testcase{ + objs: []any{fixture.SecretRootsCert, proxyJWTFallback}, + want: map[types.NamespacedName]contour_v1.DetailedCondition{ + {Name: proxyJWTFallback.Name, Namespace: proxyJWTFallback.Namespace}: fixture.NewValidCondition().WithGeneration(proxyJWTFallback.Generation). + WithError(contour_v1.ConditionTypeTLSError, "TLSIncompatibleFeatures", "Spec.Virtualhost.TLS fallback & JWT providers are incompatible"), + }, + }) + proxyAuthHTTP := fixture.NewProxy("roots/http"). WithSpec(contour_v1.HTTPProxySpec{ VirtualHost: &contour_v1.VirtualHost{ @@ -3849,6 +3879,171 @@ func TestDAGStatus(t *testing.T) { }, }) + jwtVerificationJWKSBothSources := &contour_v1.HTTPProxy{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: "roots", + Name: "jwt-verification-jwks-both-sources", + }, + Spec: contour_v1.HTTPProxySpec{ + VirtualHost: &contour_v1.VirtualHost{ + Fqdn: "example.com", + TLS: &contour_v1.TLS{ + SecretName: fixture.SecretRootsCert.Name, + }, + JWTProviders: []contour_v1.JWTProvider{ + { + Name: "provider-1", + RemoteJWKS: contour_v1.RemoteJWKS{ + URI: "https://jwt.example.com/jwks.json", + }, + LocalJWKS: contour_v1.LocalJWKS{ + SecretName: fixture.SecretRootsJWKS.Name, + Key: fixture.SecretRootsJWKSKey, + }, + }, + }, + }, + Routes: []contour_v1.Route{ + { + Conditions: []contour_v1.MatchCondition{{ + Prefix: "/foo", + }}, + Services: []contour_v1.Service{{ + Name: "home", + Port: 8080, + }}, + }, + }, + }, + } + + run(t, "JWT verification both remoteJWKS and localJWKS set", testcase{ + objs: []any{ + jwtVerificationJWKSBothSources, + fixture.SecretRootsCert, + fixture.ServiceRootsHome, + }, + want: map[types.NamespacedName]contour_v1.DetailedCondition{ + k8s.NamespacedNameOf(jwtVerificationJWKSBothSources): fixture.NewValidCondition(). + WithError( + contour_v1.ConditionTypeJWTVerificationError, + "JWKSSourceConflict", + "Spec.VirtualHost.JWTProviders for provider \"provider-1\" is invalid: at most one of remoteJWKS or localJWKS may be set", + ), + }, + }) + + jwtVerificationJWKSNeitherSource := &contour_v1.HTTPProxy{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: "roots", + Name: "jwt-verification-jwks-neither-source", + }, + Spec: contour_v1.HTTPProxySpec{ + VirtualHost: &contour_v1.VirtualHost{ + Fqdn: "example.com", + TLS: &contour_v1.TLS{ + SecretName: fixture.SecretRootsCert.Name, + }, + JWTProviders: []contour_v1.JWTProvider{ + { + Name: "provider-1", + }, + }, + }, + Routes: []contour_v1.Route{ + { + Conditions: []contour_v1.MatchCondition{{ + Prefix: "/foo", + }}, + Services: []contour_v1.Service{{ + Name: "home", + Port: 8080, + }}, + }, + }, + }, + } + + run(t, "JWT verification neither remoteJWKS nor localJWKS set", testcase{ + objs: []any{ + jwtVerificationJWKSNeitherSource, + fixture.SecretRootsCert, + fixture.ServiceRootsHome, + }, + want: map[types.NamespacedName]contour_v1.DetailedCondition{ + k8s.NamespacedNameOf(jwtVerificationJWKSNeitherSource): fixture.NewValidCondition(). + WithError( + contour_v1.ConditionTypeJWTVerificationError, + "JWKSSourceMissing", + "Spec.VirtualHost.JWTProviders for provider \"provider-1\" is invalid: exactly one of remoteJWKS or localJWKS must be set", + ), + }, + }) + + localJWKSInvalidJSONSecretKey := "key" + jwtVerificationLocalJWKSInvalidJSONSecret := &core_v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: "roots", + Name: "secret-name", + }, + Type: core_v1.SecretTypeOpaque, + Data: map[string][]byte{ + localJWKSInvalidJSONSecretKey: []byte(`{not json`), + }, + } + + jwtVerificationLocalJWKSInvalidJSON := &contour_v1.HTTPProxy{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: "roots", + Name: "jwt-verification-local-jwks-invalid-json", + }, + Spec: contour_v1.HTTPProxySpec{ + VirtualHost: &contour_v1.VirtualHost{ + Fqdn: "example.com", + TLS: &contour_v1.TLS{ + SecretName: fixture.SecretRootsCert.Name, + }, + JWTProviders: []contour_v1.JWTProvider{ + { + Name: "provider-1", + LocalJWKS: contour_v1.LocalJWKS{ + SecretName: jwtVerificationLocalJWKSInvalidJSONSecret.Name, + Key: localJWKSInvalidJSONSecretKey, + }, + }, + }, + }, + Routes: []contour_v1.Route{ + { + Conditions: []contour_v1.MatchCondition{{ + Prefix: "/foo", + }}, + Services: []contour_v1.Service{{ + Name: "home", + Port: 8080, + }}, + }, + }, + }, + } + + run(t, "JWT verification local JWKS invalid JSON", testcase{ + objs: []any{ + jwtVerificationLocalJWKSInvalidJSON, + fixture.SecretRootsCert, + fixture.ServiceRootsHome, + jwtVerificationLocalJWKSInvalidJSONSecret, + }, + want: map[types.NamespacedName]contour_v1.DetailedCondition{ + k8s.NamespacedNameOf(jwtVerificationLocalJWKSInvalidJSON): fixture.NewValidCondition(). + WithError( + contour_v1.ConditionTypeJWTVerificationError, + "LocalJWKSInvalid", + "Spec.VirtualHost.JWTProviders.LocalJWKS for provider \"provider-1\" is invalid: not valid JSON: invalid character 'n' looking for beginning of object key string", + ), + }, + }) + jwtVerificationNoProvidersRouteHasRef := &contour_v1.HTTPProxy{ ObjectMeta: meta_v1.ObjectMeta{ Namespace: "roots", diff --git a/internal/debug/debug.go b/internal/debug/debug.go index 67fcfd619fc..3ab36ffe485 100644 --- a/internal/debug/debug.go +++ b/internal/debug/debug.go @@ -13,7 +13,7 @@ // Package debug provides http endpoints for healthcheck, metrics, // and pprof debugging. -package debug +package debug // nolint:revive // Ignore var-naming warning about package name conflicting with runtime/debug. import ( "context" diff --git a/internal/debug/dot.go b/internal/debug/dot.go index ed1697b1d75..b4f84ff08f3 100644 --- a/internal/debug/dot.go +++ b/internal/debug/dot.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package debug +package debug // nolint:revive // Ignore var-naming warning about package name conflicting with runtime/debug. import ( "fmt" diff --git a/internal/debug/dot_test.go b/internal/debug/dot_test.go index 08db17c80f5..284bc159a7c 100644 --- a/internal/debug/dot_test.go +++ b/internal/debug/dot_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package debug +package debug // nolint:revive // Ignore var-naming warning about package name conflicting with runtime/debug. import ( "bytes" diff --git a/internal/envoy/bootstrap.go b/internal/envoy/bootstrap.go index e1b6c84eece..b5631d5e3a4 100644 --- a/internal/envoy/bootstrap.go +++ b/internal/envoy/bootstrap.go @@ -168,7 +168,7 @@ func WriteConfig(filename string, config proto.Message) (err error) { } else { out, err = os.Create(filename) if err != nil { - return + return err } defer func() { err = out.Close() diff --git a/internal/envoy/v3/cluster.go b/internal/envoy/v3/cluster.go index afdaf6a4069..72591d67545 100644 --- a/internal/envoy/v3/cluster.go +++ b/internal/envoy/v3/cluster.go @@ -166,10 +166,10 @@ func (e *EnvoyGen) ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_clu // TODO(jpeach): Externalname service support in https://github.com/projectcontour/contour/issues/2875 - http2Version := HTTPVersionAuto + httpVersion := HTTPVersionAuto switch ext.Protocol { case "h2": - http2Version = HTTPVersion2 + httpVersion = HTTPVersion2 cluster.TransportSocket = UpstreamTLSTransportSocket( e.UpstreamTLSContext( ext.UpstreamValidation, @@ -180,13 +180,15 @@ func (e *EnvoyGen) ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_clu ), ) case "h2c": - http2Version = HTTPVersion2 + httpVersion = HTTPVersion2 + case "http/1.1": + httpVersion = HTTPVersion1 } if ext.ClusterTimeoutPolicy.ConnectTimeout > time.Duration(0) { cluster.ConnectTimeout = durationpb.New(ext.ClusterTimeoutPolicy.ConnectTimeout) } - cluster.TypedExtensionProtocolOptions = protocolOptions(http2Version, ext.ClusterTimeoutPolicy.IdleConnectionTimeout, nil) + cluster.TypedExtensionProtocolOptions = protocolOptions(httpVersion, ext.ClusterTimeoutPolicy.IdleConnectionTimeout, nil) applyCircuitBreakers(cluster, ext.CircuitBreakers) diff --git a/internal/envoy/v3/listener.go b/internal/envoy/v3/listener.go index 3a0f977de18..ce6f6b74926 100644 --- a/internal/envoy/v3/listener.go +++ b/internal/envoy/v3/listener.go @@ -15,7 +15,6 @@ package v3 import ( "errors" - "fmt" "sort" "strings" "time" @@ -43,6 +42,7 @@ import ( envoy_filter_network_http_connection_manager_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" envoy_filter_network_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" envoy_transport_socket_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/proto" @@ -67,7 +67,7 @@ const ( HTTPVersion3 HTTPVersionType = envoy_filter_network_http_connection_manager_v3.HttpConnectionManager_HTTP3 ) -// ProtoNamesForVersions returns the slice of ALPN protocol names for the give HTTP versions. +// ProtoNamesForVersions returns the slice of ALPN protocol names for the given HTTP versions. func ProtoNamesForVersions(versions ...HTTPVersionType) []string { protocols := map[HTTPVersionType]string{ HTTPVersion1: "http/1.1", @@ -116,12 +116,26 @@ func CodecForVersions(versions ...HTTPVersionType) HTTPVersionType { } } -// TLSInspector returns a new TLS inspector listener filter. +// TLSInspector returns a new TLS inspector listener filter +// with default settings (no JA3/JA4 fingerprinting). func TLSInspector() *envoy_config_listener_v3.ListenerFilter { + return TLSInspectorWithConfig(nil, nil) +} + +// TLSInspectorWithConfig returns a new TLS inspector listener filter +// with optional JA3/JA4 fingerprinting enabled. +func TLSInspectorWithConfig(enableJA3, enableJA4 *bool) *envoy_config_listener_v3.ListenerFilter { + inspector := &envoy_filter_listener_tls_inspector_v3.TlsInspector{} + if enableJA3 != nil && *enableJA3 { + inspector.EnableJa3Fingerprinting = wrapperspb.Bool(true) + } + if enableJA4 != nil && *enableJA4 { + inspector.EnableJa4Fingerprinting = wrapperspb.Bool(true) + } return &envoy_config_listener_v3.ListenerFilter{ Name: wellknown.TlsInspector, ConfigType: &envoy_config_listener_v3.ListenerFilter_TypedConfig{ - TypedConfig: protobuf.MustMarshalAny(&envoy_filter_listener_tls_inspector_v3.TlsInspector{}), + TypedConfig: protobuf.MustMarshalAny(inspector), }, } } @@ -425,9 +439,11 @@ func (b *httpConnectionManagerBuilder) DefaultFilters() *httpConnectionManagerBu Name: LuaFilterName, ConfigType: &envoy_filter_network_http_connection_manager_v3.HttpFilter_TypedConfig{ TypedConfig: protobuf.MustMarshalAny(&envoy_filter_http_lua_v3.Lua{ - DefaultSourceCode: &envoy_config_core_v3.DataSource{ - Specifier: &envoy_config_core_v3.DataSource_InlineString{ - InlineString: "-- Placeholder for per-Route or per-Cluster overrides.", + SourceCodes: map[string]*envoy_config_core_v3.DataSource{ + cookieRewriteScriptName: { + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: cookieRewriteScript, + }, }, }, }), @@ -791,28 +807,12 @@ func FilterChains(filters ...*envoy_config_listener_v3.Filter) []*envoy_config_l } } -func FilterMisdirectedRequests(fqdn string) *envoy_filter_network_http_connection_manager_v3.HttpFilter { - var target string - - // fqdn can be "*" to match all hostnames or a wildcard prefix - // e.g. "*.foo" - if strings.HasPrefix(fqdn, "*") { - // When we have a wildcard hostname, we will have already matched - // the filter chain on an SNI that falls under the wildcard so we - // retrieve that and make sure the :authority header matches. - // See: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#requestedservername - target = "request_handle:streamInfo():requestedServerName()" - } else { - // For specific hostnames we know the SNI we need to match the - // :authority header against so we can simplify the code. - target = `"` + strings.ToLower(fqdn) + `"` - } - +func FilterMisdirectedRequests() *envoy_filter_network_http_connection_manager_v3.HttpFilter { code := ` function envoy_on_request(request_handle) local headers = request_handle:headers() local host = string.lower(headers:get(":authority")) - local target = %s + local target = request_handle:streamInfo():requestedServerName() s, e = string.find(host, ":", 1, true) if s ~= nil then @@ -834,7 +834,7 @@ end TypedConfig: protobuf.MustMarshalAny(&envoy_filter_http_lua_v3.Lua{ DefaultSourceCode: &envoy_config_core_v3.DataSource{ Specifier: &envoy_config_core_v3.DataSource_InlineString{ - InlineString: fmt.Sprintf(code, target), + InlineString: code, }, }, }), @@ -907,13 +907,50 @@ func FilterExtProc(extProc *dag.ExtProc) *envoy_filter_network_http_connection_m } } +// ExternalAuthzAllowedHeaders returns the slice of StringMatcher for a given slice of HeaderNameMatchCondition. +func ExternalAuthzAllowedHeaders(allowedHeaders []dag.HeaderNameMatchCondition) []*envoy_matcher_v3.StringMatcher { + var allowedHeaderPatterns []*envoy_matcher_v3.StringMatcher + + for _, allowedHeader := range allowedHeaders { + switch allowedHeader.MatchType { + case dag.HeaderNameMatchTypeExact: + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ + Exact: allowedHeader.Value, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case dag.HeaderNameMatchTypePrefix: + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ + Prefix: allowedHeader.Value, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case dag.HeaderNameMatchTypeSuffix: + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{ + Suffix: allowedHeader.Value, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case dag.HeaderNameMatchTypeContains: + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Contains{ + Contains: allowedHeader.Value, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + } + } + + return allowedHeaderPatterns +} + // FilterExternalAuthz returns an `ext_authz` filter configured with the // requested parameters. func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envoy_filter_network_http_connection_manager_v3.HttpFilter { authConfig := envoy_filter_http_ext_authz_v3.ExtAuthz{ - Services: &envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcService{ - GrpcService: grpcService(externalAuthorization.AuthorizationService.Name, externalAuthorization.AuthorizationService.SNI, externalAuthorization.AuthorizationResponseTimeout), - }, // Pretty sure we always want this. Why have an // external auth service if it is not going to affect // routing decisions? @@ -922,11 +959,53 @@ func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envo StatusOnError: &envoy_type_v3.HttpStatus{ Code: envoy_type_v3.StatusCode_Forbidden, }, - MetadataContextNamespaces: []string{}, - IncludePeerCertificate: true, // TODO(jpeach): When we move to the Envoy v4 API, propagate the // `transport_api_version` from ExtensionServiceSpec ProtocolVersion. - TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + IncludePeerCertificate: true, + } + + switch externalAuthorization.ServiceAPIType { + case dag.AuthorizationServiceGRPC: + authConfig.Services = &envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcService{ + GrpcService: grpcService(externalAuthorization.AuthorizationService.Name, externalAuthorization.AuthorizationService.SNI, externalAuthorization.AuthorizationResponseTimeout), + } + authConfig.MetadataContextNamespaces = []string{} + + case dag.AuthorizationServiceHTTP: + extAuthzService := &envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService{ + HttpService: &envoy_filter_http_ext_authz_v3.HttpService{ + ServerUri: &envoy_config_core_v3.HttpUri{ + // Uri is required by the Envoy API but routing is determined by the Cluster field, + // so we use a dummy value here. + Uri: "http://dummy/", + HttpUpstreamType: &envoy_config_core_v3.HttpUri_Cluster{ + Cluster: externalAuthorization.AuthorizationService.Name, + }, + Timeout: httpURITimeout(externalAuthorization.AuthorizationResponseTimeout), + }, + }, + } + + if pathPrefix := externalAuthorization.HTTPPathPrefix; pathPrefix != "" { + extAuthzService.HttpService.PathPrefix = pathPrefix + } + + if len(externalAuthorization.HTTPAllowedAuthorizationHeaders) > 0 { + authConfig.AllowedHeaders = &envoy_matcher_v3.ListStringMatcher{ + Patterns: ExternalAuthzAllowedHeaders(externalAuthorization.HTTPAllowedAuthorizationHeaders), + } + } + + if len(externalAuthorization.HTTPAllowedUpstreamHeaders) > 0 { + extAuthzService.HttpService.AuthorizationResponse = &envoy_filter_http_ext_authz_v3.AuthorizationResponse{ + AllowedUpstreamHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: ExternalAuthzAllowedHeaders(externalAuthorization.HTTPAllowedUpstreamHeaders), + }, + } + } + + authConfig.Services = extAuthzService } if externalAuthorization.AuthorizationServerWithRequestBody != nil { @@ -958,16 +1037,27 @@ func FilterJWTAuthN(jwtProviders []dag.JWTProvider) *envoy_filter_network_http_c } for _, provider := range jwtProviders { - provider := provider - var cacheDuration *durationpb.Duration - if provider.RemoteJWKS.CacheDuration != nil { - cacheDuration = durationpb.New(*provider.RemoteJWKS.CacheDuration) - } - - jwtConfig.Providers[provider.Name] = &envoy_filter_http_jwt_authn_v3.JwtProvider{ + envProv := &envoy_filter_http_jwt_authn_v3.JwtProvider{ Issuer: provider.Issuer, Audiences: provider.Audiences, - JwksSourceSpecifier: &envoy_filter_http_jwt_authn_v3.JwtProvider_RemoteJwks{ + Forward: provider.ForwardJWT, + } + + switch { + case provider.LocalJWKS != nil: + envProv.JwksSourceSpecifier = &envoy_filter_http_jwt_authn_v3.JwtProvider_LocalJwks{ + LocalJwks: &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: string(provider.LocalJWKS.JWKS), + }, + }, + } + case provider.RemoteJWKS != nil: + var cacheDuration *durationpb.Duration + if provider.RemoteJWKS.CacheDuration != nil { + cacheDuration = durationpb.New(*provider.RemoteJWKS.CacheDuration) + } + envProv.JwksSourceSpecifier = &envoy_filter_http_jwt_authn_v3.JwtProvider_RemoteJwks{ RemoteJwks: &envoy_filter_http_jwt_authn_v3.RemoteJwks{ HttpUri: &envoy_config_core_v3.HttpUri{ Uri: provider.RemoteJWKS.URI, @@ -978,10 +1068,21 @@ func FilterJWTAuthN(jwtProviders []dag.JWTProvider) *envoy_filter_network_http_c }, CacheDuration: cacheDuration, }, - }, - Forward: provider.ForwardJWT, + } + default: + // Programming error: should never happen because the DAG should have rejected it. + // Fail closed by providing an empty JWKS that will reject all tokens. + envProv.JwksSourceSpecifier = &envoy_filter_http_jwt_authn_v3.JwtProvider_LocalJwks{ + LocalJwks: &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: `{"keys":[]}`, + }, + }, + } } + jwtConfig.Providers[provider.Name] = envProv + // Set up a requirement map so that per-route filter config can refer // to a requirement by name. This is nicer than specifying rules here, // because it likely results in less Envoy config overall (don't have @@ -1045,6 +1146,15 @@ func FilterChainTLSFallback(downstream *envoy_transport_socket_tls_v3.Downstream return fc } +// httpURITimeout returns a duration for the HttpUri.Timeout field. +// It returns 0 (infinite) if the timeout is not set. +func httpURITimeout(d timeout.Setting) *durationpb.Duration { + if t := envoy.Timeout(d); t != nil { + return t + } + return durationpb.New(0) +} + // grpcService returns a envoy_config_core_v3.GrpcService for the given parameters. func grpcService(clusterName, sni string, timeout timeout.Setting) *envoy_config_core_v3.GrpcService { authority := strings.ReplaceAll(clusterName, "/", ".") diff --git a/internal/envoy/v3/listener_test.go b/internal/envoy/v3/listener_test.go index 2cc564d924d..ac6433acd84 100644 --- a/internal/envoy/v3/listener_test.go +++ b/internal/envoy/v3/listener_test.go @@ -639,9 +639,11 @@ func TestHTTPConnectionManager(t *testing.T) { Name: LuaFilterName, ConfigType: &envoy_filter_network_http_connection_manager_v3.HttpFilter_TypedConfig{ TypedConfig: protobuf.MustMarshalAny(&envoy_filter_http_lua_v3.Lua{ - DefaultSourceCode: &envoy_config_core_v3.DataSource{ - Specifier: &envoy_config_core_v3.DataSource_InlineString{ - InlineString: "-- Placeholder for per-Route or per-Cluster overrides.", + SourceCodes: map[string]*envoy_config_core_v3.DataSource{ + cookieRewriteScriptName: { + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: cookieRewriteScript, + }, }, }, }), @@ -1848,9 +1850,11 @@ func TestAddFilter(t *testing.T) { Name: LuaFilterName, ConfigType: &envoy_filter_network_http_connection_manager_v3.HttpFilter_TypedConfig{ TypedConfig: protobuf.MustMarshalAny(&envoy_filter_http_lua_v3.Lua{ - DefaultSourceCode: &envoy_config_core_v3.DataSource{ - Specifier: &envoy_config_core_v3.DataSource_InlineString{ - InlineString: "-- Placeholder for per-Route or per-Cluster overrides.", + SourceCodes: map[string]*envoy_config_core_v3.DataSource{ + cookieRewriteScriptName: { + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: cookieRewriteScript, + }, }, }, }), diff --git a/internal/envoy/v3/route.go b/internal/envoy/v3/route.go index 219236ffdd5..9e9722f35b5 100644 --- a/internal/envoy/v3/route.go +++ b/internal/envoy/v3/route.go @@ -14,12 +14,10 @@ package v3 import ( - "bytes" "fmt" "net/http" "sort" "strings" - "text/template" envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_config_rbac_v3 "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" @@ -606,9 +604,14 @@ func retryPolicy(r *dag.Route) *envoy_config_route_v3.RetryPolicy { RetryOn: r.RetryPolicy.RetryOn, RetriableStatusCodes: r.RetryPolicy.RetriableStatusCodes, } - if r.RetryPolicy.NumRetries > 0 { - rp.NumRetries = wrapperspb.UInt32(r.RetryPolicy.NumRetries) - } + // HTTPProxy documents numRetries: -1 as "disable retries". The DAG + // translates -1 to 0 (internal/dag/policy.go), so the DAG value 0 + // here uniquely means "user asked for no retries". Previously we + // only set NumRetries when > 0, and Envoy then defaulted to 1 -- + // giving exactly one retry instead of none (projectcontour#5944). + // Always set NumRetries (including 0) so Envoy respects the + // documented contract. + rp.NumRetries = wrapperspb.UInt32(r.RetryPolicy.NumRetries) rp.PerTryTimeout = envoy.Timeout(r.RetryPolicy.PerTryTimeout) return rp @@ -937,53 +940,14 @@ func containsMatch(s string, ignoreCase bool) *envoy_config_route_v3.HeaderMatch } } -func cookieRewriteConfig(routePolicies, clusterPolicies []dag.CookieRewritePolicy) *anypb.Any { - // Merge route and cluster policies - mergedPolicies := map[string]dag.CookieRewritePolicy{} - for _, p := range append(routePolicies, clusterPolicies...) { - if _, ok := mergedPolicies[p.Name]; !ok { - mergedPolicies[p.Name] = p - } else { - merged := mergedPolicies[p.Name] - // Merge this policy with an existing one. - if p.Path != nil { - merged.Path = p.Path - } - if p.Domain != nil { - merged.Domain = p.Domain - } - if p.Secure != 0 { - merged.Secure = p.Secure - } - if p.SameSite != nil { - merged.SameSite = p.SameSite - } - mergedPolicies[p.Name] = merged - } - } - policies := make([]dag.CookieRewritePolicy, len(mergedPolicies)) - i := 0 - for _, p := range mergedPolicies { - policies[i] = p - i++ - } - - codeTemplate := ` +const ( + cookieRewriteScriptName = "cookie_rewrite.lua" + cookieRewriteScript = ` function envoy_on_response(response_handle) - rewrite_table = {} - - {{range $i, $p := .}} - function cookie_{{$i}}_attribute_rewrite(attributes) - response_handle:logDebug("rewriting cookie \"{{$p.Name}}\"") - - {{if $p.Path}}attributes["Path"] = "Path={{$p.Path}}"{{end}} - {{if $p.Domain}}attributes["Domain"] = "Domain={{$p.Domain}}"{{end}} - {{if $p.SameSite}}attributes["SameSite"] = "SameSite={{$p.SameSite}}"{{end}} - {{if eq $p.Secure 1}}attributes["Secure"] = nil{{end}} - {{if eq $p.Secure 2}}attributes["Secure"] = "Secure"{{end}} + local cookie_rewrite_rules = response_handle:filterContext():get("cookie_rewrite_rules") + if cookie_rewrite_rules == nil then + return end - rewrite_table["{{$p.Name}}"] = cookie_{{$i}}_attribute_rewrite - {{end}} function rewrite_cookie(original) local original_len = string.len(original) @@ -993,9 +957,8 @@ function envoy_on_response(response_handle) end local name = string.sub(original, 1, name_end - 1) - local rewrite_func = rewrite_table[name] - -- We don't have a rewrite rule for this cookie. - if rewrite_func == nil then + local attribute_overrides = cookie_rewrite_rules[name] + if attribute_overrides == nil then return original end @@ -1041,7 +1004,28 @@ function envoy_on_response(response_handle) iter = new_iter + 1 end - rewrite_func(attributes) + response_handle:logDebug(string.format("rewriting cookie %q", name)) + + local path = attribute_overrides["Path"] + if path ~= nil then + attributes["Path"] = string.format("Path=%s", path) + end + local domain = attribute_overrides["Domain"] + if domain ~= nil then + attributes["Domain"] = string.format("Domain=%s", domain) + end + local sameSite = attribute_overrides["SameSite"] + if sameSite ~= nil then + attributes["SameSite"] = string.format("SameSite=%s", sameSite) + end + local secure = attribute_overrides["Secure"] + if secure ~= nil then + if secure then + attributes["Secure"] = "Secure" + else + attributes["Secure"] = nil + end + end local rewritten = string.format("%s=%s", name, value) for k, v in next, attributes do @@ -1067,22 +1051,47 @@ function envoy_on_response(response_handle) end end end - ` +` +) + +func cookieRewriteConfig(routePolicies, clusterPolicies []dag.CookieRewritePolicy) *anypb.Any { + // Merge route and cluster policies and convert to generic map so we can + // create filter context for the Lua filter. + mergedPolicies := map[string]any{} + for _, p := range append(routePolicies, clusterPolicies...) { + merged := map[string]any{} + if _, ok := mergedPolicies[p.Name]; ok { + merged = mergedPolicies[p.Name].(map[string]any) + } + // Merge this policy with an existing one. + if p.Path != nil { + merged["Path"] = *p.Path + } + if p.Domain != nil { + merged["Domain"] = *p.Domain + } + if p.Secure != nil { + merged["Secure"] = *p.Secure + } + if p.SameSite != nil { + merged["SameSite"] = *p.SameSite + } + mergedPolicies[p.Name] = merged + } - t := new(bytes.Buffer) - if err := template.Must(template.New("code").Parse(codeTemplate)).Execute(t, policies); err != nil { - // If template execution fails, return empty filter. + filterContext, err := structpb.NewStruct(map[string]any{ + "cookie_rewrite_rules": mergedPolicies, + }) + if err != nil { + // If struct creation fails, return empty filter. return nil } c := &envoy_filter_http_lua_v3.LuaPerRoute{ - Override: &envoy_filter_http_lua_v3.LuaPerRoute_SourceCode{ - SourceCode: &envoy_config_core_v3.DataSource{ - Specifier: &envoy_config_core_v3.DataSource_InlineString{ - InlineString: t.String(), - }, - }, + Override: &envoy_filter_http_lua_v3.LuaPerRoute_Name{ + Name: cookieRewriteScriptName, }, + FilterContext: filterContext, } return protobuf.MustMarshalAny(c) } diff --git a/internal/envoy/v3/runtime.go b/internal/envoy/v3/runtime.go index 135eddab56b..0b459c4f7a7 100644 --- a/internal/envoy/v3/runtime.go +++ b/internal/envoy/v3/runtime.go @@ -14,6 +14,8 @@ package v3 import ( + "maps" + envoy_service_runtime_v3 "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3" "google.golang.org/protobuf/types/known/structpb" ) @@ -26,9 +28,7 @@ const ( func RuntimeLayers(configurableRuntimeFields map[string]*structpb.Value) []*envoy_service_runtime_v3.Runtime { baseLayer := baseRuntimeLayer() - for k, v := range configurableRuntimeFields { - baseLayer.Fields[k] = v - } + maps.Copy(baseLayer.Fields, configurableRuntimeFields) return []*envoy_service_runtime_v3.Runtime{ { Name: DynamicRuntimeLayerName, diff --git a/internal/envoy/v3/runtime_test.go b/internal/envoy/v3/runtime_test.go index 9e84d136b65..b602e87421d 100644 --- a/internal/envoy/v3/runtime_test.go +++ b/internal/envoy/v3/runtime_test.go @@ -14,6 +14,7 @@ package v3 import ( + "maps" "testing" envoy_service_runtime_v3 "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3" @@ -42,9 +43,7 @@ func TestRuntimeLayers(t *testing.T) { "re2.max_program_size.error_level": structpb.NewNumberValue(1 << 20), "re2.max_program_size.warn_level": structpb.NewNumberValue(1000), } - for k, v := range tc.configurableFields { - expectedFields[k] = v - } + maps.Copy(expectedFields, tc.configurableFields) layers := RuntimeLayers(tc.configurableFields) require.Equal(t, []*envoy_service_runtime_v3.Runtime{ { diff --git a/internal/envoy/v3/tracing.go b/internal/envoy/v3/tracing.go index b67dbe2e1ef..387a1c74bae 100644 --- a/internal/envoy/v3/tracing.go +++ b/internal/envoy/v3/tracing.go @@ -44,6 +44,12 @@ func TracingConfig(tracing *EnvoyTracingConfig) *envoy_filter_network_http_conne OverallSampling: &envoy_type_v3.Percent{ Value: tracing.OverallSampling, }, + ClientSampling: &envoy_type_v3.Percent{ + Value: tracing.ClientSampling, + }, + RandomSampling: &envoy_type_v3.Percent{ + Value: tracing.RandomSampling, + }, MaxPathTagLength: wrapperspb.UInt32(tracing.MaxPathTagLength), CustomTags: customTags, Provider: &envoy_config_trace_v3.Tracing_Http{ @@ -102,6 +108,8 @@ type EnvoyTracingConfig struct { SNI string Timeout timeout.Setting OverallSampling float64 + ClientSampling float64 + RandomSampling float64 MaxPathTagLength uint32 CustomTags []*CustomTag } diff --git a/internal/envoy/v3/tracing_test.go b/internal/envoy/v3/tracing_test.go index e02ed7d114f..4ae56bef4ca 100644 --- a/internal/envoy/v3/tracing_test.go +++ b/internal/envoy/v3/tracing_test.go @@ -47,6 +47,8 @@ func TestTracingConfig(t *testing.T) { SNI: "some-server.com", Timeout: timeout.DurationSetting(5 * time.Second), OverallSampling: 100, + ClientSampling: 100, + RandomSampling: 100, MaxPathTagLength: 256, CustomTags: []*CustomTag{ { @@ -67,6 +69,12 @@ func TestTracingConfig(t *testing.T) { OverallSampling: &envoy_type_v3.Percent{ Value: 100.0, }, + ClientSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + RandomSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, MaxPathTagLength: wrapperspb.UInt32(256), CustomTags: []*envoy_trace_v3.CustomTag{ { @@ -121,6 +129,8 @@ func TestTracingConfig(t *testing.T) { SNI: "some-server.com", Timeout: timeout.DurationSetting(5 * time.Second), OverallSampling: 100, + ClientSampling: 100, + RandomSampling: 100, MaxPathTagLength: 256, CustomTags: nil, }, @@ -128,6 +138,12 @@ func TestTracingConfig(t *testing.T) { OverallSampling: &envoy_type_v3.Percent{ Value: 100.0, }, + ClientSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + RandomSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, MaxPathTagLength: wrapperspb.UInt32(256), CustomTags: nil, Provider: &envoy_config_trace_v3.Tracing_Http{ @@ -157,6 +173,8 @@ func TestTracingConfig(t *testing.T) { SNI: "", Timeout: timeout.DurationSetting(5 * time.Second), OverallSampling: 100, + ClientSampling: 100, + RandomSampling: 100, MaxPathTagLength: 256, CustomTags: nil, }, @@ -164,6 +182,12 @@ func TestTracingConfig(t *testing.T) { OverallSampling: &envoy_type_v3.Percent{ Value: 100.0, }, + ClientSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, + RandomSampling: &envoy_type_v3.Percent{ + Value: 100.0, + }, MaxPathTagLength: wrapperspb.UInt32(256), CustomTags: nil, Provider: &envoy_config_trace_v3.Tracing_Http{ diff --git a/internal/featuretests/v3/authorization_test.go b/internal/featuretests/v3/authorization_test.go index efbb9ae21e0..13cbd43c8d0 100644 --- a/internal/featuretests/v3/authorization_test.go +++ b/internal/featuretests/v3/authorization_test.go @@ -24,6 +24,7 @@ import ( envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_filter_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3" envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "google.golang.org/protobuf/types/known/durationpb" core_v1 "k8s.io/api/core/v1" @@ -51,6 +52,20 @@ func grpcCluster(name string) *envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcServi } } +func httpCluster(name string) *envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService { + return &envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService{ + HttpService: &envoy_filter_http_ext_authz_v3.HttpService{ + ServerUri: &envoy_config_core_v3.HttpUri{ + Uri: "http://dummy/", + HttpUpstreamType: &envoy_config_core_v3.HttpUri_Cluster{ + Cluster: name, + }, + Timeout: durationpb.New(defaultResponseTimeout), + }, + }, + } +} + func authzResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "failopen.projectcontour.io" @@ -62,6 +77,7 @@ func authzResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c *Conto Namespace: "auth", Name: "extension", }, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "10m", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -120,6 +136,7 @@ func authzInvalidResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c Namespace: "auth", Name: "extension", }, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "invalid-timeout", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -147,7 +164,8 @@ func authzFailOpen(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { Namespace: "auth", Name: "extension", }, - FailOpen: true, + ServiceType: contour_v1.AuthorizationGRPCService, + FailOpen: true, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -199,6 +217,7 @@ func authzFallbackIncompat(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont Namespace: "auth", Name: "extension", }, + ServiceType: contour_v1.AuthorizationGRPCService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -230,6 +249,7 @@ func authzOverrideDisabled(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont WithCertificate("certificate"). WithAuthServer(contour_v1.AuthorizationServer{ ExtensionServiceRef: extensionRef, + ServiceType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{Disabled: false}, }). WithSpec(contour_v1.HTTPProxySpec{ @@ -340,6 +360,7 @@ func authzMergeRouteContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Con Namespace: "auth", Name: "extension", }, + ServiceType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{ Context: map[string]string{ "root-element": "root", @@ -419,7 +440,9 @@ func authzInvalidReference(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont invalid := fixture.NewProxy("proxy"). WithFQDN(fqdn). WithCertificate("certificate"). - WithAuthServer(contour_v1.AuthorizationServer{}). + WithAuthServer(contour_v1.AuthorizationServer{ + ServiceType: contour_v1.AuthorizationGRPCService, + }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ Services: []contour_v1.Service{{ @@ -509,7 +532,8 @@ func authzWithRequestBodyBufferSettings(t *testing.T, rh ResourceEventHandlerWra Namespace: "auth", Name: "extension", }, - FailOpen: true, + ServiceType: contour_v1.AuthorizationGRPCService, + FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ MaxRequestBytes: 100, AllowPartialMessage: true, @@ -562,20 +586,512 @@ func authzWithRequestBodyBufferSettings(t *testing.T, rh ResourceEventHandlerWra }).Status(p).IsValid() } +func AuthzTypeGRPC(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typegrpc.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationGRPCService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: grpcCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTP(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: httpCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationHTTPService, + HTTPServerSettings: &contour_v1.HTTPAuthorizationServerSettings{ + PathPrefix: "/auth?", + }, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + cluster := httpCluster("extension/auth/extension") + cluster.HttpService.PathPrefix = "/auth?" + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: cluster, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: one of prefix, suffix, exact or contains is required for each allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Exact: "test", Prefix: "test", IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: only one of prefix, suffix, exact, and contains should be set in the allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Prefix: "test1", IgnoreCase: false}, + {Exact: "test2", IgnoreCase: true}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: httpCluster("extension/auth/extension"), + AllowedHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: []*envoy_matcher_v3.StringMatcher{ + {MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{Prefix: "test1"}, IgnoreCase: false}, + {MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{Exact: "test2"}, IgnoreCase: true}, + }, + }, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: one of prefix, suffix, exact or contains is required for each allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Exact: "test", Prefix: "test", IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: only one of prefix, suffix, exact, and contains should be set in the allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Prefix: "test1", IgnoreCase: false}, + {Exact: "test2", IgnoreCase: true}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + cluster := httpCluster("extension/auth/extension") + cluster.HttpService.AuthorizationResponse = &envoy_filter_http_ext_authz_v3.AuthorizationResponse{ + AllowedUpstreamHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: []*envoy_matcher_v3.StringMatcher{ + {MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{Prefix: "test1"}, IgnoreCase: false}, + {MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{Exact: "test2"}, IgnoreCase: true}, + }, + }, + } + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: cluster, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceType: contour_v1.AuthorizationHTTPService, + AuthPolicy: &contour_v1.AuthorizationPolicy{ + Context: map[string]string{ + "k1": "v1", + "k2": "v2", + }, + }, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthContextForHTTP", `Spec.Virtualhost.Authorization.AuthPolicy.Context are only applied to grpc service type`) +} + +// authzTypeUnset tests backwards compatibility: an HTTPProxy with no +// serviceAPIType set (field absent in older stored objects, Go zero-value "") +// must be treated as gRPC. +func authzTypeUnset(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typegrpc.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + // ServiceAPIType intentionally absent to simulate upgrade from older version where this field didn't exist. + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: grpcCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + func TestAuthorization(t *testing.T) { subtests := map[string]func(*testing.T, ResourceEventHandlerWrapper, *Contour){ - "MissingExtension": authzInvalidReference, - "MergeRouteContext": authzMergeRouteContext, - "OverrideDisabled": authzOverrideDisabled, - "FallbackIncompat": authzFallbackIncompat, - "FailOpen": authzFailOpen, - "ResponseTimeout": authzResponseTimeout, - "InvalidResponseTimeout": authzInvalidResponseTimeout, - "AuthzWithRequestBodyBufferSettings": authzWithRequestBodyBufferSettings, + "MissingExtension": authzInvalidReference, + "MergeRouteContext": authzMergeRouteContext, + "OverrideDisabled": authzOverrideDisabled, + "FallbackIncompat": authzFallbackIncompat, + "FailOpen": authzFailOpen, + "ResponseTimeout": authzResponseTimeout, + "InvalidResponseTimeout": authzInvalidResponseTimeout, + "AuthzWithRequestBodyBufferSettings": authzWithRequestBodyBufferSettings, + "AuthzTypeGRPC": AuthzTypeGRPC, + "AuthzTypeHTTP": authzTypeHTTP, + "AuthzTypeHTTPWithPathPrefix": authzTypeHTTPWithPathPrefix, + "AuthzTypeHTTPWithAllowedAuthorizationHeaders": authzTypeHTTPWithAllowedAuthorizationHeaders, + "AuthzTypeHTTPWithAllowedUpstreamHeaders": authzTypeHTTPWithAllowedUpstreamHeaders, + "AuthzTypeHTTPWithContext": authzTypeHTTPWithContext, + "AuthzTypeUnset": authzTypeUnset, } for n, f := range subtests { - f := f t.Run(n, func(t *testing.T) { rh, c, done := setup(t) defer done() diff --git a/internal/featuretests/v3/envoy.go b/internal/featuretests/v3/envoy.go index e9b8a5995e1..5374b3dd390 100644 --- a/internal/featuretests/v3/envoy.go +++ b/internal/featuretests/v3/envoy.go @@ -249,6 +249,20 @@ func h2cCluster(c *envoy_config_cluster_v3.Cluster) *envoy_config_cluster_v3.Clu return c } +func http1Cluster(c *envoy_config_cluster_v3.Cluster) *envoy_config_cluster_v3.Cluster { + c.TypedExtensionProtocolOptions = map[string]*anypb.Any{ + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": protobuf.MustMarshalAny( + &envoy_upstream_http_v3.HttpProtocolOptions{ + UpstreamProtocolOptions: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig_{ + ExplicitHttpConfig: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig{ + ProtocolConfig: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{}, + }, + }, + }), + } + return c +} + func withConnectionTimeout(c *envoy_config_cluster_v3.Cluster, timeout time.Duration, httpVersion envoy_v3.HTTPVersionType) *envoy_config_cluster_v3.Cluster { var config *envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig @@ -313,10 +327,8 @@ func withPrefixRewrite(route *envoy_config_route_v3.Route_Route, replacement str func withRetryPolicy(route *envoy_config_route_v3.Route_Route, retryOn string, numRetries uint32, perTryTimeout time.Duration) *envoy_config_route_v3.Route_Route { route.Route.RetryPolicy = &envoy_config_route_v3.RetryPolicy{ - RetryOn: retryOn, - } - if numRetries > 0 { - route.Route.RetryPolicy.NumRetries = wrapperspb.UInt32(numRetries) + RetryOn: retryOn, + NumRetries: wrapperspb.UInt32(numRetries), } if perTryTimeout > 0 { route.Route.RetryPolicy.PerTryTimeout = durationpb.New(perTryTimeout) @@ -508,7 +520,7 @@ func httpsFilterFor(vhost string) *envoy_config_listener_v3.Filter { XDSClusterName: envoy_v3.DefaultXDSClusterName, }) return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). RouteConfigName(path.Join("https", vhost)). MetricsPrefix(xdscache_v3.ENVOY_HTTPS_LISTENER). @@ -533,7 +545,7 @@ func httpsFilterForGateway(listener, vhost string) *envoy_config_listener_v3.Fil XDSClusterName: envoy_v3.DefaultXDSClusterName, }) return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). RouteConfigName(path.Join(listener, vhost)). MetricsPrefix(listener). @@ -549,7 +561,7 @@ func httpsFilterWithXfccFor(vhost string, d *dag.ClientCertificateDetails) *envo XDSClusterName: envoy_v3.DefaultXDSClusterName, }) return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). RouteConfigName(path.Join("https", vhost)). MetricsPrefix(xdscache_v3.ENVOY_HTTPS_LISTENER). @@ -569,7 +581,7 @@ func authzFilterFor( XDSClusterName: envoy_v3.DefaultXDSClusterName, }) return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). AddFilter(&envoy_filter_network_http_connection_manager_v3.HttpFilter{ Name: envoy_v3.ExtAuthzFilterName, @@ -591,7 +603,7 @@ func jwtAuthnFilterFor( XDSClusterName: envoy_v3.DefaultXDSClusterName, }) return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). AddFilter(&envoy_filter_network_http_connection_manager_v3.HttpFilter{ Name: envoy_v3.JWTAuthnFilterName, diff --git a/internal/featuretests/v3/extensionservice_test.go b/internal/featuretests/v3/extensionservice_test.go index a82f1fd5726..930dc0d8e82 100644 --- a/internal/featuretests/v3/extensionservice_test.go +++ b/internal/featuretests/v3/extensionservice_test.go @@ -78,7 +78,7 @@ func extBasic(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { } func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { - rh.OnAdd(&contour_v1alpha1.ExtensionService{ + es := &contour_v1alpha1.ExtensionService{ ObjectMeta: fixture.ObjectMeta("ns/ext"), Spec: contour_v1alpha1.ExtensionServiceSpec{ Protocol: ptr.To("h2c"), @@ -87,7 +87,9 @@ func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { {Name: "svc2", Port: 8082}, }, }, - }) + } + + rh.OnAdd(es) c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ TypeUrl: clusterType, @@ -97,6 +99,20 @@ func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { ), ), }) + + es.Spec.Protocol = ptr.To("http/1.1") + + rh.OnDelete(es) + rh.OnAdd(es) + + c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: clusterType, + Resources: resources(t, + DefaultCluster( + http1Cluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")), + ), + ), + }) } func extUpstreamValidation(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { @@ -313,7 +329,7 @@ func extInvalidTimeout(_ *testing.T, rh ResourceEventHandlerWrapper, c *Contour) } func extInconsistentProto(_ *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { - rh.OnAdd(&contour_v1alpha1.ExtensionService{ + es := &contour_v1alpha1.ExtensionService{ ObjectMeta: fixture.ObjectMeta("ns/ext"), Spec: contour_v1alpha1.ExtensionServiceSpec{ Services: []contour_v1alpha1.ExtensionServiceTarget{ @@ -325,8 +341,20 @@ func extInconsistentProto(_ *testing.T, rh ResourceEventHandlerWrapper, c *Conto SubjectName: "ext.projectcontour.io", }, }, + } + + rh.OnAdd(es) + + // Should have no clusters because Protocol and UpstreamValidation is inconsistent. + c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: clusterType, }) + es.Spec.Protocol = ptr.To("h1") + + rh.OnDelete(es) + rh.OnAdd(es) + // Should have no clusters because Protocol and UpstreamValidation is inconsistent. c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ TypeUrl: clusterType, @@ -710,7 +738,6 @@ func TestExtensionService(t *testing.T) { } for n, f := range subtests { - f := f t.Run(n, func(t *testing.T) { var ( rh ResourceEventHandlerWrapper diff --git a/internal/featuretests/v3/global_authorization_test.go b/internal/featuretests/v3/global_authorization_test.go index c2b35f58cae..cb405773fef 100644 --- a/internal/featuretests/v3/global_authorization_test.go +++ b/internal/featuretests/v3/global_authorization_test.go @@ -47,6 +47,7 @@ var ( Name: "extension", Namespace: "auth", }, + ServiceType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -62,6 +63,7 @@ var ( Name: "extension", Namespace: "auth", }, + ServiceType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -580,6 +582,7 @@ func globalExternalAuthorizationWithTLSAuthOverride(t *testing.T, rh ResourceEve Namespace: "auth", Name: "extension", }, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: defaultResponseTimeout.String(), FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ @@ -800,7 +803,10 @@ func TestGlobalAuthorization(t *testing.T) { ExtensionService: k8s.NamespacedNameFrom("auth/extension"), Timeout: timeout.DurationSetting(defaultResponseTimeout), }, - FailOpen: false, + ExternalAuthorization: dag.ExternalAuthorization{ + ServiceAPIType: dag.AuthorizationServiceGRPC, + AuthorizationResponseTimeout: timeout.DurationSetting(defaultResponseTimeout), + }, Context: map[string]string{ "header_type": "root_config", "header_1": "message_1", diff --git a/internal/featuretests/v3/globalratelimit_test.go b/internal/featuretests/v3/globalratelimit_test.go index 2374da0ce39..4994f77d107 100644 --- a/internal/featuretests/v3/globalratelimit_test.go +++ b/internal/featuretests/v3/globalratelimit_test.go @@ -794,7 +794,6 @@ func TestGlobalRateLimiting(t *testing.T) { } for n, f := range subtests { - f := f t.Run(n, func(t *testing.T) { rh, c, done := setup(t, func(cfg *xdscache_v3.ListenerConfig) { diff --git a/internal/featuretests/v3/jwtverification_test.go b/internal/featuretests/v3/jwtverification_test.go index 6c91d08e62a..bed7326496c 100644 --- a/internal/featuretests/v3/jwtverification_test.go +++ b/internal/featuretests/v3/jwtverification_test.go @@ -30,6 +30,7 @@ import ( "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" core_v1 "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" @@ -1761,3 +1762,97 @@ func TestJWTVerification_Inclusion(t *testing.T) { ), }) } + +func TestJWTVerificationLocalJWKS(t *testing.T) { + rh, c, done := setup(t) + defer done() + + sec1 := featuretests.TLSSecret(t, "secret", &featuretests.ServerCertificate) + rh.OnAdd(sec1) + + s1 := fixture.NewService("s1"). + WithPorts(core_v1.ServicePort{Name: "http", Port: 80}) + rh.OnAdd(s1) + + const jwksJSON = `{"keys":[]}` + + jwksSecret := &core_v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: core_v1.NamespaceDefault, + Name: "jwks-secret", + }, + Type: core_v1.SecretTypeOpaque, + Data: map[string][]byte{ + "jwks.json": []byte(jwksJSON), + }, + } + rh.OnAdd(jwksSecret) + + proxy := fixture.NewProxy("local-jwks").WithSpec( + contour_v1.HTTPProxySpec{ + VirtualHost: &contour_v1.VirtualHost{ + Fqdn: "jwt-local.example.com", + TLS: &contour_v1.TLS{ + SecretName: "secret", + }, + JWTProviders: []contour_v1.JWTProvider{ + { + Name: "provider-local", + Issuer: "issuer.local.example.com", + LocalJWKS: contour_v1.LocalJWKS{ + SecretName: "jwks-secret", + Key: "jwks.json", + }, + }, + }, + }, + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: s1.Name, + Port: 80, + }}, + JWTVerificationPolicy: &contour_v1.JWTVerificationPolicy{Require: "provider-local"}, + }}, + }) + + rh.OnAdd(proxy) + + c.Request(listenerType, "ingress_https").Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: appendFilterChains( + filterchaintls("jwt-local.example.com", sec1, + jwtAuthnFilterFor("jwt-local.example.com", &envoy_filter_http_jwt_authn_v3.JwtAuthentication{ + Providers: map[string]*envoy_filter_http_jwt_authn_v3.JwtProvider{ + "provider-local": { + Issuer: "issuer.local.example.com", + JwksSourceSpecifier: &envoy_filter_http_jwt_authn_v3.JwtProvider_LocalJwks{ + LocalJwks: &envoy_config_core_v3.DataSource{ + Specifier: &envoy_config_core_v3.DataSource_InlineString{ + InlineString: jwksJSON, + }, + }, + }, + }, + }, + RequirementMap: map[string]*envoy_filter_http_jwt_authn_v3.JwtRequirement{ + "provider-local": { + RequiresType: &envoy_filter_http_jwt_authn_v3.JwtRequirement_ProviderName{ + ProviderName: "provider-local", + }, + }, + }, + }), + nil, "h2", "http/1.1"), + ), + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + ), + }) +} diff --git a/internal/featuretests/v3/listeners_test.go b/internal/featuretests/v3/listeners_test.go index dc6c0a620f1..f9cd0149bc4 100644 --- a/internal/featuretests/v3/listeners_test.go +++ b/internal/featuretests/v3/listeners_test.go @@ -880,7 +880,7 @@ func TestLDSCustomAccessLogPaths(t *testing.T) { FilterChains: []*envoy_config_listener_v3.FilterChain{ filterchaintls("kuard.example.com", s1, envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("kuard.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). RouteConfigName("https/kuard.example.com"). MetricsPrefix(xdscache_v3.ENVOY_HTTPS_LISTENER). @@ -1644,3 +1644,240 @@ func TestSocketOptions(t *testing.T) { TypeUrl: listenerType, }) } + +func TestJA3JA4Fingerprinting(t *testing.T) { + // Test JA3 only. + t.Run("ja3 only", func(t *testing.T) { + rh, c, done := setup(t, func(conf *xdscache_v3.ListenerConfig) { + conf.EnableJA3Fingerprinting = ptr.To(true) + }) + defer done() + + s1 := featuretests.TLSSecret(t, "secret", &featuretests.ServerCertificate) + svc1 := fixture.NewService("backend"). + WithPorts(core_v1.ServicePort{Name: "http", Port: 80}) + + i1 := &networking_v1.Ingress{ + ObjectMeta: fixture.ObjectMeta("simple"), + Spec: networking_v1.IngressSpec{ + TLS: []networking_v1.IngressTLS{{ + Hosts: []string{"kuard.example.com"}, + SecretName: "secret", + }}, + Rules: []networking_v1.IngressRule{{ + Host: "kuard.example.com", + IngressRuleValue: networking_v1.IngressRuleValue{ + HTTP: &networking_v1.HTTPIngressRuleValue{ + Paths: []networking_v1.HTTPIngressPath{{ + Backend: *featuretests.IngressBackend(svc1), + }}, + }, + }, + }}, + }, + } + + rh.OnAdd(s1) + rh.OnAdd(svc1) + rh.OnAdd(i1) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspectorWithConfig(ptr.To(true), nil), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls("kuard.example.com", s1, + httpsFilterFor("kuard.example.com"), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener(), + ), + TypeUrl: listenerType, + }) + }) + + // Test JA4 only. + t.Run("ja4 only", func(t *testing.T) { + rh, c, done := setup(t, func(conf *xdscache_v3.ListenerConfig) { + conf.EnableJA4Fingerprinting = ptr.To(true) + }) + defer done() + + s1 := featuretests.TLSSecret(t, "secret", &featuretests.ServerCertificate) + svc1 := fixture.NewService("backend"). + WithPorts(core_v1.ServicePort{Name: "http", Port: 80}) + + i1 := &networking_v1.Ingress{ + ObjectMeta: fixture.ObjectMeta("simple"), + Spec: networking_v1.IngressSpec{ + TLS: []networking_v1.IngressTLS{{ + Hosts: []string{"kuard.example.com"}, + SecretName: "secret", + }}, + Rules: []networking_v1.IngressRule{{ + Host: "kuard.example.com", + IngressRuleValue: networking_v1.IngressRuleValue{ + HTTP: &networking_v1.HTTPIngressRuleValue{ + Paths: []networking_v1.HTTPIngressPath{{ + Backend: *featuretests.IngressBackend(svc1), + }}, + }, + }, + }}, + }, + } + + rh.OnAdd(s1) + rh.OnAdd(svc1) + rh.OnAdd(i1) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspectorWithConfig(nil, ptr.To(true)), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls("kuard.example.com", s1, + httpsFilterFor("kuard.example.com"), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener(), + ), + TypeUrl: listenerType, + }) + }) + + // Test both JA3 and JA4. + t.Run("ja3 and ja4", func(t *testing.T) { + rh, c, done := setup(t, func(conf *xdscache_v3.ListenerConfig) { + conf.EnableJA3Fingerprinting = ptr.To(true) + conf.EnableJA4Fingerprinting = ptr.To(true) + }) + defer done() + + s1 := featuretests.TLSSecret(t, "secret", &featuretests.ServerCertificate) + svc1 := fixture.NewService("backend"). + WithPorts(core_v1.ServicePort{Name: "http", Port: 80}) + + i1 := &networking_v1.Ingress{ + ObjectMeta: fixture.ObjectMeta("simple"), + Spec: networking_v1.IngressSpec{ + TLS: []networking_v1.IngressTLS{{ + Hosts: []string{"kuard.example.com"}, + SecretName: "secret", + }}, + Rules: []networking_v1.IngressRule{{ + Host: "kuard.example.com", + IngressRuleValue: networking_v1.IngressRuleValue{ + HTTP: &networking_v1.HTTPIngressRuleValue{ + Paths: []networking_v1.HTTPIngressPath{{ + Backend: *featuretests.IngressBackend(svc1), + }}, + }, + }, + }}, + }, + } + + rh.OnAdd(s1) + rh.OnAdd(svc1) + rh.OnAdd(i1) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspectorWithConfig(ptr.To(true), ptr.To(true)), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls("kuard.example.com", s1, + httpsFilterFor("kuard.example.com"), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener(), + ), + TypeUrl: listenerType, + }) + }) + + // Test JA3 and JA4 with PROXY protocol. + t.Run("ja3 and ja4 with proxy protocol", func(t *testing.T) { + rh, c, done := setup(t, func(conf *xdscache_v3.ListenerConfig) { + conf.UseProxyProto = true + conf.EnableJA3Fingerprinting = ptr.To(true) + conf.EnableJA4Fingerprinting = ptr.To(true) + }) + defer done() + + s1 := featuretests.TLSSecret(t, "secret", &featuretests.ServerCertificate) + svc1 := fixture.NewService("backend"). + WithPorts(core_v1.ServicePort{Name: "http", Port: 80}) + + i1 := &networking_v1.Ingress{ + ObjectMeta: fixture.ObjectMeta("simple"), + Spec: networking_v1.IngressSpec{ + TLS: []networking_v1.IngressTLS{{ + Hosts: []string{"kuard.example.com"}, + SecretName: "secret", + }}, + Rules: []networking_v1.IngressRule{{ + Host: "kuard.example.com", + IngressRuleValue: networking_v1.IngressRuleValue{ + HTTP: &networking_v1.HTTPIngressRuleValue{ + Paths: []networking_v1.HTTPIngressPath{{ + Backend: *featuretests.IngressBackend(svc1), + }}, + }, + }, + }}, + }, + } + + rh.OnAdd(s1) + rh.OnAdd(svc1) + rh.OnAdd(i1) + + httpListener := defaultHTTPListener() + httpListener.ListenerFilters = envoy_v3.ListenerFilters(envoy_v3.ProxyProtocol()) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + Resources: resources(t, + httpListener, + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.ProxyProtocol(), + envoy_v3.TLSInspectorWithConfig(ptr.To(true), ptr.To(true)), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls("kuard.example.com", s1, + httpsFilterFor("kuard.example.com"), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener(), + ), + TypeUrl: listenerType, + }) + }) +} diff --git a/internal/featuretests/v3/localratelimit_test.go b/internal/featuretests/v3/localratelimit_test.go index 9469aeb7736..75f773d7c4f 100644 --- a/internal/featuretests/v3/localratelimit_test.go +++ b/internal/featuretests/v3/localratelimit_test.go @@ -631,7 +631,6 @@ func TestLocalRateLimiting(t *testing.T) { } for n, f := range subtests { - f := f t.Run(n, func(t *testing.T) { rh, c, done := setup(t) defer done() diff --git a/internal/featuretests/v3/tracing_test.go b/internal/featuretests/v3/tracing_test.go index 6a9baa88999..783ebd09273 100644 --- a/internal/featuretests/v3/tracing_test.go +++ b/internal/featuretests/v3/tracing_test.go @@ -39,6 +39,8 @@ func TestTracing(t *testing.T) { }, ServiceName: "contour", OverallSampling: 100, + ClientSampling: 100, + RandomSampling: 100, MaxPathTagLength: 256, CustomTags: []*xdscache_v3.CustomTag{ { @@ -128,6 +130,8 @@ func TestTracing(t *testing.T) { ServiceName: tracingConfig.ServiceName, Timeout: tracingConfig.Timeout, OverallSampling: tracingConfig.OverallSampling, + ClientSampling: tracingConfig.ClientSampling, + RandomSampling: tracingConfig.RandomSampling, MaxPathTagLength: tracingConfig.MaxPathTagLength, CustomTags: []*envoy_v3.CustomTag{ { diff --git a/internal/fixture/secret_fixtures.go b/internal/fixture/secret_fixtures.go index f02595f838c..cb1ce3b63b8 100644 --- a/internal/fixture/secret_fixtures.go +++ b/internal/fixture/secret_fixtures.go @@ -40,3 +40,13 @@ var SecretRootsFallback = &core_v1.Secret{ core_v1.TLSPrivateKeyKey: []byte(RSA_PRIVATE_KEY), }, } + +var SecretRootsJWKSKey = "jwks" + +var SecretRootsJWKS = &core_v1.Secret{ + ObjectMeta: ObjectMeta("roots/jwks"), + Type: core_v1.SecretTypeOpaque, + Data: map[string][]byte{ + SecretRootsJWKSKey: []byte(`{"keys":[{"kty":"RSA","n":"abc","e":"AQAB"}]}`), + }, +} diff --git a/internal/ingressclass/ingressclass.go b/internal/ingressclass/ingressclass.go index 5ca3d448a85..9e9e6f82213 100644 --- a/internal/ingressclass/ingressclass.go +++ b/internal/ingressclass/ingressclass.go @@ -14,6 +14,8 @@ package ingressclass import ( + "slices" + networking_v1 "k8s.io/api/networking/v1" "k8s.io/utils/ptr" @@ -56,10 +58,5 @@ func matches(objIngressClass string, contourIngressClasses []string) bool { } // Otherwise, the object's ingress class must match one of Contour's. - for _, contourIngressClass := range contourIngressClasses { - if objIngressClass == contourIngressClass { - return true - } - } - return false + return slices.Contains(contourIngressClasses, objIngressClass) } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index 0f740e17922..f248686247c 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -12,7 +12,7 @@ // limitations under the License. // Package metrics provides Prometheus metrics for Contour. -package metrics +package metrics // nolint:revive // Ignore var-naming warning about package name conflicting with stdlib. import ( "net/http" diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index eb7db51e8cd..23feabd1677 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package metrics +package metrics // nolint:revive // Ignore var-naming warning about package name conflicting with stdlib. import ( "testing" diff --git a/internal/provisioner/controller/gateway.go b/internal/provisioner/controller/gateway.go index d5c1b990ec6..0f5b5f0ad6b 100644 --- a/internal/provisioner/controller/gateway.go +++ b/internal/provisioner/controller/gateway.go @@ -16,6 +16,7 @@ package controller import ( "context" "fmt" + "maps" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" @@ -216,9 +217,7 @@ func (r *gatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // if there is a same name pair, overwrite it // nolint:staticcheck - for k, v := range gatewayClassParams.Spec.ResourceLabels { - contourModel.Spec.ResourceLabels[k] = v - } + maps.Copy(contourModel.Spec.ResourceLabels, gatewayClassParams.Spec.ResourceLabels) if gatewayClassParams.Spec.Contour != nil { contourParams := gatewayClassParams.Spec.Contour @@ -259,14 +258,11 @@ func (r *gatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct contourModel.Spec.ContourDeploymentStrategy = *contourParams.Deployment.Strategy } - for k, v := range contourParams.PodAnnotations { - contourModel.Spec.ContourPodAnnotations[k] = v - } + maps.Copy(contourModel.Spec.ContourPodAnnotations, contourParams.PodAnnotations) if contourParams.CertLifetime > 0 { contourModel.Spec.CertLifetime = contourParams.CertLifetime } - } if gatewayClassParams.Spec.Envoy != nil { @@ -335,9 +331,7 @@ func (r *gatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct contourModel.Spec.EnvoyExtraVolumes = append(contourModel.Spec.EnvoyExtraVolumes, envoyParams.ExtraVolumes...) // Pod Annotations - for k, v := range envoyParams.PodAnnotations { - contourModel.Spec.EnvoyPodAnnotations[k] = v - } + maps.Copy(contourModel.Spec.EnvoyPodAnnotations, envoyParams.PodAnnotations) // Pod Labels for k, v := range envoyParams.PodLabels { diff --git a/internal/provisioner/controller/gateway_test.go b/internal/provisioner/controller/gateway_test.go index 6062f9fc7a7..c9f420e0e03 100644 --- a/internal/provisioner/controller/gateway_test.go +++ b/internal/provisioner/controller/gateway_test.go @@ -899,7 +899,7 @@ func TestGatewayReconcile(t *testing.T) { Envoy: &contour_v1alpha1.EnvoySettings{ NetworkPublishing: &contour_v1alpha1.NetworkPublishing{ Type: contour_v1alpha1.NodePortServicePublishingType, - ExternalTrafficPolicy: core_v1.ServiceExternalTrafficPolicyTypeCluster, + ExternalTrafficPolicy: core_v1.ServiceExternalTrafficPolicyCluster, IPFamilyPolicy: core_v1.IPFamilyPolicyPreferDualStack, ServiceAnnotations: map[string]string{ "key-1": "val-1", @@ -951,7 +951,7 @@ func TestGatewayReconcile(t *testing.T) { }, } require.NoError(t, r.client.Get(context.Background(), keyFor(svc), svc)) - assert.Equal(t, core_v1.ServiceExternalTrafficPolicyTypeCluster, svc.Spec.ExternalTrafficPolicy) + assert.Equal(t, core_v1.ServiceExternalTrafficPolicyCluster, svc.Spec.ExternalTrafficPolicy) assert.Equal(t, ptr.To(core_v1.IPFamilyPolicyPreferDualStack), svc.Spec.IPFamilyPolicy) assert.Equal(t, core_v1.ServiceTypeNodePort, svc.Spec.Type) require.Len(t, svc.Annotations, 2) diff --git a/internal/provisioner/controller/gatewayclass.go b/internal/provisioner/controller/gatewayclass.go index a7c45abc7ce..a6d27fe751c 100644 --- a/internal/provisioner/controller/gatewayclass.go +++ b/internal/provisioner/controller/gatewayclass.go @@ -199,7 +199,7 @@ func (r *gatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request } switch params.Spec.Envoy.NetworkPublishing.ExternalTrafficPolicy { - case "", core_v1.ServiceExternalTrafficPolicyTypeCluster, core_v1.ServiceExternalTrafficPolicyTypeLocal: + case "", core_v1.ServiceExternalTrafficPolicyCluster, core_v1.ServiceExternalTrafficPolicyLocal: default: msg := fmt.Sprintf("invalid ContourDeployment spec.envoy.networkPublishing.externalTrafficPolicy %q, must be Local or Cluster", params.Spec.Envoy.NetworkPublishing.ExternalTrafficPolicy) diff --git a/internal/provisioner/equality/equality_test.go b/internal/provisioner/equality/equality_test.go index a6ba0641b7b..ccc1c48222e 100644 --- a/internal/provisioner/equality/equality_test.go +++ b/internal/provisioner/equality/equality_test.go @@ -434,7 +434,7 @@ func TestLoadBalancerServiceChanged(t *testing.T) { { description: "if external traffic policy changed", mutate: func(svc *core_v1.Service) { - svc.Spec.ExternalTrafficPolicy = core_v1.ServiceExternalTrafficPolicyTypeCluster + svc.Spec.ExternalTrafficPolicy = core_v1.ServiceExternalTrafficPolicyCluster }, expect: true, }, diff --git a/internal/provisioner/model/model.go b/internal/provisioner/model/model.go index 5f2e18c9c18..4c8ecbb15a8 100644 --- a/internal/provisioner/model/model.go +++ b/internal/provisioner/model/model.go @@ -54,7 +54,7 @@ func Default(namespace, name string) *Contour { NetworkPublishing: NetworkPublishing{ Envoy: EnvoyNetworkPublishing{ Type: LoadBalancerServicePublishingType, - ExternalTrafficPolicy: core_v1.ServiceExternalTrafficPolicyTypeLocal, + ExternalTrafficPolicy: core_v1.ServiceExternalTrafficPolicyLocal, IPFamilyPolicy: core_v1.IPFamilyPolicySingleStack, }, }, @@ -454,7 +454,7 @@ type EnvoyNetworkPublishing struct { // and LoadBalancer IPs). // // If unset, defaults to "Local". - ExternalTrafficPolicy core_v1.ServiceExternalTrafficPolicyType + ExternalTrafficPolicy core_v1.ServiceExternalTrafficPolicy } type NetworkPublishingType = contour_v1alpha1.NetworkPublishingType diff --git a/internal/provisioner/model/names.go b/internal/provisioner/model/names.go index b35df86de4a..bdcb6e2670a 100644 --- a/internal/provisioner/model/names.go +++ b/internal/provisioner/model/names.go @@ -15,6 +15,7 @@ package model import ( "fmt" + "maps" ) // ContourConfigurationName returns the name of the ContourConfiguration resource. @@ -85,9 +86,7 @@ func (c *Contour) EnvoyRBACNames() RBACNames { // workloads (i.e. deployment(s)/daemonset). func (c *Contour) WorkloadLabels() map[string]string { labels := map[string]string{} - for k, v := range c.CommonLabels() { - labels[k] = v - } + maps.Copy(labels, c.CommonLabels()) for k, v := range c.AppPredefinedLabels() { labels[k] = v @@ -114,14 +113,10 @@ func (c *Contour) CommonLabels() map[string]string { labels := map[string]string{} // Add user-defined labels - for k, v := range c.Spec.ResourceLabels { - labels[k] = v - } + maps.Copy(labels, c.Spec.ResourceLabels) // Add owner labels - for k, v := range OwnerLabels(c) { - labels[k] = v - } + maps.Copy(labels, OwnerLabels(c)) return labels } @@ -131,9 +126,7 @@ func (c *Contour) CommonLabels() map[string]string { func (c *Contour) CommonAnnotations() map[string]string { annotations := map[string]string{} - for k, v := range c.Spec.ResourceAnnotations { - annotations[k] = v - } + maps.Copy(annotations, c.Spec.ResourceAnnotations) return annotations } diff --git a/internal/provisioner/objects/dataplane/dataplane.go b/internal/provisioner/objects/dataplane/dataplane.go index 9dcee1efa47..9e2927dca58 100644 --- a/internal/provisioner/objects/dataplane/dataplane.go +++ b/internal/provisioner/objects/dataplane/dataplane.go @@ -16,6 +16,7 @@ package dataplane import ( "context" "fmt" + "maps" "path/filepath" apps_v1 "k8s.io/api/apps/v1" @@ -61,7 +62,9 @@ const ( xdsResourceVersion = "v3" ) -// the default resource requirements for container: envoy-initconfig & shutdown-manager, the default value is come from: +// the default resource requirements for container: envoy-initconfig & shutdown-manager. +// CPU limit is generous to avoid throttling. +// See: https://github.com/projectcontour/contour/issues/7366 // ref: https://projectcontour.io/docs/1.25/deploy-options/#setting-resource-requests-and-limits var defContainerResources = core_v1.ResourceRequirements{ Requests: core_v1.ResourceList{ @@ -69,7 +72,7 @@ var defContainerResources = core_v1.ResourceRequirements{ core_v1.ResourceMemory: resource.MustParse("50Mi"), }, Limits: core_v1.ResourceList{ - core_v1.ResourceCPU: resource.MustParse("50m"), + core_v1.ResourceCPU: resource.MustParse("200m"), core_v1.ResourceMemory: resource.MustParse("100Mi"), }, } @@ -536,31 +539,20 @@ func EnvoyPodSelector(contour *model.Contour) *meta_v1.LabelSelector { // envoyPodLabels returns the labels for envoy's pods func envoyPodLabels(contour *model.Contour) map[string]string { labels := EnvoyPodSelector(contour).MatchLabels - for k, v := range contour.WorkloadLabels() { - labels[k] = v - } - for k, v := range contour.Spec.EnvoyPodLabels { - labels[k] = v - } - for k, v := range contour.AppPredefinedLabels() { - labels[k] = v - } - + maps.Copy(labels, contour.WorkloadLabels()) + maps.Copy(labels, contour.Spec.EnvoyPodLabels) + maps.Copy(labels, contour.AppPredefinedLabels()) return labels } // envoyPodAnnotations returns the annotations for envoy's pods func envoyPodAnnotations(contour *model.Contour) map[string]string { annotations := map[string]string{} - for k, v := range contour.Spec.EnvoyPodAnnotations { - annotations[k] = v - } + maps.Copy(annotations, contour.Spec.EnvoyPodAnnotations) // Annotations specified on the Gateway take precedence // over annotations specified on the GatewayClass/its parameters. - for k, v := range contour.CommonAnnotations() { - annotations[k] = v - } + maps.Copy(annotations, contour.CommonAnnotations()) return annotations } diff --git a/internal/provisioner/objects/dataplane/dataplane_test.go b/internal/provisioner/objects/dataplane/dataplane_test.go index 13b4a7f4418..37bee11f629 100644 --- a/internal/provisioner/objects/dataplane/dataplane_test.go +++ b/internal/provisioner/objects/dataplane/dataplane_test.go @@ -15,6 +15,7 @@ package dataplane import ( "fmt" + "slices" "testing" "github.com/stretchr/testify/require" @@ -240,10 +241,8 @@ func checkDaemonSecurityContext(t *testing.T, ds *apps_v1.DaemonSet) { func checkContainerHasArg(t *testing.T, container *core_v1.Container, arg string) { t.Helper() - for _, a := range container.Args { - if a == arg { - return - } + if slices.Contains(container.Args, arg) { + return } t.Errorf("container is missing argument %q", arg) } diff --git a/internal/provisioner/objects/deployment/deployment.go b/internal/provisioner/objects/deployment/deployment.go index 15a89ad6abe..787d8b6c237 100644 --- a/internal/provisioner/objects/deployment/deployment.go +++ b/internal/provisioner/objects/deployment/deployment.go @@ -16,6 +16,7 @@ package deployment import ( "context" "fmt" + "maps" "path/filepath" "slices" "strings" @@ -307,24 +308,18 @@ func ContourDeploymentPodSelector(contour *model.Contour) *meta_v1.LabelSelector // app & pod labels func contourPodLabels(contour *model.Contour) map[string]string { labels := ContourDeploymentPodSelector(contour).MatchLabels - for k, v := range contour.WorkloadLabels() { - labels[k] = v - } + maps.Copy(labels, contour.WorkloadLabels()) return labels } // contourPodAnnotations returns the annotations for contour's pods func contourPodAnnotations(contour *model.Contour) map[string]string { annotations := map[string]string{} - for k, v := range contour.Spec.ContourPodAnnotations { - annotations[k] = v - } + maps.Copy(annotations, contour.Spec.ContourPodAnnotations) // Annotations specified on the Gateway take precedence // over annotations specified on the GatewayClass/its parameters. - for k, v := range contour.CommonAnnotations() { - annotations[k] = v - } + maps.Copy(annotations, contour.CommonAnnotations()) return annotations } diff --git a/internal/provisioner/objects/deployment/deployment_test.go b/internal/provisioner/objects/deployment/deployment_test.go index 0e46c8289fe..78621c24883 100644 --- a/internal/provisioner/objects/deployment/deployment_test.go +++ b/internal/provisioner/objects/deployment/deployment_test.go @@ -15,6 +15,7 @@ package deployment import ( "fmt" + "slices" "strings" "testing" @@ -81,10 +82,8 @@ func checkPodHasAnnotation(t *testing.T, tmpl core_v1.PodTemplateSpec, key, valu func checkContainerHasArg(t *testing.T, container *core_v1.Container, arg string) { t.Helper() - for _, a := range container.Args { - if a == arg { - return - } + if slices.Contains(container.Args, arg) { + return } t.Errorf("container is missing argument %q", arg) } diff --git a/internal/provisioner/objects/rbac/clusterrole/cluster_role_test.go b/internal/provisioner/objects/rbac/clusterrole/cluster_role_test.go index a6176830d50..7f19d77559c 100644 --- a/internal/provisioner/objects/rbac/clusterrole/cluster_role_test.go +++ b/internal/provisioner/objects/rbac/clusterrole/cluster_role_test.go @@ -113,11 +113,8 @@ func TestDesiredClusterRoleFilterResources(t *testing.T) { filterContourResources := func(policyRules []rbac_v1.PolicyRule) [][]string { contourResources := [][]string{} for _, rule := range policyRules { - for _, apigroup := range rule.APIGroups { - if apigroup == contour_v1.GroupName { - contourResources = append(contourResources, rule.Resources) - break - } + if slices.Contains(rule.APIGroups, contour_v1.GroupName) { + contourResources = append(contourResources, rule.Resources) } } return contourResources diff --git a/internal/provisioner/objects/rbac/role/role_test.go b/internal/provisioner/objects/rbac/role/role_test.go index 9a6b462fe1a..82c2558718b 100644 --- a/internal/provisioner/objects/rbac/role/role_test.go +++ b/internal/provisioner/objects/rbac/role/role_test.go @@ -15,6 +15,7 @@ package role import ( "fmt" + "slices" "testing" rbac_v1 "k8s.io/api/rbac/v1" @@ -105,11 +106,8 @@ func TestDesiredRoleFilterResources(t *testing.T) { filterNamespacedGatewayResources := func(policyRules []rbac_v1.PolicyRule) [][]string { gatewayResources := [][]string{} for _, rule := range policyRules { - for _, apigroup := range rule.APIGroups { - if apigroup == gatewayapi_v1.GroupName { - gatewayResources = append(gatewayResources, rule.Resources) - break - } + if slices.Contains(rule.APIGroups, gatewayapi_v1.GroupName) { + gatewayResources = append(gatewayResources, rule.Resources) } } return gatewayResources @@ -118,11 +116,8 @@ func TestDesiredRoleFilterResources(t *testing.T) { filterContourResources := func(policyRules []rbac_v1.PolicyRule) [][]string { contourResources := [][]string{} for _, rule := range policyRules { - for _, apigroup := range rule.APIGroups { - if apigroup == contour_v1.GroupName { - contourResources = append(contourResources, rule.Resources) - break - } + if slices.Contains(rule.APIGroups, contour_v1.GroupName) { + contourResources = append(contourResources, rule.Resources) } } return contourResources diff --git a/internal/provisioner/objects/secret/secret.go b/internal/provisioner/objects/secret/secret.go index 5a458417506..393c04f001f 100644 --- a/internal/provisioner/objects/secret/secret.go +++ b/internal/provisioner/objects/secret/secret.go @@ -16,6 +16,7 @@ package secret import ( "context" "fmt" + "maps" "strings" core_v1 "k8s.io/api/core/v1" @@ -62,9 +63,7 @@ func EnsureXDSSecrets(ctx context.Context, cli client.Client, contour *model.Con if secret.Labels == nil { secret.Labels = contour.CommonLabels() } else { - for k, v := range contour.CommonLabels() { - secret.Labels[k] = v - } + maps.Copy(secret.Labels, contour.CommonLabels()) } // Add annotation indicating the version the secret was @@ -75,9 +74,7 @@ func EnsureXDSSecrets(ctx context.Context, cli client.Client, contour *model.Con } secret.Annotations[generatedByVersionAnnotation] = tagFromImage(image) - for k, v := range contour.CommonAnnotations() { - secret.Annotations[k] = v - } + maps.Copy(secret.Annotations, contour.CommonAnnotations()) if err := cli.Create(ctx, secret); err != nil { if !errors.IsAlreadyExists(err) { diff --git a/internal/provisioner/objects/service/service.go b/internal/provisioner/objects/service/service.go index 08530c8e337..bebdf931d0e 100644 --- a/internal/provisioner/objects/service/service.go +++ b/internal/provisioner/objects/service/service.go @@ -16,6 +16,7 @@ package service import ( "context" "fmt" + "maps" "strings" core_v1 "k8s.io/api/core/v1" @@ -268,9 +269,7 @@ func DesiredEnvoyService(contour *model.Contour) *core_v1.Service { if isInternal { provider := providerParams.Type internalAnnotations := InternalLBAnnotations[provider] - for name, value := range internalAnnotations { - svc.Annotations[name] = value - } + maps.Copy(svc.Annotations, internalAnnotations) } case model.NodePortServicePublishingType: svc.Spec.Type = core_v1.ServiceTypeNodePort @@ -295,9 +294,7 @@ func DesiredEnvoyService(contour *model.Contour) *core_v1.Service { svc.Annotations = map[string]string{} } - for k, v := range contour.Spec.NetworkPublishing.Envoy.ServiceAnnotations { - svc.Annotations[k] = v - } + maps.Copy(svc.Annotations, contour.Spec.NetworkPublishing.Envoy.ServiceAnnotations) } return svc diff --git a/internal/provisioner/objects/service/service_test.go b/internal/provisioner/objects/service/service_test.go index faf2a89b602..62810507115 100644 --- a/internal/provisioner/objects/service/service_test.go +++ b/internal/provisioner/objects/service/service_test.go @@ -116,7 +116,7 @@ func checkServiceHasType(t *testing.T, svc *core_v1.Service, svcType core_v1.Ser } } -func checkServiceHasExternalTrafficPolicy(t *testing.T, svc *core_v1.Service, policy core_v1.ServiceExternalTrafficPolicyType) { +func checkServiceHasExternalTrafficPolicy(t *testing.T, svc *core_v1.Service, policy core_v1.ServiceExternalTrafficPolicy) { t.Helper() if svc.Spec.ExternalTrafficPolicy != policy { @@ -182,7 +182,7 @@ func TestDesiredEnvoyService(t *testing.T) { svc := DesiredEnvoyService(cntr) checkServiceHasType(t, svc, core_v1.ServiceTypeNodePort) - checkServiceHasExternalTrafficPolicy(t, svc, core_v1.ServiceExternalTrafficPolicyTypeLocal) + checkServiceHasExternalTrafficPolicy(t, svc, core_v1.ServiceExternalTrafficPolicyLocal) checkServiceHasIPFamilyPolicy(t, svc, core_v1.IPFamilyPolicySingleStack) checkServiceHasPort(t, svc, EnvoyServiceHTTPPort) checkServiceHasPort(t, svc, EnvoyServiceHTTPSPort) @@ -204,13 +204,13 @@ func TestDesiredEnvoyService(t *testing.T) { // Check LB annotations for the different provider types, starting with AWS ELB (the default // if AWS provider params are not passed). cntr.Spec.NetworkPublishing.Envoy.Type = model.LoadBalancerServicePublishingType - cntr.Spec.NetworkPublishing.Envoy.ExternalTrafficPolicy = core_v1.ServiceExternalTrafficPolicyTypeCluster + cntr.Spec.NetworkPublishing.Envoy.ExternalTrafficPolicy = core_v1.ServiceExternalTrafficPolicyCluster cntr.Spec.NetworkPublishing.Envoy.IPFamilyPolicy = core_v1.IPFamilyPolicyPreferDualStack cntr.Spec.NetworkPublishing.Envoy.LoadBalancer.Scope = model.ExternalLoadBalancer cntr.Spec.NetworkPublishing.Envoy.LoadBalancer.ProviderParameters.Type = model.AWSLoadBalancerProvider svc = DesiredEnvoyService(cntr) checkServiceHasType(t, svc, core_v1.ServiceTypeLoadBalancer) - checkServiceHasExternalTrafficPolicy(t, svc, core_v1.ServiceExternalTrafficPolicyTypeCluster) + checkServiceHasExternalTrafficPolicy(t, svc, core_v1.ServiceExternalTrafficPolicyCluster) checkServiceHasIPFamilyPolicy(t, svc, core_v1.IPFamilyPolicyPreferDualStack) checkServiceHasAnnotations(t, svc, awsLbBackendProtoAnnotation, awsLBProxyProtocolAnnotation) diff --git a/internal/provisioner/slice/slice.go b/internal/provisioner/slice/slice.go index a75cb3d3b73..3acefcfe3a1 100644 --- a/internal/provisioner/slice/slice.go +++ b/internal/provisioner/slice/slice.go @@ -13,6 +13,8 @@ package slice +import "slices" + // RemoveString returns a newly created []string that contains all items from slice that // are not equal to s. func RemoveString(slice []string, s string) []string { @@ -33,20 +35,10 @@ func RemoveString(slice []string, s string) []string { // ContainsString checks if a given slice of strings contains the provided string. func ContainsString(slice []string, s string) bool { - for _, item := range slice { - if item == s { - return true - } - } - return false + return slices.Contains(slice, s) } // ContainsInt32 checks if a given int32 slice contains the provided int32. func ContainsInt32(slice []int32, i int32) bool { - for _, item := range slice { - if item == i { - return true - } - } - return false + return slices.Contains(slice, i) } diff --git a/internal/xdscache/v3/endpointslicetranslator.go b/internal/xdscache/v3/endpointslicetranslator.go index a1c7e554e83..bcc1eef34b5 100644 --- a/internal/xdscache/v3/endpointslicetranslator.go +++ b/internal/xdscache/v3/endpointslicetranslator.go @@ -15,6 +15,7 @@ package v3 import ( "fmt" + "maps" "sort" "sync" @@ -307,9 +308,7 @@ func (e *EndpointSliceTranslator) Merge(entries map[string]*envoy_config_endpoin e.mu.Lock() defer e.mu.Unlock() - for k, v := range entries { - e.entries[k] = v - } + maps.Copy(e.entries, entries) } // OnChange observes DAG rebuild events. diff --git a/internal/xdscache/v3/endpointslicetranslator_test.go b/internal/xdscache/v3/endpointslicetranslator_test.go index 3515e7330cb..e9df0619517 100644 --- a/internal/xdscache/v3/endpointslicetranslator_test.go +++ b/internal/xdscache/v3/endpointslicetranslator_test.go @@ -964,7 +964,6 @@ func TestEndpointSliceTranslatorRecomputeClusterLoadAssignment(t *testing.T) { } for name, tc := range tests { - tc := tc t.Run(name, func(t *testing.T) { endpointSliceTranslator := NewEndpointSliceTranslator(fixture.NewTestLogger(t)) require.NoError(t, endpointSliceTranslator.cache.SetClusters([]*dag.ServiceCluster{&tc.cluster})) diff --git a/internal/xdscache/v3/listener.go b/internal/xdscache/v3/listener.go index 4a0ce63e76e..291afcf6d00 100644 --- a/internal/xdscache/v3/listener.go +++ b/internal/xdscache/v3/listener.go @@ -161,6 +161,12 @@ type ListenerConfig struct { // SocketOptions configures socket options HTTP and HTTPS listeners. SocketOptions *contour_v1alpha1.SocketOptions + + // EnableJA3Fingerprinting enables JA3 fingerprinting for HTTPS listeners. + EnableJA3Fingerprinting *bool + + // EnableJA4Fingerprinting enables JA4 fingerprinting for HTTPS listeners. + EnableJA4Fingerprinting *bool } type ExtensionServiceConfig struct { @@ -176,6 +182,10 @@ type TracingConfig struct { OverallSampling float64 + ClientSampling float64 + + RandomSampling float64 + MaxPathTagLength uint32 CustomTags []*CustomTag @@ -207,9 +217,8 @@ type RateLimitConfig struct { type GlobalExternalAuthConfig struct { ExtensionServiceConfig - FailOpen bool - Context map[string]string - WithRequestBody *dag.AuthorizationServerBufferSettings + dag.ExternalAuthorization + Context map[string]string } type GlobalExtProcConfig struct { @@ -437,7 +446,7 @@ func (c *ListenerCache) OnChange(root *dag.DAG) { listener.Port, cfg.PerConnectionBufferLimitBytes, socketOptions, - secureProxyProtocol(cfg.UseProxyProto), + secureProxyProtocol(cfg), ) } @@ -467,7 +476,7 @@ func (c *ListenerCache) OnChange(root *dag.DAG) { cm := c.envoyGen.HTTPConnectionManagerBuilder(). Compression(cfg.Compression). Codec(envoy_v3.CodecForVersions(cfg.DefaultHTTPVersions...)). - AddFilter(envoy_v3.FilterMisdirectedRequests(vh.VirtualHost.Name)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). AddFilter(envoy_v3.FilterJWTAuthN(vh.JWTProviders)). AddFilter(authzFilter). @@ -621,9 +630,13 @@ func httpGlobalExternalAuthConfig(config *GlobalExternalAuthConfig) *envoy_filte Name: dag.ExtensionClusterName(config.ExtensionService), SNI: config.SNI, }, - AuthorizationFailOpen: config.FailOpen, + ServiceAPIType: config.ServiceAPIType, + HTTPAllowedAuthorizationHeaders: config.HTTPAllowedAuthorizationHeaders, + HTTPAllowedUpstreamHeaders: config.HTTPAllowedUpstreamHeaders, + HTTPPathPrefix: config.HTTPPathPrefix, + AuthorizationFailOpen: config.AuthorizationFailOpen, AuthorizationResponseTimeout: config.Timeout, - AuthorizationServerWithRequestBody: config.WithRequestBody, + AuthorizationServerWithRequestBody: config.AuthorizationServerWithRequestBody, }) } @@ -672,6 +685,8 @@ func envoyTracingConfig(config *TracingConfig) *envoy_v3.EnvoyTracingConfig { SNI: config.SNI, Timeout: config.Timeout, OverallSampling: config.OverallSampling, + ClientSampling: config.ClientSampling, + RandomSampling: config.RandomSampling, MaxPathTagLength: config.MaxPathTagLength, CustomTags: envoyTracingConfigCustomTag(config.CustomTags), } @@ -702,6 +717,6 @@ func proxyProtocol(useProxy bool) []*envoy_config_listener_v3.ListenerFilter { return nil } -func secureProxyProtocol(useProxy bool) []*envoy_config_listener_v3.ListenerFilter { - return append(proxyProtocol(useProxy), envoy_v3.TLSInspector()) +func secureProxyProtocol(cfg ListenerConfig) []*envoy_config_listener_v3.ListenerFilter { + return append(proxyProtocol(cfg.UseProxyProto), envoy_v3.TLSInspectorWithConfig(cfg.EnableJA3Fingerprinting, cfg.EnableJA4Fingerprinting)) } diff --git a/internal/xdscache/v3/listener_test.go b/internal/xdscache/v3/listener_test.go index 299f996314c..08af8f858b0 100644 --- a/internal/xdscache/v3/listener_test.go +++ b/internal/xdscache/v3/listener_test.go @@ -91,7 +91,7 @@ func TestListenerVisit(t *testing.T) { }) httpsFilterFor := func(vhost string) *envoy_config_listener_v3.Filter { return envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests(vhost)). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", vhost)). @@ -585,7 +585,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("whatever.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "whatever.example.com")). @@ -924,7 +924,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1015,7 +1015,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1106,7 +1106,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1197,7 +1197,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1288,7 +1288,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1379,7 +1379,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -1933,7 +1933,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2210,7 +2210,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2276,7 +2276,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2341,7 +2341,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2406,7 +2406,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2572,7 +2572,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2698,7 +2698,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -2899,7 +2899,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -3004,7 +3004,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -3340,7 +3340,7 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). MetricsPrefix(ENVOY_HTTPS_LISTENER). RouteConfigName(path.Join("https", "www.example.com")). @@ -3417,12 +3417,12 @@ func TestListenerVisit(t *testing.T) { }, TransportSocket: transportSocket(envoyGen, "secret", envoy_transport_socket_tls_v3.TlsParameters_TLSv1_2, envoy_transport_socket_tls_v3.TlsParameters_TLSv1_3, nil, "h2", "http/1.1"), Filters: envoy_v3.Filters(envoyGen.HTTPConnectionManagerBuilder(). - AddFilter(envoy_v3.FilterMisdirectedRequests("www.example.com")). + AddFilter(envoy_v3.FilterMisdirectedRequests()). DefaultFilters(). AddFilter(envoy_v3.FilterJWTAuthN([]dag.JWTProvider{{ Name: jwtProvider.Name, Issuer: jwtProvider.Issuer, - RemoteJWKS: dag.RemoteJWKS{ + RemoteJWKS: &dag.RemoteJWKS{ URI: jwtProvider.RemoteJWKS.URI, Cluster: dag.DNSNameCluster{ Address: jwksURL.Hostname(), diff --git a/internal/xdscache/v3/route_test.go b/internal/xdscache/v3/route_test.go index 4f01441bbdd..364cd09917c 100644 --- a/internal/xdscache/v3/route_test.go +++ b/internal/xdscache/v3/route_test.go @@ -3896,10 +3896,8 @@ func routetimeout(cluster string, timeout time.Duration) *envoy_config_route_v3. func routeretry(cluster, retryOn string, numRetries uint32, perTryTimeout time.Duration) *envoy_config_route_v3.Route_Route { r := routecluster(cluster) r.Route.RetryPolicy = &envoy_config_route_v3.RetryPolicy{ - RetryOn: retryOn, - } - if numRetries > 0 { - r.Route.RetryPolicy.NumRetries = wrapperspb.UInt32(numRetries) + RetryOn: retryOn, + NumRetries: wrapperspb.UInt32(numRetries), } if perTryTimeout > 0 { r.Route.RetryPolicy.PerTryTimeout = durationpb.New(perTryTimeout) diff --git a/internal/xdscache/v3/runtime.go b/internal/xdscache/v3/runtime.go index f062debcb79..98a86a914ce 100644 --- a/internal/xdscache/v3/runtime.go +++ b/internal/xdscache/v3/runtime.go @@ -15,6 +15,7 @@ package v3 import ( "fmt" + "maps" "sync" resource "github.com/envoyproxy/go-control-plane/pkg/resource/v3" @@ -58,14 +59,10 @@ func NewRuntimeCache(runtimeSettings ConfigurableRuntimeSettings) *RuntimeCache func (c *RuntimeCache) buildDynamicLayer() []proto.Message { values := make(map[string]*structpb.Value) - for k, v := range c.runtimeKV { - values[k] = v - } + maps.Copy(values, c.runtimeKV) c.mu.Lock() defer c.mu.Unlock() - for k, v := range c.dynamicRuntimeKV { - values[k] = v - } + maps.Copy(values, c.dynamicRuntimeKV) return protobuf.AsMessages(envoy_v3.RuntimeLayers(values)) } diff --git a/internal/xdscache/v3/runtime_test.go b/internal/xdscache/v3/runtime_test.go index aded8fb4526..826946315cc 100644 --- a/internal/xdscache/v3/runtime_test.go +++ b/internal/xdscache/v3/runtime_test.go @@ -14,6 +14,7 @@ package v3 import ( + "maps" "testing" envoy_service_runtime_v3 "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3" @@ -62,9 +63,7 @@ func TestRuntimeCacheContents(t *testing.T) { "re2.max_program_size.error_level": structpb.NewNumberValue(1 << 20), "re2.max_program_size.warn_level": structpb.NewNumberValue(1000), } - for k, v := range tc.additionalFields { - fields[k] = v - } + maps.Copy(fields, tc.additionalFields) protobuf.ExpectEqual(t, []proto.Message{ &envoy_service_runtime_v3.Runtime{ Name: "dynamic", diff --git a/netlify.toml b/netlify.toml index 1d507316cf3..a1192902572 100644 --- a/netlify.toml +++ b/netlify.toml @@ -4,7 +4,7 @@ publish = "site/public" [build.environment] - HUGO_VERSION = "0.119.0" + HUGO_VERSION = "0.152.0" [context.production.environment] HUGO_ENV = "production" diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index fb07b215d79..099320c914a 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -178,6 +178,10 @@ type TLSParameters struct { // to be used when establishing TLS connection to upstream // cluster. ClientCertificate NamespacedName `yaml:"envoy-client-certificate,omitempty"` + + // Fingerprint defines TLS fingerprinting configuration. + // This only applies to listener TLS (not upstream TLS). + Fingerprint *TLSFingerprint `yaml:"fingerprint,omitempty"` } // ProtocolParameters holds configuration details for TLS protocol specifics. @@ -193,6 +197,17 @@ type ProtocolParameters struct { CipherSuites TLSCiphers `yaml:"cipher-suites,omitempty"` } +// TLSFingerprint defines TLS fingerprinting configuration. +type TLSFingerprint struct { + // JA3 enables JA3 fingerprinting in the TLS Inspector. + // When true, populates JA3 hash in dynamic metadata. + JA3 *bool `yaml:"ja3,omitempty"` + + // JA4 enables JA4 fingerprinting in the TLS Inspector. + // When true, populates JA4 hash in dynamic metadata. + JA4 *bool `yaml:"ja4,omitempty"` +} + // Validate TLS fallback certificate, client certificate, and cipher suites func (t TLSParameters) Validate() error { // Check TLS secret names. @@ -431,11 +446,11 @@ func (p *ClusterParameters) Validate() error { } if p.MaxRequestsPerConnection != nil && *p.MaxRequestsPerConnection < 1 { - return fmt.Errorf("invalid max connections per request value %q set on cluster, minimum value is 1", *p.MaxRequestsPerConnection) + return fmt.Errorf("invalid max connections per request value %d set on cluster, minimum value is 1", *p.MaxRequestsPerConnection) } if p.PerConnectionBufferLimitBytes != nil && *p.PerConnectionBufferLimitBytes < 1 { - return fmt.Errorf("invalid per connections buffer limit bytes value %q set on cluster, minimum value is 1", *p.PerConnectionBufferLimitBytes) + return fmt.Errorf("invalid per connections buffer limit bytes value %d set on cluster, minimum value is 1", *p.PerConnectionBufferLimitBytes) } if err := p.UpstreamTLS.Validate(); err != nil { @@ -526,23 +541,23 @@ func (p *ListenerParameters) Validate() error { } if p.MaxRequestsPerConnection != nil && *p.MaxRequestsPerConnection < 1 { - return fmt.Errorf("invalid max connections per request value %q set on listener, minimum value is 1", *p.MaxRequestsPerConnection) + return fmt.Errorf("invalid max connections per request value %d set on listener, minimum value is 1", *p.MaxRequestsPerConnection) } if p.PerConnectionBufferLimitBytes != nil && *p.PerConnectionBufferLimitBytes < 1 { - return fmt.Errorf("invalid per connections buffer limit bytes value %q set on listener, minimum value is 1", *p.PerConnectionBufferLimitBytes) + return fmt.Errorf("invalid per connections buffer limit bytes value %d set on listener, minimum value is 1", *p.PerConnectionBufferLimitBytes) } if p.MaxRequestsPerIOCycle != nil && *p.MaxRequestsPerIOCycle < 1 { - return fmt.Errorf("invalid max connections per IO cycle value %q set on listener, minimum value is 1", *p.MaxRequestsPerIOCycle) + return fmt.Errorf("invalid max connections per IO cycle value %d set on listener, minimum value is 1", *p.MaxRequestsPerIOCycle) } if p.HTTP2MaxConcurrentStreams != nil && *p.HTTP2MaxConcurrentStreams < 1 { - return fmt.Errorf("invalid max HTTP/2 concurrent streams value %q set on listener, minimum value is 1", *p.HTTP2MaxConcurrentStreams) + return fmt.Errorf("invalid max HTTP/2 concurrent streams value %d set on listener, minimum value is 1", *p.HTTP2MaxConcurrentStreams) } if p.MaxConnectionsPerListener != nil && *p.MaxConnectionsPerListener < 1 { - return fmt.Errorf("invalid max connections per listener value %q set on listener, minimum value is 1", *p.MaxConnectionsPerListener) + return fmt.Errorf("invalid max connections per listener value %d set on listener, minimum value is 1", *p.MaxConnectionsPerListener) } return p.SocketOptions.Validate() @@ -730,6 +745,14 @@ type Tracing struct { // the default value is 100. OverallSampling *string `yaml:"overallSampling,omitempty"` + // ClientSampling defines the client sampling rate of trace data. + // the default value is 100. + ClientSampling *string `yaml:"clientSampling,omitempty"` + + // RandomSampling defines the random sampling rate of trace data. + // the default value is 100. + RandomSampling *string `yaml:"randomSampling,omitempty"` + // MaxPathTagLength defines maximum length of the request path // to extract and include in the HttpUrl tag. // the default value is 256. @@ -764,6 +787,16 @@ type GlobalExternalAuthorization struct { // ExtensionService identifies the extension service defining the RLS, // formatted as /. ExtensionService string `yaml:"extensionService,omitempty"` + // ServiceType defines the external authorization service API type. + // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server + // or a gRPC authorization server. + // + // +optional + ServiceType contour_v1.AuthorizationServiceType `yaml:"serviceType,omitempty"` + // HttpAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. + // + // +optional + HTTPServerSettings *contour_v1.HTTPAuthorizationServerSettings `yaml:"httpSettings,omitempty"` // AuthPolicy sets a default authorization policy for client requests. // This policy will be used unless overridden by individual routes. // diff --git a/pkg/config/parameters_test.go b/pkg/config/parameters_test.go index 6d689619d80..16df452eb05 100644 --- a/pkg/config/parameters_test.go +++ b/pkg/config/parameters_test.go @@ -682,6 +682,8 @@ func TestTracingConfigValidation(t *testing.T) { IncludePodDetail: ptr.To(false), ServiceName: ptr.To("contour"), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: nil, ExtensionService: "projectcontour/otel-collector", @@ -692,6 +694,8 @@ func TestTracingConfigValidation(t *testing.T) { IncludePodDetail: ptr.To(false), ServiceName: ptr.To("contour"), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: nil, } @@ -700,6 +704,8 @@ func TestTracingConfigValidation(t *testing.T) { trace = &Tracing{ IncludePodDetail: ptr.To(false), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: nil, ExtensionService: "projectcontour/otel-collector", @@ -708,6 +714,8 @@ func TestTracingConfigValidation(t *testing.T) { trace = &Tracing{ OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: []CustomTag{ { @@ -722,6 +730,8 @@ func TestTracingConfigValidation(t *testing.T) { trace = &Tracing{ OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: []CustomTag{ { @@ -735,6 +745,8 @@ func TestTracingConfigValidation(t *testing.T) { trace = &Tracing{ IncludePodDetail: ptr.To(true), OverallSampling: ptr.To("100"), + ClientSampling: ptr.To("100"), + RandomSampling: ptr.To("100"), MaxPathTagLength: ptr.To(uint32(256)), CustomTags: []CustomTag{ { diff --git a/site/content/docs/1.33/config/client-authorization.md b/site/content/docs/1.33/config/client-authorization.md index 4db2b932eff..ba01ba8b5d5 100644 --- a/site/content/docs/1.33/config/client-authorization.md +++ b/site/content/docs/1.33/config/client-authorization.md @@ -74,6 +74,7 @@ Each virtual host can use a different `ExtensionService`, but only one `ExtensionService` can be used by a single virtual host. Authorization servers can only be attached to `HTTPProxy` objects that have TLS termination enabled. +Client authorization is not compatible with the [fallback certificate](tls-termination.md#fallback-certificate) feature. ### Migrating from Application Authorization @@ -93,7 +94,7 @@ The HTTPProxy [authorization policy][6] allows authorization to be disabled for both an entire virtual host and for specific routes. The initial authorization policy is set on the HTTPProxy virtual host -in the `.spec.virtualhost.authorization.authPolicy` field. +in the `.spec.virtualhost.authorization.authPolicy` field. This configures whether authorization is enabled, and the default authorization policy context. If authorization is disabled on the virtual host, it is also disabled by default on all the routes for that virtual host that do not specify an authorization policy. diff --git a/site/content/docs/1.33/config/jwt-verification.md b/site/content/docs/1.33/config/jwt-verification.md index 3f884ad2aef..726bde6b362 100644 --- a/site/content/docs/1.33/config/jwt-verification.md +++ b/site/content/docs/1.33/config/jwt-verification.md @@ -12,6 +12,8 @@ If verification fails, an HTTP 401 (Unauthorized) will be returned to the client JWT verification is only supported on TLS-terminating virtual hosts. +JWT verification is not compatible with the [fallback certificate](tls-termination.md#fallback-certificate) feature. + ## Configuring providers and rules A JWT provider is configured for an HTTPProxy's virtual host, and defines how to verify JWTs: diff --git a/site/content/docs/1.33/config/tls-termination.md b/site/content/docs/1.33/config/tls-termination.md index d284ba082b0..0b0c51c4061 100644 --- a/site/content/docs/1.33/config/tls-termination.md +++ b/site/content/docs/1.33/config/tls-termination.md @@ -70,7 +70,8 @@ Some legacy TLS clients do not send the server name, so Envoy does not know how _**Note:** The minimum TLS protocol version for any fallback request is defined by the `minimum TLS protocol version` set in the Contour configuration file. -Enabling the fallback certificate is not compatible with TLS client authentication._ +The fallback certificate feature uses a single shared connection manager for all participating virtual hosts. +Per-virtual host security policies are therefore not supported with fallback certificates, including TLS client authentication, external authorization, and JWT verification._ ### Fallback Certificate Configuration diff --git a/site/content/docs/main/config/access-logging.md b/site/content/docs/main/config/access-logging.md index 0c5b6e1583c..d3731a78fc0 100644 --- a/site/content/docs/main/config/access-logging.md +++ b/site/content/docs/main/config/access-logging.md @@ -125,6 +125,32 @@ For JSON access logging, the following fields can be added (these are Contour-sp - `contour_config_namespace` - `contour_config_name` +### Logging TLS Fingerprints (JA3/JA4) + +When JA3/JA4 TLS fingerprinting is enabled in [configuration file](../configuration#tls-fingerprint) or [ContourConfiguration resource](api-reference.html#projectcontour.io/v1alpha1.TLSFingerprint), fingerprints can be logged in access logs. + +For text-based access logging, the following command operators can be used: +- `%TLS_JA3_FINGERPRINT%` - JA3 fingerprint hash (MD5 of the TLS ClientHello) +- `%TLS_JA4_FINGERPRINT%` - JA4 fingerprint + +For JSON access logging, the following built-in fields can be added: +- `tls_ja3_fingerprint` +- `tls_ja4_fingerprint` + +Example JSON access log configuration: +```yaml +accesslog-format: json +json-fields: + - "@timestamp" + - "method" + - "path" + - "response_code" + - "tls_ja3_fingerprint" + - "tls_ja4_fingerprint" +``` + +**Note:** The fingerprint values will be empty (`-`) for non-TLS connections or if the corresponding fingerprinting feature is not enabled. + ## Using Access Log Formatter Extensions Envoy allows implementing custom access log command operators as extensions. diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index f540c3a85bb..4ec8c54ae29 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -334,7 +334,8 @@

AuthorizationServer

AuthorizationServer configures an external server to authenticate client requests. The external server must implement the v3 Envoy -external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto).

+external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto) +or the HTTP authorization server protocol.

@@ -361,6 +362,37 @@

AuthorizationServer

+ + + + + + + +
+serviceType +
+ + +AuthorizationServiceType + + +
+(Optional) +

ServiceType sets the protocol used to communicate with +the external authorization server.

+
+httpSettings +
+ + +HTTPAuthorizationServerSettings + + +
+(Optional) +

HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server.

+
authPolicy
@@ -482,6 +514,29 @@

AuthorizationSer

+

AuthorizationServiceType +(string alias)

+

+(Appears on: +AuthorizationServer) +

+

+

AuthorizationServiceType indicates the protocol +implemented by the external authorization server.

+

+ + + + + + + + + + + + +
ValueDescription

"grpc"

"http"

BodySendMode (string alias)

@@ -1482,6 +1537,156 @@

GlobalRateLimitPolicy +

HTTPAuthorizationServerAllowedHeaders +

+

+(Appears on: +HTTPAuthorizationServerSettings) +

+

+

HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers +in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user +experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+exact +
+ +string + +
+(Optional) +

Exact specifies a string that the header name must be equal to.

+
+prefix +
+ +string + +
+(Optional) +

Prefix defines a prefix match for the header name.

+
+suffix +
+ +string + +
+(Optional) +

Suffix defines a suffix match for a header name.

+
+contains +
+ +string + +
+(Optional) +

Contains specifies a substring that must be present in the header name.

+
+ignoreCase +
+ +bool + +
+(Optional) +

IgnoreCase specifies whether string matching should be case-insensitive.

+
+

HTTPAuthorizationServerSettings +

+

+(Appears on: +AuthorizationServer) +

+

+

HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pathPrefix +
+ +string + +
+(Optional) +

PathPrefix Sets a prefix to the value of authorization request header Path.

+
+allowedAuthorizationHeaders +
+ + +[]HTTPAuthorizationServerAllowedHeaders + + +
+(Optional) +

AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. +Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list.

+
+allowedUpstreamHeaders +
+ + +[]HTTPAuthorizationServerAllowedHeaders + + +
+(Optional) +

AllowedUpstreamHeaders specifies response headers from the authorization server +that may be added to the original client request before sending it to the upstream.

+

HTTPDirectResponsePolicy

@@ -2698,7 +2903,7 @@

JWTProvider -remoteJWKS +remoteJWKS,omitzero
@@ -2707,7 +2912,23 @@

JWTProvider -

Remote JWKS to use for verifying JWT signatures.

+(Optional) +

Remote JWKS fetches signing keys from an HTTP(S) endpoint.

+ + + + +localJWKS,omitzero +
+ +
+LocalJWKS + + + + +(Optional) +

Local JWKS loads signing keys from a Kubernetes Secret.

@@ -2834,6 +3055,49 @@

LoadBalancerPolicy +

LocalJWKS +

+

+(Appears on: +JWTProvider) +

+

+

LocalJWKS defines how to fetch a JWKS from a Kubernetes secret.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+secretName +
+ +string + +
+

The name of the secret that contains the JWKS.

+
+key +
+ +string + +
+

The key of the secret that contains the JWKS.

+

LocalRateLimitPolicy

@@ -5380,8 +5644,8 @@

UpstreamValidation -

Key which is expected to be present in the ‘subjectAltName’ of the presented certificate. -Deprecated: migrate to using the plural field subjectNames.

+

Key which is expected to be present in the ‘subjectAltName’ of the presented certificate.

+

Deprecated: migrate to using the plural field subjectNames.

@@ -6123,7 +6387,7 @@

ExtensionService

Services specifies the set of Kubernetes Service resources that -receive GRPC extension API requests. +receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -6157,7 +6421,7 @@

ExtensionService (Optional)

Protocol may be used to specify (or override) the protocol used to reach this Service. -Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations.

@@ -6172,9 +6436,9 @@

ExtensionService (Optional) -

The policy for load balancing GRPC service requests. Note that the +

The policy for load balancing service requests. Note that the Cookie and RequestHash load balancing strategies cannot be used -here.

+here for GRPC service requests.

@@ -7854,8 +8118,8 @@

EnvoyListenerConfig tls
- -EnvoyTLS + +EnvoyListenerTLS @@ -7931,6 +8195,58 @@

EnvoyListenerConfig +

EnvoyListenerTLS +

+

+(Appears on: +EnvoyListenerConfig) +

+

+

EnvoyListenerTLS describes TLS parameters for Envoy listeners. +It extends EnvoyTLS with listener-specific settings like TLS fingerprinting.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+EnvoyTLS +
+ + +EnvoyTLS + + +
+

+(Members of EnvoyTLS are embedded into this type.) +

+
+fingerprint +
+ + +TLSFingerprint + + +
+(Optional) +

Fingerprint defines TLS fingerprinting configuration +for the TLS Inspector listener filter.

+

EnvoyLogging

@@ -8291,10 +8607,11 @@

EnvoyTLS

(Appears on: ClusterParameters, -EnvoyListenerConfig) +EnvoyListenerTLS)

-

EnvoyTLS describes tls parameters for Envoy listneners.

+

EnvoyTLS describes TLS protocol parameters shared between +listener and upstream TLS contexts.

@@ -8437,7 +8754,7 @@

ExtensionServiceSpec

@@ -8486,9 +8803,9 @@

ExtensionServiceSpec

@@ -9703,6 +10020,53 @@

TLS

Services specifies the set of Kubernetes Service resources that -receive GRPC extension API requests. +receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -8471,7 +8788,7 @@

ExtensionServiceSpec

(Optional)

Protocol may be used to specify (or override) the protocol used to reach this Service. -Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations.

(Optional) -

The policy for load balancing GRPC service requests. Note that the +

The policy for load balancing service requests. Note that the Cookie and RequestHash load balancing strategies cannot be used -here.

+here for GRPC service requests.

+

TLSFingerprint +

+

+(Appears on: +EnvoyListenerTLS) +

+

+

TLSFingerprint defines TLS fingerprinting configuration for the TLS Inspector.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+ja3 +
+ +bool + +
+(Optional) +

JA3 enables JA3 fingerprinting in the TLS Inspector. +When true, populates JA3 hash in dynamic metadata.

+
+ja4 +
+ +bool + +
+(Optional) +

JA4 enables JA4 fingerprinting in the TLS Inspector. +When true, populates JA4 hash in dynamic metadata.

+

TimeoutParameters

@@ -9907,6 +10271,34 @@

TracingConfig +clientSampling +
+ +string + + + +(Optional) +

ClientSampling defines the sampling rate when x-client-trace-id header is set. +contour’s default is 100.

+ + + + +randomSampling +
+ +string + + + +(Optional) +

RandomSampling defines the random sampling rate for all requests. +contour’s default is 100.

+ + + + maxPathTagLength
diff --git a/site/content/docs/main/config/client-authorization.md b/site/content/docs/main/config/client-authorization.md index 4db2b932eff..ba01ba8b5d5 100644 --- a/site/content/docs/main/config/client-authorization.md +++ b/site/content/docs/main/config/client-authorization.md @@ -74,6 +74,7 @@ Each virtual host can use a different `ExtensionService`, but only one `ExtensionService` can be used by a single virtual host. Authorization servers can only be attached to `HTTPProxy` objects that have TLS termination enabled. +Client authorization is not compatible with the [fallback certificate](tls-termination.md#fallback-certificate) feature. ### Migrating from Application Authorization @@ -93,7 +94,7 @@ The HTTPProxy [authorization policy][6] allows authorization to be disabled for both an entire virtual host and for specific routes. The initial authorization policy is set on the HTTPProxy virtual host -in the `.spec.virtualhost.authorization.authPolicy` field. +in the `.spec.virtualhost.authorization.authPolicy` field. This configures whether authorization is enabled, and the default authorization policy context. If authorization is disabled on the virtual host, it is also disabled by default on all the routes for that virtual host that do not specify an authorization policy. diff --git a/site/content/docs/main/config/jwt-verification.md b/site/content/docs/main/config/jwt-verification.md index 3f884ad2aef..2396e838b8d 100644 --- a/site/content/docs/main/config/jwt-verification.md +++ b/site/content/docs/main/config/jwt-verification.md @@ -12,6 +12,8 @@ If verification fails, an HTTP 401 (Unauthorized) will be returned to the client JWT verification is only supported on TLS-terminating virtual hosts. +JWT verification is not compatible with the [fallback certificate](tls-termination.md#fallback-certificate) feature. + ## Configuring providers and rules A JWT provider is configured for an HTTPProxy's virtual host, and defines how to verify JWTs: @@ -88,6 +90,42 @@ If the CA secret's namespace is not the same namespace as the `HTTPProxy` resour **Note:** If `spec.virtualhost.jwtProviders[].remoteJWKS.validation` is present, `spec.virtualhost.jwtProviders[].remoteJWKS.uri` must have a scheme of `https`. +## Using a local JWKS from a Kubernetes Secret + +Instead of fetching keys from a remote endpoint, you can load them from a Kubernetes Secret: + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: jwt-verification-local + namespace: default +spec: + virtualhost: + fqdn: example.com + tls: + secretName: example-com-tls-cert + jwtProviders: + - name: provider-1 + issuer: example.com + localJWKS: + secretName: my-jwks + key: jwks.json + routes: + - conditions: + - prefix: / + jwtVerificationPolicy: + require: provider-1 + services: + - name: s1 + port: 80 +``` + +The Secret must be of type `Opaque` and reside in the same namespace as the HTTPProxy. +The data entry referenced by `key` must contain a valid JWKS JSON object. + +Each provider must specify exactly one of `remoteJWKS` or `localJWKS`. + ## Setting a default provider The previous section showed how to explicitly require JWT providers for specific routes. diff --git a/site/content/docs/main/config/request-rewriting.md b/site/content/docs/main/config/request-rewriting.md index 88fa3cc2508..40142cc492d 100644 --- a/site/content/docs/main/config/request-rewriting.md +++ b/site/content/docs/main/config/request-rewriting.md @@ -203,6 +203,8 @@ documentation for details of what each of these resolve to: * `%RESPONSE_FLAGS%` * `%RESPONSE_CODE_DETAILS%` * `%UPSTREAM_REMOTE_ADDRESS%` +* `%TLS_JA3_FINGERPRINT%` +* `%TLS_JA4_FINGERPRINT%` Note that Envoy passes variables that can't be expanded through unchanged or skips them entirely - for example: diff --git a/site/content/docs/main/config/request-routing.md b/site/content/docs/main/config/request-routing.md index 19ef5386e86..3d4a17d96d7 100644 --- a/site/content/docs/main/config/request-routing.md +++ b/site/content/docs/main/config/request-routing.md @@ -126,6 +126,36 @@ Modifiers: - `ignoreCase`: IgnoreCase specifies that string matching should be case insensitive. It has no effect on the `Regex` parameter. - `treatMissingAsEmpty`: specifies if the header match rule specified header does not exist, this header value will be treated as empty. Defaults to false. Unlike the underlying Envoy implementation this is **only** supported for negative matches (e.g. NotContains, NotExact). +#### Method Matching + +HTTPProxy currently does not provide a dedicated field for matching HTTP methods. However, you can achieve method-based routing using **header conditions** with the HTTP/2 pseudo-header formatted`:method` header match. This allows routing requests based on HTTP methods such as `GET`, `POST`, `PUT`, etc. + +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: method-matching + namespace: default +spec: + virtualhost: + fqdn: methods.example.com + routes: + - conditions: + - header: + name: ":method" + exact: GET + services: + - name: get-service + port: 80 + - conditions: + - header: + name: ":method" + exact: POST + services: + - name: post-service + port: 80 +``` + #### Query parameter conditions Similar to the `header` conditions, `queryParameter` conditions also require the @@ -133,6 +163,10 @@ Similar to the `header` conditions, `queryParameter` conditions also require the e.g. `search` when the query string looks like `/?search=term` and `term` representing the value. +The `name` field is matched **case-sensitively** against the query string — +`Search` and `search` are different parameters. The `ignoreCase` modifier +described below applies only to the *value* of the parameter, not its name. + There are six operator fields: `exact`, `prefix`, `suffix`, `regex`, `contains` and `present` and a modifier `ignoreCase` which can be used together with all of the operator fields except `regex` and `present`. @@ -158,6 +192,31 @@ the operator fields except `regex` and `present`. - `ignoreCase` is a boolean, and if set to `true` it will enable case insensitive matching for any of the string operator matching methods. +#### Match Condition Precedence + +Envoy matches requests against routes in the order it receives them. When a +single virtualhost has multiple routes whose match conditions could overlap, +Contour sorts the routes by **specificity** before sending them to Envoy, so +the most specific match wins regardless of the order they appear in the +HTTPProxy spec. + +The sort order is: + +1. **Path matches**, most specific first: + 1. **Exact** matches. + 2. **Regex** matches, ordered by expression length (longer regexes first, + as a proxy for specificity). + 3. **Prefix** matches, ordered by prefix length (longer prefixes first). +2. **Header conditions** — routes with more header conditions win over + routes with fewer. +3. **Query parameter conditions** — routes with more query parameter + conditions win over routes with fewer. + +This is intentionally similar to Gateway API's [HTTPRouteRule precedence +spec](https://gateway-api.sigs.k8s.io/reference/api-spec/main/spec/#httprouterule), +so HTTPProxy users moving between the two APIs can rely on the same mental +model. + ## Request Redirection HTTP redirects can be implemented in HTTPProxy using `requestRedirectPolicy` on a route. diff --git a/site/content/docs/main/config/tls-termination.md b/site/content/docs/main/config/tls-termination.md index d284ba082b0..0b0c51c4061 100644 --- a/site/content/docs/main/config/tls-termination.md +++ b/site/content/docs/main/config/tls-termination.md @@ -70,7 +70,8 @@ Some legacy TLS clients do not send the server name, so Envoy does not know how _**Note:** The minimum TLS protocol version for any fallback request is defined by the `minimum TLS protocol version` set in the Contour configuration file. -Enabling the fallback certificate is not compatible with TLS client authentication._ +The fallback certificate feature uses a single shared connection manager for all participating virtual hosts. +Per-virtual host security policies are therefore not supported with fallback certificates, including TLS client authentication, external authorization, and JWT verification._ ### Fallback Certificate Configuration diff --git a/site/content/docs/main/config/tracing.md b/site/content/docs/main/config/tracing.md index 5500c26d547..fb8c19762e8 100644 --- a/site/content/docs/main/config/tracing.md +++ b/site/content/docs/main/config/tracing.md @@ -13,13 +13,15 @@ Contour supports configuring envoy to export data to OpenTelemetry, and allows u - Custom service name, the default is `contour`. - Custom sampling rate, the default is `100`. +- Custom client sampling rate, the default is `100`. +- Custom random sampling rate, the default is `100`. - Custom the maximum length of the request path, the default is `256`. - Customize span tags from literal or request headers. - Customize whether to include the pod's hostname and namespace. ## Tracing-config -In order to use this feature, you must first select and deploy an opentelemetry-collector to receive the tracing data exported by envoy. +In order to use this feature, you must first select and deploy an opentelemetry-collector to receive the tracing data exported by envoy. First we should deploy an opentelemetry-collector to receive the tracing data exported by envoy ```bash @@ -79,7 +81,7 @@ metadata: name: contour namespace: projectcontour data: - contour.yaml: | + contour.yaml: | tracing: # Whether to send the namespace and instance where envoy is located to open, the default is true. includePodDetail: true @@ -87,6 +89,12 @@ data: extensionService: projectcontour/otel-collector # The service name that envoy sends to openTelemetry-collector, the default is contour. serviceName: some-service-name + # The overall sampling rate for tracing, the default is 100. + overallSampling: 100 + # The client sampling rate for tracing, the default is 100. + clientSampling: 100 + # The random sampling rate for tracing, the default is 100. + randomSampling: 100 # A custom set of tags. customTags: # envoy will send the tagName to the collector. @@ -94,9 +102,9 @@ data: # fixed tag value. literal: foo - tagName: header-tag - # The tag value obtained from the request header, + # The tag value obtained from the request header, # if the request header does not exist, this tag will not be sent. - requestHeaderName: X-Custom-Header + requestHeaderName: X-Custom-Header EOF ``` @@ -114,4 +122,3 @@ Now you should be able to see traces in the logs of the otel collector. [1]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/tracing [2]: https://opentelemetry.io/ - diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md index 7fd5b5c260f..7ab1771ab74 100644 --- a/site/content/docs/main/configuration.md +++ b/site/content/docs/main/configuration.md @@ -32,7 +32,7 @@ Many of these flags are mirrored in the [Contour Configuration File](#configurat | `--http-port=` | Port the metrics HTTP endpoint will bind to. | | `--health-address=` | Address the health HTTP endpoint will bind to | | `--health-port=` | Port the health HTTP endpoint will bind to | -| `--contour-cafile=` | CA bundle file name for serving gRPC with TLS | +| `--contour-cafile=` | CA bundle file name for serving gRPC with TLS | | `--contour-cert-file=` | Contour certificate file name for serving gRPC over TLS | | `--contour-key-file=` | Contour key file name for serving gRPC over TLS | | `--insecure` | Allow serving without TLS secured gRPC | @@ -117,6 +117,7 @@ Contour should provision TLS hosts. | fallback-certificate | | | [Fallback certificate configuration](#fallback-certificate). | | envoy-client-certificate | | | [Client certificate configuration for Envoy](#envoy-client-certificate). | | cipher-suites | []string | See [config package documentation](https://pkg.go.dev/github.com/projectcontour/contour/pkg/config#pkg-variables) | This field specifies the TLS ciphers to be supported by TLS listeners when negotiating TLS 1.2. This parameter should only be used by advanced users. Note that this is ignored when TLS 1.3 is in use. The set of ciphers that are allowed is a superset of those supported by default in stock, non-FIPS Envoy builds and FIPS builds as specified [here](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/transport_sockets/tls/v3/common.proto#envoy-v3-api-field-extensions-transport-sockets-tls-v3-tlsparameters-cipher-suites). Custom ciphers not accepted by Envoy in a standard build are not supported. | +| fingerprint | | | [TLS fingerprinting configuration](#tls-fingerprint). | ### Upstream TLS Configuration @@ -143,6 +144,14 @@ The Upstream TLS configuration block can be used to configure default values for | name | string | `""` | This field specifies the name of the Kubernetes secret to use as the client certificate and private key when establishing TLS connections to the backend service. | | namespace | string | `""` | This field specifies the namespace of the Kubernetes secret to use as the client certificate and private key when establishing TLS connections to the backend service. | +### TLS Fingerprint + +When enabled, TLS fingerprints are computed for HTTPS listeners and can be logged in access logs. See [Logging TLS Fingerprints](config/access-logging#logging-tls-fingerprints-ja3ja4) for more information. + +| Field Name | Type | Default | Description | +| ---------- | ------- | ------- | ------------------------------------ | +| ja3 | boolean | `false` | Enables JA3 TLS fingerprinting. | +| ja4 | boolean | `false` | Enables JA4 TLS fingerprinting. | ### Timeout Configuration diff --git a/site/content/examples/authdemo/01-prereq.yaml b/site/content/examples/authdemo/01-prereq.yaml deleted file mode 100644 index c00497602d7..00000000000 --- a/site/content/examples/authdemo/01-prereq.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: projectcontour-auth ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: projectcontour-auth-htpasswd - namespace: projectcontour-auth ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: projectcontour:authserver:htpasswd - namespace: projectcontour-auth -rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: projectcontour:authserver:htpasswd - namespace: projectcontour-auth -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: projectcontour:authserver:htpasswd -subjects: - - kind: ServiceAccount - name: projectcontour-auth-htpasswd - namespace: projectcontour-auth diff --git a/site/content/examples/authdemo/02-auth-deployment.yaml b/site/content/examples/authdemo/02-auth-deployment.yaml deleted file mode 100644 index 2ab5dc28d37..00000000000 --- a/site/content/examples/authdemo/02-auth-deployment.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: htpasswd - namespace: projectcontour-auth - labels: - app.kubernetes.io/name: htpasswd -spec: - selector: - matchLabels: - app.kubernetes.io/name: htpasswd - replicas: 1 - template: - metadata: - labels: - app.kubernetes.io/name: htpasswd - spec: - serviceAccountName: projectcontour-auth-htpasswd - containers: - - name: htpasswd - image: ghcr.io/projectcontour/contour-authserver:v4 - imagePullPolicy: IfNotPresent - command: - - /contour-authserver - args: - - htpasswd - - --address=:9443 - - --tls-ca-path=/tls/ca.crt - - --tls-cert-path=/tls/tls.crt - - --tls-key-path=/tls/tls.key - ports: - - name: auth - containerPort: 9443 - protocol: TCP - volumeMounts: - - name: tls - mountPath: /tls - readOnly: true - resources: - limits: - cpu: 100m - memory: 30Mi - volumes: - - name: tls - secret: - secretName: contourcert ---- -apiVersion: v1 -kind: Service -metadata: - name: htpasswd - namespace: projectcontour-auth - labels: - app.kubernetes.io/name: htpasswd -spec: - ports: - - name: auth - protocol: TCP - port: 9443 - targetPort: 9443 - selector: - app.kubernetes.io/name: htpasswd - type: ClusterIP diff --git a/site/content/examples/authdemo/02-certsjob.yaml b/site/content/examples/authdemo/02-certsjob.yaml deleted file mode 100644 index eaa916e81ac..00000000000 --- a/site/content/examples/authdemo/02-certsjob.yaml +++ /dev/null @@ -1,73 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: contour-certgen - namespace: projectcontour-auth ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: RoleBinding -metadata: - name: contour - namespace: projectcontour-auth -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: contour-certgen -subjects: - - kind: ServiceAccount - name: contour-certgen - namespace: projectcontour-auth ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: Role -metadata: - name: contour-certgen - namespace: projectcontour-auth -rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - create - - update ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: contour-certgen-main - namespace: projectcontour-auth -spec: - ttlSecondsAfterFinished: 0 - template: - metadata: - labels: - app: "contour-certgen" - spec: - containers: - - name: contour - image: ghcr.io/projectcontour/contour:main - imagePullPolicy: Always - command: - - contour - - certgen - - --kube - - --incluster - - --overwrite - - --secrets-format=compact - - --namespace=$(CONTOUR_NAMESPACE) - env: - - name: CONTOUR_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - restartPolicy: Never - serviceAccountName: contour-certgen - securityContext: - runAsNonRoot: true - runAsUser: 65534 - runAsGroup: 65534 - parallelism: 1 - completions: 1 - backoffLimit: 1 diff --git a/site/content/examples/authdemo/03-secret.yaml b/site/content/examples/authdemo/03-secret.yaml deleted file mode 100644 index 73c1bf90a6e..00000000000 --- a/site/content/examples/authdemo/03-secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: v1 -kind: Secret -type: Opaque -metadata: - name: passwords - namespace: projectcontour-auth - annotations: - projectcontour.io/auth-type: basic -data: - auth: dXNlcjE6JGFwcjEkbEFFcGZZYW8kem1IZFdZa0dkVUVKSzkwbk9saTRCMQp1c2VyMjokYXByMSQ3R0Yuczg4VSQ2dEI4Y2FXWkROalZQTnVuMEQ2aVcwCnVzZXIzOiRhcHIxJEVSTWtmQmJnJE41MWQydll0VjBYZUVHWEpTLnBUTy4K - diff --git a/site/content/examples/authdemo/04-extensionservice.yaml b/site/content/examples/authdemo/04-extensionservice.yaml deleted file mode 100644 index ade8eda8648..00000000000 --- a/site/content/examples/authdemo/04-extensionservice.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: projectcontour.io/v1alpha1 -kind: ExtensionService -metadata: - name: htpasswd - namespace: projectcontour-auth -spec: - protocol: h2 - services: - - name: htpasswd - port: 9443 diff --git a/site/content/examples/authdemo/04-sampleapp.yaml b/site/content/examples/authdemo/04-sampleapp.yaml deleted file mode 100644 index 86b60a7c4e2..00000000000 --- a/site/content/examples/authdemo/04-sampleapp.yaml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ingress-conformance-echo - namespace: projectcontour-auth -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: ingress-conformance-echo - template: - metadata: - labels: - app.kubernetes.io/name: ingress-conformance-echo - spec: - containers: - - name: conformance-echo - image: agervais/ingress-conformance-echo:latest - ports: - - name: http-api - containerPort: 3000 - readinessProbe: - httpGet: - path: /health - port: 3000 - ---- - -apiVersion: v1 -kind: Service -metadata: - name: ingress-conformance-echo - namespace: projectcontour-auth -spec: - ports: - - name: http - port: 80 - targetPort: http-api - selector: - app.kubernetes.io/name: ingress-conformance-echo diff --git a/site/content/examples/authdemo/05-proxy.yaml b/site/content/examples/authdemo/05-proxy.yaml deleted file mode 100644 index 9126e841ae8..00000000000 --- a/site/content/examples/authdemo/05-proxy.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: echo - namespace: projectcontour-auth -spec: - virtualhost: - fqdn: local.projectcontour.io - tls: - secretName: envoycert - routes: - - services: - - name: ingress-conformance-echo - port: 80 diff --git a/site/content/examples/authdemo/06-proxy-auth.yaml b/site/content/examples/authdemo/06-proxy-auth.yaml deleted file mode 100644 index 928c8139932..00000000000 --- a/site/content/examples/authdemo/06-proxy-auth.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: echo - namespace: projectcontour-auth -spec: - virtualhost: - fqdn: local.projectcontour.io - tls: - secretName: envoycert - authorization: - extensionRef: - name: htpasswd - namespace: projectcontour-auth - routes: - - services: - - name: ingress-conformance-echo - port: 80 diff --git a/site/content/examples/proxydemo/01-prereq.yaml b/site/content/examples/proxydemo/01-prereq.yaml deleted file mode 100644 index 84dfc676f21..00000000000 --- a/site/content/examples/proxydemo/01-prereq.yaml +++ /dev/null @@ -1,130 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: projectcontour-roots ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: rootapp - namespace: projectcontour-roots -spec: - replicas: 1 - selector: - matchLabels: - app: rootapp - template: - metadata: - labels: - app: rootapp - spec: - containers: - - name: echo - image: stevesloka/echo-server - command: ["echo-server"] - args: - - --echotext=This is the default app site! - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: rootapp - name: rootapp - namespace: projectcontour-roots -spec: - ports: - - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: rootapp - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: secureapp - namespace: projectcontour-roots -spec: - replicas: 1 - selector: - matchLabels: - app: secureapp - template: - metadata: - labels: - app: secureapp - spec: - containers: - - name: echo - image: stevesloka/echo-server - command: ["echo-server"] - args: - - --echotext=This is the secure app site! - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: secureapp - name: secureapp - namespace: projectcontour-roots -spec: - ports: - - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: secureapp - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: secureapp-default - namespace: projectcontour-roots -spec: - replicas: 1 - selector: - matchLabels: - app: secureapp-default - template: - metadata: - labels: - app: secureapp-default - spec: - containers: - - name: echo - image: stevesloka/echo-server - command: ["echo-server"] - args: - - --echotext=This is the DEFAULT secure app site! - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: secureapp-default - name: secureapp-default - namespace: projectcontour-roots -spec: - ports: - - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: secureapp-default - type: ClusterIP diff --git a/site/content/examples/proxydemo/02-proxy-basic.yaml b/site/content/examples/proxydemo/02-proxy-basic.yaml deleted file mode 100644 index 3861bc26c5a..00000000000 --- a/site/content/examples/proxydemo/02-proxy-basic.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: root - namespace: projectcontour-roots -spec: - virtualhost: - fqdn: local.projectcontour.io - routes: - - services: - - name: rootapp - port: 80 - - services: - - name: secureapp - port: 80 - conditions: - - prefix: /secure diff --git a/site/content/examples/proxydemo/03-proxy-conditions.yaml b/site/content/examples/proxydemo/03-proxy-conditions.yaml deleted file mode 100644 index 4aac53bb56f..00000000000 --- a/site/content/examples/proxydemo/03-proxy-conditions.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: root - namespace: projectcontour-roots -spec: - virtualhost: - fqdn: local.projectcontour.io - routes: - - services: - - name: rootapp - port: 80 - conditions: - - prefix: / - - services: - - name: secureapp-default - port: 80 - conditions: - - prefix: /secure - - services: - - name: secureapp - port: 80 - conditions: - - prefix: /secure - - header: - name: User-Agent - contains: Chrome diff --git a/site/content/examples/proxydemo/04-marketing-prereq.yaml b/site/content/examples/proxydemo/04-marketing-prereq.yaml deleted file mode 100644 index 7ff4f79f3f2..00000000000 --- a/site/content/examples/proxydemo/04-marketing-prereq.yaml +++ /dev/null @@ -1,88 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: projectcontour-marketing ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: wwwblog - namespace: projectcontour-marketing -spec: - replicas: 1 - selector: - matchLabels: - app: wwwblog - template: - metadata: - labels: - app: wwwblog - spec: - containers: - - name: echo - image: stevesloka/echo-server - command: ["echo-server"] - args: - - --echotext=This is the blog site! - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: wwwblog - name: wwwblog - namespace: projectcontour-marketing -spec: - ports: - - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: wwwblog - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: info - namespace: projectcontour-marketing -spec: - replicas: 1 - selector: - matchLabels: - app: info - template: - metadata: - labels: - app: info - spec: - containers: - - name: echo - image: stevesloka/echo-server - command: ["echo-server"] - args: - - --echotext=This is the INFO site! - imagePullPolicy: IfNotPresent - ports: - - name: http - containerPort: 8080 ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app: info - name: info - namespace: projectcontour-marketing -spec: - ports: - - port: 80 - protocol: TCP - targetPort: 8080 - selector: - app: info - type: ClusterIP diff --git a/site/content/examples/proxydemo/04-proxy-include-basic.yaml b/site/content/examples/proxydemo/04-proxy-include-basic.yaml deleted file mode 100644 index 4bf93417c3a..00000000000 --- a/site/content/examples/proxydemo/04-proxy-include-basic.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: root - namespace: projectcontour-roots -spec: - virtualhost: - fqdn: local.projectcontour.io - includes: - - name: blogsite - namespace: projectcontour-marketing - conditions: - - prefix: /blog - routes: - - services: - - name: rootapp - port: 80 - conditions: - - prefix: / - - services: - - name: secureapp-default - port: 80 - conditions: - - prefix: /secure - - services: - - name: secureapp - port: 80 - conditions: - - prefix: /secure - - header: - name: User-Agent - contains: Chrome ---- -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: blogsite - namespace: projectcontour-marketing -spec: - routes: - - services: - - name: wwwblog - port: 80 diff --git a/site/content/examples/proxydemo/05-proxy-include-info.yaml b/site/content/examples/proxydemo/05-proxy-include-info.yaml deleted file mode 100644 index fc9b5c67830..00000000000 --- a/site/content/examples/proxydemo/05-proxy-include-info.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: blogsite - namespace: projectcontour-marketing -spec: - includes: - - name: infosite - conditions: - - prefix: /info - routes: - - services: - - name: wwwblog - port: 80 ---- -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: infosite - namespace: projectcontour-marketing -spec: - routes: - - services: - - name: info - port: 80 diff --git a/site/content/examples/proxydemo/06-proxy-include-headers.yaml b/site/content/examples/proxydemo/06-proxy-include-headers.yaml deleted file mode 100644 index a9acc411e1a..00000000000 --- a/site/content/examples/proxydemo/06-proxy-include-headers.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: root - namespace: projectcontour-roots -spec: - virtualhost: - fqdn: local.projectcontour.io - includes: - - name: blogsite - namespace: projectcontour-marketing - conditions: - - prefix: /blog - - header: - name: User-Agent - notcontains: Chrome - - header: - name: User-Agent - notcontains: Firefox - routes: - - services: - - name: rootapp - port: 80 - conditions: - - prefix: / - - services: - - name: secureapp-default - port: 80 - conditions: - - prefix: /secure - - services: - - name: secureapp - port: 80 - conditions: - - prefix: /secure - - header: - name: User-Agent - contains: Chrome diff --git a/site/content/getting-started/_index.md b/site/content/getting-started/_index.md index 0bf1394a5b4..53e89cdc9da 100644 --- a/site/content/getting-started/_index.md +++ b/site/content/getting-started/_index.md @@ -47,16 +47,16 @@ You should see the following: ### Option 2: Helm This option requires [Helm to be installed locally][29]. -Add the bitnami chart repository (which contains the Contour chart) by running the following: +Add projectcontour's chart repository by running the following: ```bash -$ helm repo add bitnami https://charts.bitnami.com/bitnami +$ helm repo add contour https://projectcontour.github.io/helm-charts/ ``` Install the Contour chart by running the following: ```bash -$ helm install my-release bitnami/contour --namespace projectcontour --create-namespace +$ helm install my-release contour/contour --namespace projectcontour --create-namespace ``` Verify Contour is ready by running: @@ -232,8 +232,6 @@ If you encounter issues, review the [troubleshooting][17] page, [file an issue][ [12]: {{< param slack_url >}} [13]: https://projectcontour.io/resources/deprecation-policy/ [14]: /docs/{{< param latest_version >}}/guides/gateway-api -[15]: https://github.com/bitnami/charts/tree/master/bitnami/contour -[16]: https://github.com/helm/charts#%EF%B8%8F-deprecation-and-archive-notice [17]: /docs/{{< param latest_version >}}/troubleshooting [18]: /docs/{{< param latest_version >}}/architecture [19]: https://youtu.be/YA82A4Rcs_A diff --git a/site/content/resources/compatibility-matrix.md b/site/content/resources/compatibility-matrix.md index 502cfe1c5a0..6bb7deaeea2 100644 --- a/site/content/resources/compatibility-matrix.md +++ b/site/content/resources/compatibility-matrix.md @@ -10,10 +10,23 @@ These combinations of versions are specifically tested in CI and supported by th | Contour Version | Envoy Version | Kubernetes Versions | Gateway API Version | | --------------- | :------------------- | ------------------- | --------------------| -| main | [1.35.2][74] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| main | [1.38.1][77] | 1.36, 1.35, 1.34 | [1.3.0][113] | +| 1.33.5 | [1.35.10][80] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| 1.33.4 | [1.35.10][80] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| 1.33.3 | [1.35.9][78] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| 1.33.2 | [1.35.8][74] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| 1.33.1 | [1.35.8][74] | 1.34, 1.33, 1.32 | [1.3.0][113] | | 1.33.0 | [1.35.2][74] | 1.34, 1.33, 1.32 | [1.3.0][113] | +| 1.32.5 | [1.34.14][81] | 1.33, 1.32, 1.31 | [1.2.1][112] | +| 1.32.4 | [1.34.13][79] | 1.33, 1.32, 1.31 | [1.2.1][112] | +| 1.32.3 | [1.34.12][75] | 1.33, 1.32, 1.31 | [1.2.1][112] | +| 1.32.2 | [1.34.12][75] | 1.33, 1.32, 1.31 | [1.2.1][112] | | 1.32.1 | [1.34.4][75] | 1.33, 1.32, 1.31 | [1.2.1][112] | | 1.32.0 | [1.34.1][72] | 1.33, 1.32, 1.31 | [1.2.1][112] | +| 1.31.6 | [1.34.14][81] | 1.32, 1.31, 1.30 | [1.2.1][112] | +| 1.31.5 | [1.34.13][79] | 1.32, 1.31, 1.30 | [1.2.1][112] | +| 1.31.4 | [1.34.12][75] | 1.32, 1.31, 1.30 | [1.2.1][112] | +| 1.31.3 | [1.34.12][75] | 1.32, 1.31, 1.30 | [1.2.1][112] | | 1.31.2 | [1.34.4][75] | 1.32, 1.31, 1.30 | [1.2.1][112] | | 1.31.1 | [1.34.1][72] | 1.32, 1.31, 1.30 | [1.2.1][112] | | 1.31.0 | [1.34.0][66] | 1.32, 1.31, 1.30 | [1.2.1][112] | @@ -236,9 +249,14 @@ __Note:__ This list of extensions was last verified to be complete with Envoy v1 [71]: https://www.envoyproxy.io/docs/envoy/v1.31.6/version_history/v1.31/v1.31.6 [72]: https://www.envoyproxy.io/docs/envoy/v1.34.1/version_history/v1.34/v1.34 [73]: https://www.envoyproxy.io/docs/envoy/v1.31.8/version_history/v1.31/v1.31.8 -[74]: https://www.envoyproxy.io/docs/envoy/v1.35.2/version_history/v1.35/v1.35.2 -[75]: https://www.envoyproxy.io/docs/envoy/v1.34.4/version_history/v1.34/v1.34 +[74]: https://www.envoyproxy.io/docs/envoy/v1.35.8/version_history/v1.35/v1.35 +[75]: https://www.envoyproxy.io/docs/envoy/v1.34.12/version_history/v1.34/v1.34 [76]: https://www.envoyproxy.io/docs/envoy/v1.31.10/version_history/v1.31/v1.31 +[77]: https://www.envoyproxy.io/docs/envoy/v1.38.1/version_history/v1.38/v1.38.1 +[78]: https://www.envoyproxy.io/docs/envoy/v1.35.9/version_history/v1.35/v1.35.9 +[79]: https://www.envoyproxy.io/docs/envoy/v1.34.13/version_history/v1.34/v1.34.13 +[80]: https://www.envoyproxy.io/docs/envoy/v1.35.10/version_history/v1.35/v1.35.10 +[81]: https://www.envoyproxy.io/docs/envoy/v1.34.14/version_history/v1.34/v1.34.14 [98]: https://github.com/kubernetes/client-go [99]: https://github.com/kubernetes/client-go#compatibility-matrix diff --git a/site/content/resources/support.md b/site/content/resources/support.md index 12047e69f53..66840da33f9 100644 --- a/site/content/resources/support.md +++ b/site/content/resources/support.md @@ -7,40 +7,17 @@ This document describes which versions of Contour are supported by the Contour t ## Supported releases -Contour is changing both to quarterly releases and three supported releases. +Contour supports a single release track at a time. New releases are made on-demand rather than following a fixed schedule. -The first Contour version covered by the quarterly release cadence is Contour v1.20, released in Jan 2022. - -At the time it is released, it will be the only supported version, and versions 1.21 and 1.22 will continue supporting back to Contour 1.20. - -When Contour 1.23 releases, Contour 1.20 will fall out of support. - -The following table illustrates how this will work. The given dates are estimates, not guarantees. -They are our best guess as to when each version will be released. - -| Version | v1.19 | v1.20 | v1.21 | v1.22 | v1.23 | -|---------|-------|-------|-------|-------|-------| -| Q4 2021 | ✅ | -| Q1 2022 | ❌ | ✅ | -| Q2 2022 | ❌ | ✅ | ✅ | -| Q3 2022 | ❌ | ✅ | ✅ | ✅ | -| Q4 2022 | ❌ | ❌ | ✅ | ✅ | ✅ | +When a new release is published, the previous release is no longer supported. For example, when Contour 1.34 is released, Contour 1.33 will no longer be supported. ## What does a release being "supported" mean? In short, "supported" means that Contour will issue fixes for security and other critical bugs for that release's supported lifetime. However, the project will require users to upgrade to the most recent patch release for a version to be supported. - -That is: -- The latest patch version in each release is the supported version. -- If you are not running the supported version from your release train, you'll need to upgrade first if you have any problems. -- When a new patch is cut, that will become the supported version for that release. - So, if Contour 1.20.0 is the only supported version, and Contour 1.20.1 is released, then the supported version will change to 1.20.1. -If, later on, the supported versions are 1.21, 1.22, and 1.23, and 1.21.1, 1.22.1, and 1.23.1 are released, then the patch releases will be the only supported versions. - ## Latest version and the `:latest` tag The latest stable release is identified by the [Docker tag `:latest`][1]. `:latest` is an alias for {{< param latest_version >}} which is the current stable release. diff --git a/site/themes/contour/layouts/_default/baseof.html b/site/themes/contour/layouts/_default/baseof.html index 5e0eb876e62..df61d756bfc 100644 --- a/site/themes/contour/layouts/_default/baseof.html +++ b/site/themes/contour/layouts/_default/baseof.html @@ -8,7 +8,7 @@ {{ with .Site.Params.description }}{{ end }} {{ with .Site.Params.author }}{{ end }} {{ $options := (dict "targetPath" "css/style.css" "outputStyle" "compressed" "enableSourceMap" true "includePaths" (slice "node_modules/myscss")) }} - {{ $style := resources.Get "scss/site.scss" | resources.ToCSS $options }} + {{ $style := resources.Get "scss/site.scss" | css.Sass $options }}