From ac85e259bdf8543647d33e8871fa0c1fa14aec2d Mon Sep 17 00:00:00 2001 From: Juncheng Zhu <74894646+junczhu@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:02:57 +0800 Subject: [PATCH 01/65] docs: some improvement in release instructions (#1815) Signed-off-by: junczhu --- RELEASES.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index bbf03b1ac..1069f3f1f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -48,7 +48,8 @@ Applicable fixes, including security fixes, may be backported to supported relea When a minor release is required, the release commits should be merged with the `main` branch when ready. * Alpha and Beta releases will be cut from the main branch. -* For RC and stable releases, a new branch `release-X.Y` will be created from `main`. Required changes for the minor release should be PRed to the `dev` branch, the change will then be cherry picked to `release-X.Y` from `main`.S +* For RC and stable releases, a new branch `release-X.Y` will be created from `main`. +* Required changes for the minor release should be PRed to the `dev` branch, the change will then be cherry picked to `release-X.Y` from `main`. ### Major releases @@ -56,7 +57,10 @@ When a major release is required, the release commits should be merged with the ### Tag and Release -**X.Y.Z** refers to the version (git tag) of Ratify that is released. Prepare the release with a [PR](https://github.com/ratify-project/ratify/pull/1031/files) to update the chart value. When the `release-X.Y` branch is ready, a tag **X.Y.Z** should be pushed. e.g. `git tag v1.1.1` and `git push --tags`. This will trigger a [Goreleaser](https://goreleaser.com/) action that will build the binaries and creates a [GitHub release](https://help.github.com/articles/creating-releases/): +**X.Y.Z** refers to the version (git tag) of Ratify that is released. + +1. Prepare the release with a [PR](https://github.com/ratify-project/ratify/pull/1801/files) to update the chart value. +2. When the `release-X.Y` branch is ready, a tag **X.Y.Z** should be pushed. e.g. `git tag v1.1.1` and `git push --tags`. This will trigger a [Goreleaser](https://goreleaser.com/) action that will build the binaries and creates a [GitHub release](https://help.github.com/articles/creating-releases/): * The release will be marked as a draft to allow an final editing before publishing. * The release notes and other fields can edited after the action completes. The description can be in Markdown. @@ -78,7 +82,7 @@ For example, if Gatekeeper _supported_ versions are v3.13 and v3.14, and Kuberne ## Post Release Activity -After a successful release, please manually trigger [quick start action](.github/quick-start.yml) to validate the quick start test is passing. Validate in the run logs that the version of ratify matches the latest released version. +After a successful release, please prepare a [PR](https://github.com/ratify-project/ratify/pull/1805/files) to update the chart value in `dev` branch. After PR gets merged, manually trigger [quick start action](.github/quick-start.yml) to validate the quick start test is passing. Validate in the run logs that the version of ratify matches the latest released version. ### Weekly Dev Release From d3e49d28ac9525364d4b9955a1d8a40b4eee0996 Mon Sep 17 00:00:00 2001 From: Yi Zha Date: Wed, 25 Sep 2024 11:59:15 +0800 Subject: [PATCH 02/65] chore: update the roadmap after v1.3.0 release (#1817) Signed-off-by: Yi Zha --- ROADMAP.md | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index b98c8f72f..88d6fbf8b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ ## Overview -At Ratify, our mission is to safeguard the container supply chain by ratifying trustworthy and compliant artifacts. We achieve this through a robust and pluggable verification engine that includes built-in verifiers. These verifiers can be customized to validate supply chain metadata associated with artifacts, covering essential aspects such as signatures and attestations (including vulnerability reports, SBOM, provenance data, and VEX documents). As the landscape of supply chain security evolves, we actively develop new verifiers, which can be seamlessly integrated into our verification engine. Additionally, if you have a specific use case, you can create your own verifier following our comprehensive guidance. Each verifier will generate detailed verfication reports, which can be consumed by various policy controllers to enforce policies. +At Ratify, our mission is to safeguard the container supply chain by ratifying trustworthy and compliant artifacts. We achieve this through a robust and pluggable verification engine that includes built-in verifiers. These verifiers can be customized to validate supply chain metadata associated with artifacts, covering essential aspects such as signatures and attestations (including vulnerability reports, SBOM, provenance data, and VEX documents). As the landscape of supply chain security evolves, we actively develop new verifiers, which can be seamlessly integrated into our verification engine. Additionally, if you have a specific use case, you can create your own verifier following our comprehensive guidance. Each verifier will generate detailed verification reports, which can be consumed by various policy controllers to enforce policies. Ratify is designed to address several critical scenarios. It seamlessly integrates with OPA Gatekeeper, acting as the Kubernetes policy controller that shields your cluster from untrustworthy and non-compliant container images. As an external data provider for Gatekeeper, Ratify delivers artifact verification results that are in alignment with defined policies. Additionally, Ratify enhances security at the Kubernetes node level by extending its capabilities to container runtime through its plugin interface, which allows for detailed policy evaluations based on artifact verification outcomes. Lastly, incorporating Ratify into your CI/CD pipeline ensures the trustworthiness and compliance of container images prior to their usage. @@ -60,33 +60,37 @@ See details in [GitHub milestone v1.2.0](https://github.com/ratify-project/ratif ### v1.3 -**Status**: In progress +**Status**: Completed + +**Target date**: Sep 16, 2024 -**Target date**: Aug 30, 2024 +**Release link**: [v1.3.0 Release Notes](https://github.com/ratify-project/ratify/releases/tag/v1.3.0) **Major features** -- Error logs improvements -- Kubernetes multi-tenancy support (Verifying Common images across namespaces) -- Cosign keyless verification using OIDC settings -- Notary Project signature verification with Time-stamping support -- Signing Certificate/key rotation support +- Support of validating Notary Project signatures with timestamping +- Support of periodic retrieval of keys and certificates stored in a Key Management System +- Introducing trust policy configuration for Cosign keyless verification +- Error logs improvements for artifact verification See details in [GitHub milestone v1.3.0](https://github.com/ratify-project/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.3.0). ### v1.4 -**Status**: Tentative +**Status**: In process **Target date**: Nov 30, 2024 **Major features** -- Attestations support -- Ratify supports Azure Trusted Signing as a new KeyManagementProvider -- Use Ratify at container runtime (Preview) +- Enable revocation checking using CRL (Certificate Revocation List) for Notary Project signatures +- Add Trusted Signing as a Key Management Provider +- Support retaining multiple previous versions of certificates/keys in Key Management Provider +- Artifact filtering based on annotations -### v2.0 +See details in [GitHub milestone v1.4.0](https://github.com/ratify-project/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.4.0). + +### v2.x Status: Tentative @@ -94,5 +98,8 @@ Target date: TBD **Major features** -- Use Ratify in CI/CD pipelines (Preview) -- Support CEL as additional policy language \ No newline at end of file +- Attestations support +- Kubernetes multi-tenancy support - Verifying Common images across namespaces +- Use Ratify at container runtime +- Use Ratify in CI/CD pipelines +- Support CEL as additional policy language From 77fbbaf7be5cd4e13b159ee72e6af6514d73d8b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:23:24 +0800 Subject: [PATCH 03/65] chore: Bump github/codeql-action from 3.26.8 to 3.26.9 (#1828) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3f6b73d0c..d7f1f3b82 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@294a9d92911152fe08befb9ec03e240add280cb3 # tag=v3.26.8 + uses: github/codeql-action/init@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 with: languages: go - name: Run tidy @@ -45,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@294a9d92911152fe08befb9ec03e240add280cb3 # tag=v3.26.8 + uses: github/codeql-action/analyze@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 74be38718..6c2ca6320 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -55,6 +55,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@294a9d92911152fe08befb9ec03e240add280cb3 # tag=v3.26.8 + uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 with: sarif_file: results.sarif From 8d727368b6e081f3a7f5d0f5404e505a80223609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:51:41 +0000 Subject: [PATCH 04/65] chore: Bump vscode/devcontainers/go from `44c273a` to `68e6bd3` in /.devcontainer (#1826) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 6734c86e8..5fba6cc1a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -14,7 +14,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go/.devcontainer/base.Dockerfile # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1.22-bullseye, 1.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:44c273a0506ee6c7c13f4f0c1abe8dd077469ac9f3ae6be0617d6c59a1256089 +FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:68e6bd322c03117d54ce8f84de518d7ffce72126d869072ae3b5a72ab84267c9 # [Choice] Node.js version: none, lts/*, 18, 16, 14 ARG NODE_VERSION="none" From 2a7ec4d71ba91000400af02c6cba9cf8e924a14c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 03:27:23 +0000 Subject: [PATCH 05/65] chore: Bump actions/checkout from 4.1.7 to 4.2.0 (#1830) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-pr.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/e2e-aks.yml | 2 +- .github/workflows/e2e-cli.yml | 8 ++++---- .github/workflows/e2e-k8s.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/high-availability.yml | 2 +- .github/workflows/pr-to-main.yml | 2 +- .github/workflows/publish-charts.yml | 2 +- .github/workflows/publish-dev-assets.yml | 2 +- .github/workflows/publish-package.yml | 2 +- .github/workflows/quick-start.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/run-full-validation.yml | 2 +- .github/workflows/scan-vulns.yaml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/sync-gh-pages.yml | 2 +- 17 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index ee56520d5..3fdbebc06 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -75,7 +75,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d7f1f3b82..28cb3f9b9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index cdf2a8304..aac09edb4 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -33,7 +33,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml index 67038445c..4e87c53f8 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Check license header uses: apache/skywalking-eyes/header@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91 with: @@ -39,7 +39,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: @@ -68,7 +68,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: @@ -96,7 +96,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: submodules: recursive - name: Run link check diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index 717ff937c..a21a0c394 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index cb827af40..ccdb30f83 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: "1.22" - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: golangci-lint uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 with: diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index 631f9bb6f..e0e748552 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml index 5fcefe211..cc7da82dc 100644 --- a/.github/workflows/pr-to-main.yml +++ b/.github/workflows/pr-to-main.yml @@ -18,7 +18,7 @@ jobs: egress-policy: audit - name: git checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Get current date id: date run: echo "::set-output name=date::$(date +'%Y-%m-%d')" diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index 850838750..7cae355e7 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -17,7 +17,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Publish Helm charts uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0 with: diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index cf9083917..db93e41ed 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -21,7 +21,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Install Notation uses: notaryproject/notation-action/setup@104aa999103172f827373af8ac14dde7aa6d28f1 # v1.1.0 - name: Install cosign diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index e4d81f984..c908a1423 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -20,7 +20,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: prepare id: prepare run: | diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index f6739f243..dd32dac1b 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e261c1bd6..40b0ae290 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 with: fetch-depth: 0 diff --git a/.github/workflows/run-full-validation.yml b/.github/workflows/run-full-validation.yml index bef82603d..bb69d0286 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -63,7 +63,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index aed9bba20..e890752c5 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -44,7 +44,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Download trivy run: | diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 6c2ca6320..2bbb85a76 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 with: persist-credentials: false diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml index 8c584c7e7..3c2b87113 100644 --- a/.github/workflows/sync-gh-pages.yml +++ b/.github/workflows/sync-gh-pages.yml @@ -21,7 +21,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: everlytic/branch-merge@c4a244dc23143f824ae6c022a10732566cb8e973 with: github_token: ${{ github.token }} From aea7688947de7769d587baec46ca587194cfd8b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:22:59 +1000 Subject: [PATCH 06/65] chore: Bump notaryproject/notation-action from 1.1.0 to 1.2.0 (#1832) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-dev-assets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index db93e41ed..cd0638154 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -23,7 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Install Notation - uses: notaryproject/notation-action/setup@104aa999103172f827373af8ac14dde7aa6d28f1 # v1.1.0 + uses: notaryproject/notation-action/setup@03242349f62aeddc995e12c6fbcea3b87697873f # v1.2.0 - name: Install cosign uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 - name: Az CLI login @@ -118,7 +118,7 @@ jobs: helm push ratify-${{ steps.prepare.outputs.semversion }}.tgz oci://${{ steps.prepare.outputs.chartrepo }} helm push ratify-${{ steps.prepare.outputs.semversionrolling }}.tgz oci://${{ steps.prepare.outputs.chartrepo }} - name: Sign with Notation - uses: notaryproject/notation-action/sign@104aa999103172f827373af8ac14dde7aa6d28f1 # v1.1.0 + uses: notaryproject/notation-action/sign@03242349f62aeddc995e12c6fbcea3b87697873f # v1.2.0 with: plugin_name: azure-kv plugin_url: ${{ vars.AZURE_KV_PLUGIN_URL }} From c260f3b0afccc2f838953585178cbe0b84c456fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:09:30 +0000 Subject: [PATCH 07/65] chore: Bump github.com/aws/aws-sdk-go-v2/credentials from 1.17.34 to 1.17.37 (#1834) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 7256e34b5..b33f28fcb 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 github.com/aws/aws-sdk-go-v2 v1.31.0 github.com/aws/aws-sdk-go-v2/config v1.27.36 - github.com/aws/aws-sdk-go-v2/credentials v1.17.34 + github.com/aws/aws-sdk-go-v2/credentials v1.17.37 github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6 github.com/cespare/xxhash/v2 v2.3.0 github.com/dapr/go-sdk v1.8.0 @@ -144,9 +144,9 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 // indirect github.com/aws/smithy-go v1.21.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index 28ceffc59..4218495c1 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72Qm github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34 h1:gmkk1l/cDGSowPRzkdxYi8edw+gN4HmVK151D/pqGNc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.34/go.mod h1:4R9OEV3tgFMsok4ZeFpExn7zQaZRa9MRGFYnI/xC/vs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.37 h1:G2aOH01yW8X373JK419THj5QVqu9vKEwxSEsGxihoW0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.37/go.mod h1:0ecCjlb7htYCptRD45lXJ6aJDQac6D2NlKGpZqyTG6A= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= @@ -149,12 +149,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3 h1:wLBgq6nDNYdd0A5CvscVAKV5SVlHKOHVPedpgtigATg= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3/go.mod h1:8lETO9lelSG2B6KMXFh2OwPPqGV6WQM3RqLAEjP1xaU= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0 h1:fHySkG0IGj2nepgGJPmmhZYL9ndnsq1Tvc6MeuVQCaQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.0/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0 h1:cU/OeQPNReyMj1JEBgjE29aclYZYtXcsPMXbTkVGMFk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.0/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0 h1:GNVxIHBTi2EgwCxpNiozhNasMOK+ROUA2Z3X+cSBX58= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.0/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 h1:rs4JCczF805+FDv2tRhZ1NU0RB2H6ryAvsWPanAr72Y= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.3/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 h1:S7EPdMVZod8BGKQQPTBK+FcX9g7bKR7c4+HxWqHP7Vg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 h1:VzudTFrDCIDakXtemR7l6Qzt2+JYsVqo2MxBPt5k8T8= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.3/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= From 70e47443f0a2942364481933045d5b398170f195 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:37:57 +0000 Subject: [PATCH 08/65] chore: Bump vscode/devcontainers/go from `68e6bd3` to `d638d11` in /.devcontainer (#1836) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5fba6cc1a..b4c855814 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -14,7 +14,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go/.devcontainer/base.Dockerfile # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1.22-bullseye, 1.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:68e6bd322c03117d54ce8f84de518d7ffce72126d869072ae3b5a72ab84267c9 +FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:d638d1127e6e211c96ef03effd4aacf1c372c97f9ca9ca605af2a61163c16287 # [Choice] Node.js version: none, lts/*, 18, 16, 14 ARG NODE_VERSION="none" From d72b0d76491808f1bf1968f642959023bcf1a637 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 00:39:43 +0000 Subject: [PATCH 09/65] chore: Bump golang from `4594271` to `ddad330` in /httpserver (#1837) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- httpserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index 4ba69f179..e756814c6 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.22@sha256:4594271250150c1a322ed749abfd218e1a8c6eb1ade90872e325a664412e2037 as builder +FROM --platform=$BUILDPLATFORM golang:1.22@sha256:ddad33062f94a276b78c1d536b70d23f5d2548f619e3dd67aa5972bb415fe648 as builder ARG TARGETPLATFORM ARG TARGETOS From 8786419f011b8a77f085cc3bc74319ad0d11d22b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:07:52 +0000 Subject: [PATCH 10/65] chore: Bump distroless/static from `dcd3f1f` to `26f9b99` in /httpserver (#1838) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- httpserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index e756814c6..3f119a31f 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -41,7 +41,7 @@ RUN if [ "$build_licensechecker" = "true" ]; then go build -o /app/out/plugins/ RUN if [ "$build_schemavalidator" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/schemavalidator; fi RUN if [ "$build_vulnerabilityreport" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/vulnerabilityreport; fi -FROM gcr.io/distroless/static:nonroot@sha256:dcd3f1f09adef5689088c9c4d96a8d98c889d8281d3946145074f89eafe7e1af +FROM gcr.io/distroless/static:nonroot@sha256:26f9b99f2463f55f20db19feb4d96eb88b056e0f1be7016bb9296a464a89d772 LABEL org.opencontainers.image.source https://github.com/ratify-project/ratify ARG RATIFY_FOLDER=$HOME/.ratify/ From 036beb96a5028509e86c972b229949af7554f56a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:09:30 +0000 Subject: [PATCH 11/65] chore: Bump github/codeql-action from 3.26.9 to 3.26.10 (#1840) --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 28cb3f9b9..7921bffb1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 + uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 with: languages: go - name: Run tidy @@ -45,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 + uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2bbb85a76..207b6c239 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -55,6 +55,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # tag=v3.26.9 + uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 with: sarif_file: results.sarif From 6bf96b0e8a9c1a5a60167edace54ecb85be96d72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 05:32:36 +0000 Subject: [PATCH 12/65] chore: Bump golang/govulncheck-action from 1.0.3 to 1.0.4 (#1841) --- .github/workflows/scan-vulns.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index e890752c5..8181b0ecf 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -31,7 +31,7 @@ jobs: with: go-version: "1.22" check-latest: true - - uses: golang/govulncheck-action@dd0578b371c987f96d1185abb54344b44352bd58 # v1.0.3 + - uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4 scan_vulnerabilities: name: "[Trivy] Scan for vulnerabilities" From ad5cdcf2fd2377218dd7a585b99a08b8c1253102 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:01:08 +0000 Subject: [PATCH 13/65] chore: Bump codecov/codecov-action from 4.5.0 to 4.6.0 (#1842) --- .github/workflows/e2e-cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml index 4e87c53f8..c921aa68c 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -51,7 +51,7 @@ jobs: - name: Check build run: bin/ratify version - name: Upload coverage to codecov.io - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: token: ${{ secrets.CODECOV_TOKEN }} - name: Run helm lint @@ -84,7 +84,7 @@ jobs: make install ratify-config install-bats make test-e2e-cli GOCOVERDIR=${GITHUB_WORKSPACE}/test/e2e/.cover - name: Upload coverage to codecov.io - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 with: token: ${{ secrets.CODECOV_TOKEN }} markdown-link-check: From b94c067c5cac21837aff3f29718327b9a475f1a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 23:56:38 -0700 Subject: [PATCH 14/65] chore: Bump golangci/golangci-lint-action from 6.1.0 to 6.1.1 (#1845) --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ccdb30f83..d4e542e44 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -24,7 +24,7 @@ jobs: go-version: "1.22" - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: golangci-lint - uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: version: v1.59.1 args: --timeout=10m From 5327afe7b8d127810135fa4a50b3d6c89e3474b2 Mon Sep 17 00:00:00 2001 From: Susan Shi Date: Sun, 6 Oct 2024 23:26:54 -0700 Subject: [PATCH 15/65] docs: add commits doc to contributing guide (#1844) Signed-off-by: huish@microsoft.com --- CONTRIBUTING.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5acefcd1a..b634fac3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,15 @@ Welcome! We are very happy to accept community contributions to Ratify, whether those are [Pull Requests](#pull-requests), [Plugins](#plugins), [Feature Suggestions](#feature-suggestions) or [Bug Reports](#bug-reports)! Please note that by participating in this project, you agree to abide by the [Code of Conduct](./CODE_OF_CONDUCT.md), as well as the terms of the [CLA](#cla). +## Table of Contents +- [Getting Started](#getting-started) +- [Feature Areas](#feature-areas) +- [Feature Enhancements](#feature-enhancements) +- [Feature Suggestions](#feature-suggestions) +- [Bug Reports](#bug-reports) +- [Developing](#developing) +- [Pull Requests](#pull-requests) + ## Getting Started * If you don't already have it, you will need [go](https://golang.org/dl/) v1.16+ installed locally to build the project. @@ -12,7 +21,6 @@ Welcome! We are very happy to accept community contributions to Ratify, whether ## Feature Enhancements For non-trivial enhancements or bug fixes, please start by raising a document PR. You can refer to the example [here](https://github.com/ratify-project/ratify/blame/dev/docs/proposals/Release-Supply-Chain-Metadata.md). - Major user experience updates should be documented in [/doc/proposals](https://github.com/ratify-project/ratify/tree/dev/docs/proposals). Changes to technical implementation should be added to [/doc/design](https://github.com/ratify-project/ratify/tree/dev/docs/design). Consider adding the following section where applicable: @@ -45,6 +53,18 @@ If the PR contains a regression that could not pass the full validation, please 3. Follow the same process to get this PR gets merged into `dev`. 4. Work on the fix and follow the above PR process. +### Commit + +You should follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) to write commit message. As the Ratify Project repositories enforces the [DCO (Developer Certificate of Origin)](https://github.com/apps/dco) on Pull Requests, contributors are required to sign off that they adhere to those requirements by adding a `Signed-off-by` line to the commit messages. Git has even provided a `-s` command line option to append that automatically to your commit messages, please use it when you commit your changes. + +The Ratify Project repositories require signed commits, please refer to [SSH commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification) on signing commits using SSH as it is easy to set up. You can find other methods to sign commits in the document [commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification). Git has provided a `-S` flag to create a signed commit. + +An example of `git commit` command: + +```shell +git commit -s -S -m +``` + ## Developing ### Components @@ -71,6 +91,7 @@ The Ratify project is composed of the following main components: ### Debugging Ratify with VS Code Ratify can run through cli command or run as a http server. Create a [launch.json](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) file in the .vscode directory, then hit F5 to debug. Note the first debug session may take a few minutes to load, subsequent session will be much faster. +A demo of VS Code debugging experience is available from ratify community meeting [recording](https://youtu.be/o5ufkZRDiIg?si=mzSw5XHPxBJmgq8i&t=2793) min 46:33. Here is a sample json for cli. Note that for the following sample json to successfully work, you need to make sure that `verificationCerts` attribute of the verifier in your config file points to the notation verifier's certificate file. In order to do that, you can download the cert file with the following command: `curl -sSLO https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt`, From 6fd804fcf400c388d14a0c21fec84b8fe8037adc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:44:42 +0000 Subject: [PATCH 16/65] chore: Bump github/codeql-action from 3.26.10 to 3.26.11 (#1846) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7921bffb1..5299258b7 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 + uses: github/codeql-action/init@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 with: languages: go - name: Run tidy @@ -45,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 + uses: github/codeql-action/analyze@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 207b6c239..9c7918d76 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -55,6 +55,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # tag=v3.26.10 + uses: github/codeql-action/upload-sarif@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 with: sarif_file: results.sarif From 8162d6aa42b97bb2c11f7676be1cfc6e24cf84d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:16:59 +0000 Subject: [PATCH 17/65] chore: Bump golang from `ddad330` to `628529a` in /httpserver (#1847) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- httpserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index 3f119a31f..1748ecbe6 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.22@sha256:ddad33062f94a276b78c1d536b70d23f5d2548f619e3dd67aa5972bb415fe648 as builder +FROM --platform=$BUILDPLATFORM golang:1.22@sha256:628529a29f130a8ab336b994be99d134ce98cd23b8f2052d8995678681e97ca2 as builder ARG TARGETPLATFORM ARG TARGETOS From 1af700185390a10cf9e099a31a4f9113461dcc04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:44:33 +0000 Subject: [PATCH 18/65] chore: Bump vscode/devcontainers/go from `d638d11` to `bdecb4c` in /.devcontainer (#1848) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b4c855814..3b534a9b0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -14,7 +14,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go/.devcontainer/base.Dockerfile # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1.22-bullseye, 1.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:d638d1127e6e211c96ef03effd4aacf1c372c97f9ca9ca605af2a61163c16287 +FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:bdecb4ca0d168e7bd73b01e475d017aac0888ee22c7d4998a09858ab95157669 # [Choice] Node.js version: none, lts/*, 18, 16, 14 ARG NODE_VERSION="none" From b41acf8ba80dc615406bba6ee324f1177328750b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:25:51 +0000 Subject: [PATCH 19/65] chore: Bump github.com/aws/aws-sdk-go-v2/config from 1.27.36 to 1.27.41 (#1852) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 24 ++++++++++++------------ go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index b33f28fcb..cd1900994 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,9 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 - github.com/aws/aws-sdk-go-v2 v1.31.0 - github.com/aws/aws-sdk-go-v2/config v1.27.36 - github.com/aws/aws-sdk-go-v2/credentials v1.17.37 + github.com/aws/aws-sdk-go-v2 v1.32.0 + github.com/aws/aws-sdk-go-v2/config v1.27.41 + github.com/aws/aws-sdk-go-v2/credentials v1.17.39 github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6 github.com/cespare/xxhash/v2 v2.3.0 github.com/dapr/go-sdk v1.8.0 @@ -82,7 +82,7 @@ require ( github.com/aliyun/credentials-go v1.3.1 // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.31.3 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect @@ -139,15 +139,15 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 // indirect - github.com/aws/smithy-go v1.21.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.0 // indirect + github.com/aws/smithy-go v1.22.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/bshuster-repo/logrus-logstash-hook v1.1.0 diff --git a/go.sum b/go.sum index 4218495c1..9d778c712 100644 --- a/go.sum +++ b/go.sum @@ -125,38 +125,38 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/config v1.27.36 h1:4IlvHh6Olc7+61O1ktesh0jOcqmq/4WG6C2Aj5SKXy0= -github.com/aws/aws-sdk-go-v2/config v1.27.36/go.mod h1:IiBpC0HPAGq9Le0Xxb1wpAKzEfAQ3XlYgJLYKEVYcfw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.37 h1:G2aOH01yW8X373JK419THj5QVqu9vKEwxSEsGxihoW0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.37/go.mod h1:0ecCjlb7htYCptRD45lXJ6aJDQac6D2NlKGpZqyTG6A= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2 v1.32.0 h1:GuHp7GvMN74PXD5C97KT5D87UhIy4bQPkflQKbfkndg= +github.com/aws/aws-sdk-go-v2 v1.32.0/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2/config v1.27.41 h1:esG3WpmEuNJ6F4kVFLumN8nCfA5VBav1KKb3JPx83O4= +github.com/aws/aws-sdk-go-v2/config v1.27.41/go.mod h1:haUg09ebP+ClvPjU3EB/xe0HF9PguO19PD2fdjM2X14= +github.com/aws/aws-sdk-go-v2/credentials v1.17.39 h1:tmVexAhoGqJxNE2oc4/SJqL+Jz1x1iCPt5ts9XcqZCU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.39/go.mod h1:zgOdbDI9epE608PdboJ87CYvPIejAgFevazeJW6iauQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15 h1:kGjlNc2IXXcxPDcfMyCshNCjVgxUhC/vTJv7NvC9wKk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15/go.mod h1:rk/HmqPo+dX0Uv0Q1+4w3QKFdICEGSsTYz1hRWvH8UI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19 h1:Q/k5wCeJkSWs+62kDfOillkNIJ5NqmE3iOfm48g/W8c= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19/go.mod h1:Wns1C66VvtA2Bv/cUBuKZKQKdjo7EVMhp90aAa+8oTI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19 h1:AYLE0lUfKvN6icFTR/p+NmD1amYKTbqHQ1Nm+jwE6BM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19/go.mod h1:1giLakj64GjuH1NBzF/DXqly5DWHtMTaOzRZ53nFX0I= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6 h1:CnQNpQv+WGl5aECyAXrJ4w+Qccz2aC/uXg2OjxiPl30= github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6/go.mod h1:1FKdZMR/Tfx40IKjdLDRlFz/UKlff8CKQuC7mhlTAMM= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.7 h1:dsmihXaPkhFuUTiL+ygm9RtUYEmhOeIl7DXNIHCoKDg= github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.7/go.mod h1:g7If3uXj+mKcmIuxh08qh8I9ju6f/aOSWMyc6hEEi58= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0 h1:AdbiDUgQZmM28rDIZbiSwFxz8+3B94aOXxzs6oH+EA0= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0/go.mod h1:uV476Bd80tiDTX4X2redMtagQUg65aU/gzPojSJ4kSI= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3 h1:wLBgq6nDNYdd0A5CvscVAKV5SVlHKOHVPedpgtigATg= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3/go.mod h1:8lETO9lelSG2B6KMXFh2OwPPqGV6WQM3RqLAEjP1xaU= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 h1:rs4JCczF805+FDv2tRhZ1NU0RB2H6ryAvsWPanAr72Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.3/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 h1:S7EPdMVZod8BGKQQPTBK+FcX9g7bKR7c4+HxWqHP7Vg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 h1:VzudTFrDCIDakXtemR7l6Qzt2+JYsVqo2MxBPt5k8T8= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.3/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.0 h1:71FvP6XFj53NK+YiAEGVzeiccLVeFnHOCvMig0zOHsE= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.0/go.mod h1:UVJqtKXSd9YppRKgdBIkyv7qgbSGv5DchM3yX0BN2mU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0 h1:Uco4o19bi3AmBapImNzuMk+rfzlui52BDyVK1UfJeRA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0/go.mod h1:+HLFhCpnG08hBee8bUdfd1mBK+rFKPt4O5igR9lXDfk= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.0 h1:GiQUjZM2KUZX68o/LpZ1xqxYMuvoxpRrOwYARYog3vc= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.0/go.mod h1:dKnu7M4MAS2SDlng1ytxd03H+y0LoUfEQ5E2VaaSw/4= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 96fb63db0abb710d647360ae7765aec80d8ed5bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:56:42 -0700 Subject: [PATCH 20/65] chore: Bump google.golang.org/grpc from 1.66.2 to 1.66.3 (#1850) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cd1900994..47064879b 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 go.opentelemetry.io/otel/sdk/metric v1.27.0 golang.org/x/sync v0.8.0 - google.golang.org/grpc v1.66.2 + google.golang.org/grpc v1.66.3 google.golang.org/protobuf v1.34.2 k8s.io/api v0.28.14 k8s.io/apimachinery v0.28.14 diff --git a/go.sum b/go.sum index 9d778c712..cbf0082b8 100644 --- a/go.sum +++ b/go.sum @@ -941,8 +941,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi 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.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.66.3 h1:TWlsh8Mv0QI/1sIbs1W36lqRclxrmF+eFJ4DbI0fuhA= +google.golang.org/grpc v1.66.3/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 7ce62b74ec6309833dab505ec82ba04d21781589 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:23:55 +0000 Subject: [PATCH 21/65] chore: Bump sigstore/cosign-installer from 3.6.0 to 3.7.0 (#1851) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-cosign-sample.yml | 2 +- .github/workflows/publish-dev-assets.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-cosign-sample.yml b/.github/workflows/publish-cosign-sample.yml index 566c27885..36f3a897c 100644 --- a/.github/workflows/publish-cosign-sample.yml +++ b/.github/workflows/publish-cosign-sample.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: Install cosign - uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - name: Get repo run: | diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index cd0638154..8b4ce2b72 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -25,7 +25,7 @@ jobs: - name: Install Notation uses: notaryproject/notation-action/setup@03242349f62aeddc995e12c6fbcea3b87697873f # v1.2.0 - name: Install cosign - uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - name: Az CLI login uses: azure/login@a65d910e8af852a8061c627c456678983e180302 # v2.2.0 with: From 2b1890bc62aac60c4271f684d2d67c825a8b8538 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:11:13 +0800 Subject: [PATCH 22/65] chore: Bump actions/upload-artifact from 4.4.0 to 4.4.1 (#1855) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-aks.yml | 2 +- .github/workflows/e2e-k8s.yml | 2 +- .github/workflows/high-availability.yml | 2 +- .github/workflows/quick-start.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index aac09edb4..c14903445 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -64,7 +64,7 @@ jobs: make e2e-aks KUBERNETES_VERSION=${{ inputs.k8s_version }} GATEKEEPER_VERSION=${{ inputs.gatekeeper_version }} TENANT_ID=${{ secrets.AZURE_TENANT_ID }} AZURE_SP_OBJECT_ID=${{ secrets.AZURE_SP_OBJECT_ID }} - name: Upload artifacts - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 if: ${{ always() }} with: name: e2e-logs-aks-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index a21a0c394..bb7de39ca 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -65,7 +65,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json - name: Upload artifacts - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 if: ${{ always() }} with: name: e2e-logs-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index e0e748552..6cc0a650f 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -60,7 +60,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.DAPR_VERSION }}.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.DAPR_VERSION }}.json - name: Upload artifacts - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 if: ${{ always() }} with: name: e2e-logs-${{ matrix.DAPR_VERSION }} diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index dd32dac1b..84df1e3b2 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -59,7 +59,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-config-policy.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-config-policy.json - name: Upload artifacts - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 if: ${{ always() }} with: name: e2e-logs-${{ matrix.KUBERNETES_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40b0ae290..1906eeb3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: $RUNNER_TEMP/sbom-tool generate -b . -bc . -pn ratify -pv $GITHUB_REF_NAME -ps Microsoft -nsb https://microsoft.com -V Verbose - name: Upload a Build Artifact - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # tag=v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # tag=v4.4.1 with: name: SBOM SPDX files path: _manifest/spdx_2.2/** diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 9c7918d76..494a17fee 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -48,7 +48,7 @@ jobs: publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # tag=v4.4.0 + uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # tag=v4.4.1 with: name: SARIF file path: results.sarif From f6fae7ec860904b8e123958fb44e5939b8034b89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 19:38:49 +0800 Subject: [PATCH 23/65] chore: Bump actions/checkout from 4.2.0 to 4.2.1 (#1857) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-pr.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/e2e-aks.yml | 2 +- .github/workflows/e2e-cli.yml | 8 ++++---- .github/workflows/e2e-k8s.yml | 2 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/high-availability.yml | 2 +- .github/workflows/pr-to-main.yml | 2 +- .github/workflows/publish-charts.yml | 2 +- .github/workflows/publish-dev-assets.yml | 2 +- .github/workflows/publish-package.yml | 2 +- .github/workflows/quick-start.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/run-full-validation.yml | 2 +- .github/workflows/scan-vulns.yaml | 2 +- .github/workflows/scorecards.yml | 2 +- .github/workflows/sync-gh-pages.yml | 2 +- 17 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 3fdbebc06..3f5195ca9 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -75,7 +75,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5299258b7..3d57cb9fa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # tag=3.0.2 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index c14903445..85a3b81d5 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -33,7 +33,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml index c921aa68c..08a265249 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Check license header uses: apache/skywalking-eyes/header@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91 with: @@ -39,7 +39,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: @@ -68,7 +68,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: @@ -96,7 +96,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: submodules: recursive - name: Run link check diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index bb7de39ca..9fae3941e 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index d4e542e44..f6c1aba55 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: "1.22" - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: golangci-lint uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index 6cc0a650f..9cd72b8d7 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml index cc7da82dc..fb7938dba 100644 --- a/.github/workflows/pr-to-main.yml +++ b/.github/workflows/pr-to-main.yml @@ -18,7 +18,7 @@ jobs: egress-policy: audit - name: git checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Get current date id: date run: echo "::set-output name=date::$(date +'%Y-%m-%d')" diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index 7cae355e7..45338d088 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -17,7 +17,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Publish Helm charts uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0 with: diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index 8b4ce2b72..30b9b6c9e 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -21,7 +21,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Install Notation uses: notaryproject/notation-action/setup@03242349f62aeddc995e12c6fbcea3b87697873f # v1.2.0 - name: Install cosign diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index c908a1423..6a1499ecb 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -20,7 +20,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: prepare id: prepare run: | diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index 84df1e3b2..82c6444d1 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: setup go environment uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1906eeb3f..ab9e8c0f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # tag=3.0.2 with: fetch-depth: 0 diff --git a/.github/workflows/run-full-validation.yml b/.github/workflows/run-full-validation.yml index bb69d0286..1584a2d3f 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -63,7 +63,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index 8181b0ecf..b2ead2507 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -44,7 +44,7 @@ jobs: egress-policy: audit - name: Check out code into the Go module directory - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - name: Download trivy run: | diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 494a17fee..5af7a571f 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # tag=3.0.2 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # tag=3.0.2 with: persist-credentials: false diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml index 3c2b87113..0990fc29b 100644 --- a/.github/workflows/sync-gh-pages.yml +++ b/.github/workflows/sync-gh-pages.yml @@ -21,7 +21,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - uses: everlytic/branch-merge@c4a244dc23143f824ae6c022a10732566cb8e973 with: github_token: ${{ github.token }} From 1ecd21fe00098f8662811db6d24387a97a7a0719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:08:00 +0800 Subject: [PATCH 24/65] chore: Bump github/codeql-action from 3.26.11 to 3.26.12 (#1856) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3d57cb9fa..cda4a9af4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 + uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 with: languages: go - name: Run tidy @@ -45,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 + uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 5af7a571f..d0e8bc59a 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -55,6 +55,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # tag=v3.26.11 + uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 with: sarif_file: results.sarif From 957207b9312c49dc499495cba519ae700c8adacf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:57:04 +0800 Subject: [PATCH 25/65] chore: Bump github/codeql-action from 3.26.12 to 3.26.13 (#1869) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecards.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cda4a9af4..5a92c3ad1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # tag=v3.26.13 with: languages: go - name: Run tidy @@ -45,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # tag=v3.26.13 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index d0e8bc59a..a1b98ce7c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -55,6 +55,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # tag=v3.26.12 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # tag=v3.26.13 with: sarif_file: results.sarif From 6f96ebce4e0cb6e758455f4e8fa7d80d24485f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:24:45 +0000 Subject: [PATCH 26/65] chore: Bump golang from `628529a` to `b274ff1` in /httpserver (#1865) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- httpserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index 1748ecbe6..39ee6444c 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.22@sha256:628529a29f130a8ab336b994be99d134ce98cd23b8f2052d8995678681e97ca2 as builder +FROM --platform=$BUILDPLATFORM golang:1.22@sha256:b274ff14d8eb9309b61b1a45333bf0559a554ebcf6732fa2012dbed9b01ea56f as builder ARG TARGETPLATFORM ARG TARGETOS From 4ed4425934e2e9f88665f56ea63c2692d04213df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:18:43 +0000 Subject: [PATCH 27/65] chore: Bump actions/upload-artifact from 4.4.1 to 4.4.3 (#1859) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/e2e-aks.yml | 2 +- .github/workflows/e2e-k8s.yml | 2 +- .github/workflows/high-availability.yml | 2 +- .github/workflows/quick-start.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/scorecards.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index 85a3b81d5..0ce8950a1 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -64,7 +64,7 @@ jobs: make e2e-aks KUBERNETES_VERSION=${{ inputs.k8s_version }} GATEKEEPER_VERSION=${{ inputs.gatekeeper_version }} TENANT_ID=${{ secrets.AZURE_TENANT_ID }} AZURE_SP_OBJECT_ID=${{ secrets.AZURE_SP_OBJECT_ID }} - name: Upload artifacts - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: ${{ always() }} with: name: e2e-logs-aks-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index 9fae3941e..be26f5362 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -65,7 +65,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json - name: Upload artifacts - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: ${{ always() }} with: name: e2e-logs-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index 9cd72b8d7..e9e576851 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -60,7 +60,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.DAPR_VERSION }}.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.DAPR_VERSION }}.json - name: Upload artifacts - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: ${{ always() }} with: name: e2e-logs-${{ matrix.DAPR_VERSION }} diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index 82c6444d1..3d9b3aa6e 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -59,7 +59,7 @@ jobs: kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-config-policy.json kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-config-policy.json - name: Upload artifacts - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: ${{ always() }} with: name: e2e-logs-${{ matrix.KUBERNETES_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab9e8c0f6..0b692df82 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: $RUNNER_TEMP/sbom-tool generate -b . -bc . -pn ratify -pv $GITHUB_REF_NAME -ps Microsoft -nsb https://microsoft.com -V Verbose - name: Upload a Build Artifact - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # tag=v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # tag=v4.4.3 with: name: SBOM SPDX files path: _manifest/spdx_2.2/** diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a1b98ce7c..a45432923 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -48,7 +48,7 @@ jobs: publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # tag=v4.4.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # tag=v4.4.3 with: name: SARIF file path: results.sarif From 85781ccba706e460c8f33109f4a95f8fbe4b9f3d Mon Sep 17 00:00:00 2001 From: Maneesh Singh Date: Tue, 15 Oct 2024 21:38:42 -0700 Subject: [PATCH 28/65] feat: additional env vars for ratify container via helm chart (#1854) Signed-off-by: Maneesh Singh --- charts/ratify/README.md | 1 + charts/ratify/templates/deployment.yaml | 3 +++ charts/ratify/values.yaml | 7 ++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/charts/ratify/README.md b/charts/ratify/README.md index af3600209..571bfa141 100644 --- a/charts/ratify/README.md +++ b/charts/ratify/README.md @@ -47,6 +47,7 @@ Values marked `# DEPRECATED` in the `values.yaml` as well as **DEPRECATED** in t | replicaCount | The number of Ratify replicas in deployment | 1 | | affinity | Pod affinity for the Ratify deployment | `{}` | | tolerations | Pod tolerations for the Ratify deployment | `[]` | +| env | Environment variables for Ratify container | `[]` | | notationCerts | An array of public certificate/certificate chain used to create inline certstore used by Notation verifier | `` | | cosignKeys | An array of public keys used to create inline key management providers used by Cosign verifier | `[]` | | notation.enabled | Enables/disables the built-in notation verifier. MUST be set to true for notation verification. | `true` | diff --git a/charts/ratify/templates/deployment.yaml b/charts/ratify/templates/deployment.yaml index 7a979ca43..46ed544ae 100644 --- a/charts/ratify/templates/deployment.yaml +++ b/charts/ratify/templates/deployment.yaml @@ -110,6 +110,9 @@ spec: readOnly: true {{- end }} env: + {{- with .Values.env }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- if .Values.logger.level }} - name: RATIFY_LOG_LEVEL value: {{ .Values.logger.level }} diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml index ee7c82d41..348736e9b 100644 --- a/charts/ratify/values.yaml +++ b/charts/ratify/values.yaml @@ -169,4 +169,9 @@ akvCertConfig: # DEPRECATED: Use azurekeyvault instead cert2Name: # DEPRECATED: Use azurekeyvault.certificates instead cert2Version: # DEPRECATED: Use azurekeyvault.certificates instead certificates: # DEPRECATED: Use azurekeyvault.certificates instead - tenantId: # DEPRECATED: Use azurekeyvault.tenantId instead \ No newline at end of file + tenantId: # DEPRECATED: Use azurekeyvault.tenantId instead + +# env: environment variables for ratify container +env: [] +# - name: https_proxy +# value: http://proxy-server:80 From 6376762bd4b58ac0278e655ca66bfdfca74adf1c Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 16 Oct 2024 13:05:56 +0800 Subject: [PATCH 29/65] ci: replace trivy with trivy-action (#1871) Signed-off-by: Binbin Li --- .github/workflows/scan-vulns.yaml | 70 ++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index b2ead2507..ad2d2fb54 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -37,6 +37,8 @@ jobs: name: "[Trivy] Scan for vulnerabilities" runs-on: ubuntu-22.04 timeout-minutes: 15 + env: + TRIVY_VERSION: v0.49.1 steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 @@ -46,30 +48,58 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - - name: Download trivy - run: | - pushd $(mktemp -d) - wget https://github.com/aquasecurity/trivy/releases/download/v${{ env.TRIVY_VERSION }}/trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz - tar zxvf trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz - echo "$(pwd)" >> $GITHUB_PATH - env: - TRIVY_VERSION: "0.46.0" + - name: Manual Trivy Setup + uses: aquasecurity/setup-trivy@eadb05c36f891dc855bba00f67174a1e61528cd4 # v0.2.1 + with: + cache: true + version: ${{ env.TRIVY_VERSION }} - name: Run trivy on git repository - run: | - trivy fs --format table --ignore-unfixed --scanners vuln . + uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + with: + scan-type: 'fs' + scan-ref: '.' + ignore-unfixed: true + scanners: 'vuln' + version: ${{ env.TRIVY_VERSION }} - name: Build docker images run: | make e2e-build-local-ratify-image make e2e-build-crd-image - - name: Run trivy on images for all severity - run: | - for img in "localbuild:test" "localbuildcrd:test"; do - trivy image --ignore-unfixed --vuln-type="os,library" "${img}" - done - - name: Run trivy on images and exit on HIGH severity - run: | - for img in "localbuild:test" "localbuildcrd:test"; do - trivy image --ignore-unfixed --exit-code 1 --severity HIGH --vuln-type="os,library" "${img}" - done + + - name: Run Trivy vulnerability scanner on localbuild:test + uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + with: + scan-type: 'image' + image-ref: 'localbuild:test' + ignore-unfixed: true + version: ${{ env.TRIVY_VERSION }} + + - name: Run Trivy vulnerability scanner on localbuildcrd:test + uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + with: + scan-type: 'image' + image-ref: 'localbuildcrd:test' + ignore-unfixed: true + version: ${{ env.TRIVY_VERSION }} + + - name: Run Trivy vulnerability scanner on localbuild:test and exit on HIGH severity + uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + with: + scan-type: 'image' + image-ref: 'localbuild:test' + ignore-unfixed: true + severity: 'HIGH,CRITICAL' + exit-code: '1' + version: ${{ env.TRIVY_VERSION }} + + - name: Run Trivy vulnerability scanner on localbuildcrd:test and exit on HIGH severity + uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + with: + scan-type: 'image' + image-ref: 'localbuildcrd:test' + ignore-unfixed: true + severity: 'HIGH,CRITICAL' + exit-code: '1' + version: ${{ env.TRIVY_VERSION }} \ No newline at end of file From 130194feca08fe937e69111caee0aebb40d65775 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:55:55 +0800 Subject: [PATCH 30/65] chore: Bump anchore/sbom-action from 0.17.2 to 0.17.4 (#1872) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b692df82..3d95884f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - name: Install Syft - uses: anchore/sbom-action/download-syft@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2 + uses: anchore/sbom-action/download-syft@8d0a6505bf28ced3e85154d13dc6af83299e13f1 # v0.17.4 - name: Set up Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 From 1757d2a2b249e507cb9082959c9f0b35a34d9b5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:24:45 +0800 Subject: [PATCH 31/65] chore: Bump aquasecurity/trivy-action from 0.27.0 to 0.28.0 (#1873) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/scan-vulns.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index ad2d2fb54..a4182aa7e 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -55,7 +55,7 @@ jobs: version: ${{ env.TRIVY_VERSION }} - name: Run trivy on git repository - uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: scan-type: 'fs' scan-ref: '.' @@ -69,7 +69,7 @@ jobs: make e2e-build-crd-image - name: Run Trivy vulnerability scanner on localbuild:test - uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: scan-type: 'image' image-ref: 'localbuild:test' @@ -77,7 +77,7 @@ jobs: version: ${{ env.TRIVY_VERSION }} - name: Run Trivy vulnerability scanner on localbuildcrd:test - uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: scan-type: 'image' image-ref: 'localbuildcrd:test' @@ -85,7 +85,7 @@ jobs: version: ${{ env.TRIVY_VERSION }} - name: Run Trivy vulnerability scanner on localbuild:test and exit on HIGH severity - uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: scan-type: 'image' image-ref: 'localbuild:test' @@ -95,7 +95,7 @@ jobs: version: ${{ env.TRIVY_VERSION }} - name: Run Trivy vulnerability scanner on localbuildcrd:test and exit on HIGH severity - uses: aquasecurity/trivy-action@5681af892cd0f4997658e2bacc62bd0a894cf564 # 0.27.0 + uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 with: scan-type: 'image' image-ref: 'localbuildcrd:test' From deafb4aaa456c2ef4c535d0d1463ff6c95f6ce8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:52:24 +0000 Subject: [PATCH 32/65] chore: Bump github.com/aws/aws-sdk-go-v2/config from 1.27.41 to 1.27.43 (#1861) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 20 ++++++++++---------- go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index 47064879b..49cfc2ea3 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,9 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 - github.com/aws/aws-sdk-go-v2 v1.32.0 - github.com/aws/aws-sdk-go-v2/config v1.27.41 - github.com/aws/aws-sdk-go-v2/credentials v1.17.39 + github.com/aws/aws-sdk-go-v2 v1.32.2 + github.com/aws/aws-sdk-go-v2/config v1.27.43 + github.com/aws/aws-sdk-go-v2/credentials v1.17.41 github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6 github.com/cespare/xxhash/v2 v2.3.0 github.com/dapr/go-sdk v1.8.0 @@ -139,14 +139,14 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/OneOfOne/xxhash v1.2.8 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.0 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.32.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect diff --git a/go.sum b/go.sum index cbf0082b8..e99841334 100644 --- a/go.sum +++ b/go.sum @@ -125,18 +125,18 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.51.6 h1:Ld36dn9r7P9IjU8WZSaswQ8Y/XUCRpewim5980DwYiU= github.com/aws/aws-sdk-go v1.51.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.32.0 h1:GuHp7GvMN74PXD5C97KT5D87UhIy4bQPkflQKbfkndg= -github.com/aws/aws-sdk-go-v2 v1.32.0/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2/config v1.27.41 h1:esG3WpmEuNJ6F4kVFLumN8nCfA5VBav1KKb3JPx83O4= -github.com/aws/aws-sdk-go-v2/config v1.27.41/go.mod h1:haUg09ebP+ClvPjU3EB/xe0HF9PguO19PD2fdjM2X14= -github.com/aws/aws-sdk-go-v2/credentials v1.17.39 h1:tmVexAhoGqJxNE2oc4/SJqL+Jz1x1iCPt5ts9XcqZCU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.39/go.mod h1:zgOdbDI9epE608PdboJ87CYvPIejAgFevazeJW6iauQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15 h1:kGjlNc2IXXcxPDcfMyCshNCjVgxUhC/vTJv7NvC9wKk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.15/go.mod h1:rk/HmqPo+dX0Uv0Q1+4w3QKFdICEGSsTYz1hRWvH8UI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19 h1:Q/k5wCeJkSWs+62kDfOillkNIJ5NqmE3iOfm48g/W8c= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.19/go.mod h1:Wns1C66VvtA2Bv/cUBuKZKQKdjo7EVMhp90aAa+8oTI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19 h1:AYLE0lUfKvN6icFTR/p+NmD1amYKTbqHQ1Nm+jwE6BM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.19/go.mod h1:1giLakj64GjuH1NBzF/DXqly5DWHtMTaOzRZ53nFX0I= +github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= +github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2/config v1.27.43 h1:p33fDDihFC390dhhuv8nOmX419wjOSDQRb+USt20RrU= +github.com/aws/aws-sdk-go-v2/config v1.27.43/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/service/ecr v1.28.6 h1:CnQNpQv+WGl5aECyAXrJ4w+Qccz2aC/uXg2OjxiPl30= @@ -145,16 +145,16 @@ github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.7 h1:dsmihXaPkhFuUTiL+ygm9R github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.23.7/go.mod h1:g7If3uXj+mKcmIuxh08qh8I9ju6f/aOSWMyc6hEEi58= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0 h1:AdbiDUgQZmM28rDIZbiSwFxz8+3B94aOXxzs6oH+EA0= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.0/go.mod h1:uV476Bd80tiDTX4X2redMtagQUg65aU/gzPojSJ4kSI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3 h1:wLBgq6nDNYdd0A5CvscVAKV5SVlHKOHVPedpgtigATg= github.com/aws/aws-sdk-go-v2/service/kms v1.31.3/go.mod h1:8lETO9lelSG2B6KMXFh2OwPPqGV6WQM3RqLAEjP1xaU= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.0 h1:71FvP6XFj53NK+YiAEGVzeiccLVeFnHOCvMig0zOHsE= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.0/go.mod h1:UVJqtKXSd9YppRKgdBIkyv7qgbSGv5DchM3yX0BN2mU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0 h1:Uco4o19bi3AmBapImNzuMk+rfzlui52BDyVK1UfJeRA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.0/go.mod h1:+HLFhCpnG08hBee8bUdfd1mBK+rFKPt4O5igR9lXDfk= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.0 h1:GiQUjZM2KUZX68o/LpZ1xqxYMuvoxpRrOwYARYog3vc= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.0/go.mod h1:dKnu7M4MAS2SDlng1ytxd03H+y0LoUfEQ5E2VaaSw/4= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 h1:SoFYaT9UyGkR0+nogNyD/Lj+bsixB+SNuAS4ABlEs6M= From 7213fed17448ae8bcffaaeeae3d422668674d340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 05:48:05 +0000 Subject: [PATCH 33/65] chore: Bump golang from `b274ff1` to `0ca97f4` in /httpserver (#1876) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- httpserver/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index 39ee6444c..33e3ccc0b 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=$BUILDPLATFORM golang:1.22@sha256:b274ff14d8eb9309b61b1a45333bf0559a554ebcf6732fa2012dbed9b01ea56f as builder +FROM --platform=$BUILDPLATFORM golang:1.22@sha256:0ca97f4ab335f4b284a5b8190980c7cdc21d320d529f2b643e8a8733a69bfb6b as builder ARG TARGETPLATFORM ARG TARGETOS From c8e8e00e4a89310c88884c0f0cdbaf8e542809eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 06:24:54 +0000 Subject: [PATCH 34/65] chore: Bump github.com/prometheus/client_golang from 1.20.4 to 1.20.5 (#1877) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 49cfc2ea3..928608310 100644 --- a/go.mod +++ b/go.mod @@ -202,7 +202,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.4 + github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect diff --git a/go.sum b/go.sum index e99841334..56f1d4edc 100644 --- a/go.sum +++ b/go.sum @@ -575,8 +575,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= From a9ee77699c374f8d6bb4b59e52969a7f8f730daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 06:51:50 +0000 Subject: [PATCH 35/65] chore: Bump vscode/devcontainers/go from `bdecb4c` to `46f85d1` in /.devcontainer (#1879) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3b534a9b0..c5db92a0b 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -14,7 +14,7 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go/.devcontainer/base.Dockerfile # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1.22-bullseye, 1.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:bdecb4ca0d168e7bd73b01e475d017aac0888ee22c7d4998a09858ab95157669 +FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:46f85d17eff2b121269b4ed547eb366c2499b5f549d8eaa16fbe6e38f04dfb93 # [Choice] Node.js version: none, lts/*, 18, 16, 14 ARG NODE_VERSION="none" From 9c40fba3f72d91bb63001c914b4f7277ff45975b Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Tue, 22 Oct 2024 01:59:13 +0800 Subject: [PATCH 36/65] chore: bump up go version to 1.22.8 (#1880) Signed-off-by: Binbin Li Signed-off-by: Binbin Li --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 928608310..60bde1044 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ratify-project/ratify -go 1.22.5 +go 1.22.8 // Accidentally published prior to 1.0.0 release retract ( From ea3cee574fde8f1335ec20783fd7b3a9ed4a8ab8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 19:21:13 +0000 Subject: [PATCH 37/65] chore: Bump github.com/sigstore/sigstore from 1.8.9 to 1.8.10 (#1878) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 60bde1044..27d1f11c1 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/owenrumney/go-sarif/v2 v2.3.3 github.com/pkg/errors v0.9.1 github.com/sigstore/cosign/v2 v2.2.4 - github.com/sigstore/sigstore v1.8.9 + github.com/sigstore/sigstore v1.8.10 github.com/sirupsen/logrus v1.9.3 github.com/spdx/tools-golang v0.5.5 github.com/spf13/cobra v1.8.1 @@ -234,14 +234,14 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 56f1d4edc..d94ab83cf 100644 --- a/go.sum +++ b/go.sum @@ -613,8 +613,8 @@ github.com/sigstore/fulcio v1.4.5 h1:WWNnrOknD0DbruuZWCbN+86WRROpEl3Xts+WT2Ek1yc github.com/sigstore/fulcio v1.4.5/go.mod h1:oz3Qwlma8dWcSS/IENR/6SjbW4ipN0cxpRVfgdsjMU8= github.com/sigstore/rekor v1.3.6 h1:QvpMMJVWAp69a3CHzdrLelqEqpTM3ByQRt5B5Kspbi8= github.com/sigstore/rekor v1.3.6/go.mod h1:JDTSNNMdQ/PxdsS49DJkJ+pRJCO/83nbR5p3aZQteXc= -github.com/sigstore/sigstore v1.8.9 h1:NiUZIVWywgYuVTxXmRoTT4O4QAGiTEKup4N1wdxFadk= -github.com/sigstore/sigstore v1.8.9/go.mod h1:d9ZAbNDs8JJfxJrYmulaTazU3Pwr8uLL9+mii4BNR3w= +github.com/sigstore/sigstore v1.8.10 h1:r4t+TYzJlG9JdFxMy+um9GZhZ2N1hBTyTex0AHEZxFs= +github.com/sigstore/sigstore v1.8.10/go.mod h1:BekjqxS5ZtHNJC4u3Q3Stvfx2eyisbW/lUZzmPU2u4A= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3 h1:LTfPadUAo+PDRUbbdqbeSl2OuoFQwUFTnJ4stu+nwWw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.3/go.mod h1:QV/Lxlxm0POyhfyBtIbTWxNeF18clMlkkyL9mu45y18= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3 h1:xgbPRCr2npmmsuVVteJqi/ERw9+I13Wou7kq0Yk4D8g= @@ -780,8 +780,8 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= @@ -826,8 +826,8 @@ golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= @@ -871,8 +871,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -883,8 +883,8 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -897,8 +897,8 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 1d653d4b399315df4432288ddc1f6181bbb158f2 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 24 Sep 2024 14:42:42 +1000 Subject: [PATCH 38/65] chore: migrate azure-sdk-for-go/containerregistry to the latest release Signed-off-by: Shahram Kalantari --- go.mod | 7 ++++--- go.sum | 18 +++++++++-------- .../oras/authprovider/azure/azureidentity.go | 19 +++++++++++++++--- .../azure/azureworkloadidentity.go | 20 ++++++++++++++++--- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 27d1f11c1..37cc55c60 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ retract ( require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2 github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 github.com/aws/aws-sdk-go-v2 v1.32.2 github.com/aws/aws-sdk-go-v2/config v1.27.43 @@ -130,7 +131,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect diff --git a/go.sum b/go.sum index d94ab83cf..8a05bb2d1 100644 --- a/go.sum +++ b/go.sum @@ -18,12 +18,14 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2 h1:wBx10efdJcl8FSewgc41kAW4AvHPgmJZmN7fpNxn8rc= +github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry v0.2.2/go.mod h1:zzmu18cpAinSbhC86oWd47nmgbb91Fl+Yac2PE8NdYk= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= @@ -823,8 +825,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 0a5a00e5c..19fd78ca1 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -29,7 +29,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ) type azureManagedIdentityProviderFactory struct{} @@ -135,8 +135,21 @@ func (d *azureManagedIdentityAuthProvider) Provide(ctx context.Context, artifact serverURL := "https://" + artifactHostName // create registry client and exchange AAD token for registry refresh token - refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) - rt, err := refreshTokenClient.GetFromExchange(ctx, "access_token", artifactHostName, d.tenantID, "", d.identityToken.Token) + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + if err != nil { + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) + } + // refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) + rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( + context.Background(), + "access_token", + artifactHostName, + &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ + AccessToken: &d.identityToken.Token, + Tenant: &d.tenantID, + }, + ) + // rt, err := refreshTokenClient.GetFromExchange(ctx, "access_token", artifactHostName, d.tenantID, "", d.identityToken.Token) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "failed to get refresh token for container registry by azure managed identity token", re.HideStackTrace) } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index a40ce4436..c5acbcee0 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -21,13 +21,13 @@ import ( "os" "time" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" "github.com/ratify-project/ratify/pkg/metrics" "github.com/ratify-project/ratify/pkg/utils/azureauth" - "github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) @@ -130,9 +130,23 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro serverURL := "https://" + artifactHostName // create registry client and exchange AAD token for registry refresh token - refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) + // TODO: Consider adding authentication client options for multicloud scenarios + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + if err != nil { + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) + } + // refreshTokenClient := azcontainerregistry.NewRefreshTokensClient(serverURL) startTime := time.Now() - rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) + rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( + context.Background(), + "access_token", + artifactHostName, + &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ + AccessToken: &d.aadToken.AccessToken, + Tenant: &d.tenantID, + }, + ) + // rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to get refresh token for container registry", re.HideStackTrace) } From c7137d87d74ac6f11fe19af2b9c9832f191d9d21 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:11:51 +1000 Subject: [PATCH 39/65] chore: refactor to enable mocking and add unit tests to azureworkloadidentity_test.go Signed-off-by: Shahram Kalantari --- go.mod | 1 + go.sum | 1 + .../azure/azureworkloadidentity.go | 65 +++++++++++++------ .../azure/azureworkloadidentity_test.go | 60 +++++++++++++++++ 4 files changed, 107 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 37cc55c60..c580fa570 100644 --- a/go.mod +++ b/go.mod @@ -119,6 +119,7 @@ require ( github.com/sigstore/timestamp-authority v1.2.2 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/sourcegraph/conc v0.3.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect diff --git a/go.sum b/go.sum index 8a05bb2d1..ecb26633c 100644 --- a/go.sum +++ b/go.sum @@ -659,6 +659,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index c5acbcee0..183b7f87a 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -21,7 +21,7 @@ import ( "os" "time" - "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + azcontainerregistry "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" @@ -33,9 +33,40 @@ import ( type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name type azureWIAuthProvider struct { - aadToken confidential.AuthResult - tenantID string - clientID string + aadToken confidential.AuthResult + tenantID string + clientID string + authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) + getRegistryHost func(artifact string) (string, error) + getAADAccessToken func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) + reportMetrics func(ctx context.Context, duration int64, artifactHostName string) +} + +type authenticationClientWrapper struct { + client *azcontainerregistry.AuthenticationClient +} + +func (w *authenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) +} + +type authClient interface { + ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) +} + +func NewAzureWIAuthProvider() *azureWIAuthProvider { + return &azureWIAuthProvider{ + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) + if err != nil { + return nil, err + } + return &authenticationClientWrapper{client: client}, nil + }, + getRegistryHost: provider.GetRegistryHostName, + getAADAccessToken: azureauth.GetAADAccessToken, + reportMetrics: metrics.ReportACRExchangeDuration, + } } type azureWIAuthProviderConf struct { @@ -103,22 +134,18 @@ func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { return true } -// Provide returns the credentials for a specified artifact. -// Uses Azure Workload Identity to retrieve an AAD access token which can be -// exchanged for a valid ACR refresh token for login. func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("azure workload identity auth provider is not properly enabled") } - // parse the artifact reference string to extract the registry host name - artifactHostName, err := provider.GetRegistryHostName(artifact) + + artifactHostName, err := d.getRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } - // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.aadToken.ExpiresOn) { - newToken, err := azureauth.GetAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) + newToken, err := d.getAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, nil, "could not refresh AAD token", re.HideStackTrace) } @@ -126,19 +153,16 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro logger.GetLogger(ctx, logOpt).Info("successfully refreshed AAD token") } - // add protocol to generate complete URI serverURL := "https://" + artifactHostName - - // create registry client and exchange AAD token for registry refresh token // TODO: Consider adding authentication client options for multicloud scenarios - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + client, err := d.authClientFactory(serverURL, nil) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) } - // refreshTokenClient := azcontainerregistry.NewRefreshTokensClient(serverURL) + startTime := time.Now() - rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( - context.Background(), + response, err := client.ExchangeAADAccessTokenForACRRefreshToken( + ctx, "access_token", artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ @@ -146,11 +170,12 @@ func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (pro Tenant: &d.tenantID, }, ) - // rt, err := refreshTokenClient.GetFromExchange(context.Background(), "access_token", artifactHostName, d.tenantID, "", d.aadToken.AccessToken) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to get refresh token for container registry", re.HideStackTrace) } - metrics.ReportACRExchangeDuration(ctx, time.Since(startTime).Milliseconds(), artifactHostName) + rt := response.ACRRefreshToken + + d.reportMetrics(ctx, time.Since(startTime).Milliseconds(), artifactHostName) refreshTokenExpiry := getACRExpiryIfEarlier(d.aadToken.ExpiresOn) authConfig := provider.AuthConfig{ diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 3695ef65a..a10f374d3 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -22,9 +22,12 @@ import ( "testing" "time" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) // Verifies that Enabled checks if tenantID is empty or AAD token is empty @@ -131,3 +134,60 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err) } } + +type mockAuthClient struct { + mock.Mock +} + +func (m *mockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + args := m.Called(ctx, grantType, service, options) + return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) +} + +func TestProvide_Success(t *testing.T) { + mockClient := new(mockAuthClient) + expectedRefreshToken := "mocked_refresh_token" + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). + Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, + }, nil) + + provider := &azureWIAuthProvider{ + aadToken: confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, + tenantID: "mockTenantID", + clientID: "mockClientID", + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + return mockClient, nil + }, + getRegistryHost: func(artifact string) (string, error) { + return "myregistry.azurecr.io", nil + }, + getAADAccessToken: func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, nil + }, + reportMetrics: func(ctx context.Context, duration int64, artifactHostName string) {}, + } + + authConfig, err := provider.Provide(context.Background(), "artifact") + + assert.NoError(t, err) + // Assert that the returned refresh token matches the expected one + assert.Equal(t, expectedRefreshToken, authConfig.Password) +} + +func TestProvide_Failure_InvalidHostName(t *testing.T) { + provider := &azureWIAuthProvider{ + getRegistryHost: func(artifact string) (string, error) { + return "", errors.New("invalid hostname") + }, + } + + _, err := provider.Provide(context.Background(), "artifact") + assert.Error(t, err) +} From b1a397a1437eccc66d49cd9785c1e080b8cfed88 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:22:49 +1000 Subject: [PATCH 40/65] chore: lint Signed-off-by: Shahram Kalantari --- .../authprovider/azure/azureworkloadidentity.go | 12 ++++++------ .../azure/azureworkloadidentity_test.go | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 183b7f87a..241696bcf 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -32,7 +32,7 @@ import ( ) type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name -type azureWIAuthProvider struct { +type WIAuthProvider struct { aadToken confidential.AuthResult tenantID string clientID string @@ -54,8 +54,8 @@ type authClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } -func NewAzureWIAuthProvider() *azureWIAuthProvider { - return &azureWIAuthProvider{ +func NewAzureWIAuthProvider() *WIAuthProvider { + return &WIAuthProvider{ authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { @@ -114,7 +114,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "", re.HideStackTrace) } - return &azureWIAuthProvider{ + return &WIAuthProvider{ aadToken: token, tenantID: tenant, clientID: clientID, @@ -122,7 +122,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } // Enabled checks for non empty tenant ID and AAD access token -func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { +func (d *WIAuthProvider) Enabled(_ context.Context) bool { if d.tenantID == "" || d.clientID == "" { return false } @@ -134,7 +134,7 @@ func (d *azureWIAuthProvider) Enabled(_ context.Context) bool { return true } -func (d *azureWIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { +func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("azure workload identity auth provider is not properly enabled") } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index a10f374d3..b4a9a1f7c 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -32,7 +32,7 @@ import ( // Verifies that Enabled checks if tenantID is empty or AAD token is empty func TestAzureWIEnabled_ExpectedResults(t *testing.T) { - azAuthProvider := azureWIAuthProvider{ + azAuthProvider := WIAuthProvider{ tenantID: "test_tenant", clientID: "test_client", aadToken: confidential.AuthResult{ @@ -152,26 +152,26 @@ func TestProvide_Success(t *testing.T) { ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, }, nil) - provider := &azureWIAuthProvider{ + provider := &WIAuthProvider{ aadToken: confidential.AuthResult{ AccessToken: "mockToken", ExpiresOn: time.Now().Add(time.Hour), }, tenantID: "mockTenantID", clientID: "mockClientID", - authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { return mockClient, nil }, - getRegistryHost: func(artifact string) (string, error) { + getRegistryHost: func(_ string) (string, error) { return "myregistry.azurecr.io", nil }, - getAADAccessToken: func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + getAADAccessToken: func(_ context.Context, _, _, _ string) (confidential.AuthResult, error) { return confidential.AuthResult{ AccessToken: "mockToken", ExpiresOn: time.Now().Add(time.Hour), }, nil }, - reportMetrics: func(ctx context.Context, duration int64, artifactHostName string) {}, + reportMetrics: func(_ context.Context, _ int64, _ string) {}, } authConfig, err := provider.Provide(context.Background(), "artifact") @@ -182,8 +182,8 @@ func TestProvide_Success(t *testing.T) { } func TestProvide_Failure_InvalidHostName(t *testing.T) { - provider := &azureWIAuthProvider{ - getRegistryHost: func(artifact string) (string, error) { + provider := &WIAuthProvider{ + getRegistryHost: func(_ string) (string, error) { return "", errors.New("invalid hostname") }, } From 24d306d5415ca3900de9500b5b972c9596d7bcad Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Sun, 29 Sep 2024 19:35:43 +1000 Subject: [PATCH 41/65] chore: address comments Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureworkloadidentity.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 241696bcf..b35a4e6ec 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -155,7 +155,8 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider serverURL := "https://" + artifactHostName // TODO: Consider adding authentication client options for multicloud scenarios - client, err := d.authClientFactory(serverURL, nil) + var options *azcontainerregistry.AuthenticationClientOptions + client, err := d.authClientFactory(serverURL, options) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) } From c5d87bf9086a3ad15a043bc9f98848ee34efd8bc Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Mon, 30 Sep 2024 10:46:16 +1000 Subject: [PATCH 42/65] chore: add comments Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureworkloadidentity.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index b35a4e6ec..df96cc0ba 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -134,16 +134,21 @@ func (d *WIAuthProvider) Enabled(_ context.Context) bool { return true } +// Provide returns the credentials for a specified artifact. +// Uses Azure Workload Identity to retrieve an AAD access token which can be +// exchanged for a valid ACR refresh token for login. func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("azure workload identity auth provider is not properly enabled") } + // parse the artifact reference string to extract the registry host name artifactHostName, err := d.getRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } + // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.aadToken.ExpiresOn) { newToken, err := d.getAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) if err != nil { @@ -153,7 +158,10 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider logger.GetLogger(ctx, logOpt).Info("successfully refreshed AAD token") } + // add protocol to generate complete URI serverURL := "https://" + artifactHostName + + // create registry client and exchange AAD token for registry refresh token // TODO: Consider adding authentication client options for multicloud scenarios var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory(serverURL, options) From 0d4e8a60c11e374c3203b956e98150ea84ee2051 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 1 Oct 2024 17:36:52 +1000 Subject: [PATCH 43/65] chore: more unit tests Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 60 ++++--- .../authprovider/azure/azureidentity_test.go | 156 +++++++++++++++++- .../azure/azureworkloadidentity.go | 14 +- .../azure/azureworkloadidentity_test.go | 135 ++++++++++++++- 4 files changed, 329 insertions(+), 36 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 19fd78ca1..5cfc21b3c 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -33,10 +33,28 @@ import ( ) type azureManagedIdentityProviderFactory struct{} -type azureManagedIdentityAuthProvider struct { - identityToken azcore.AccessToken - clientID string - tenantID string +type MIAuthProvider struct { + identityToken azcore.AccessToken + clientID string + tenantID string + authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) + getRegistryHost func(artifact string) (string, error) + getManagedIdentityToken func(ctx context.Context, clientID string) (azcore.AccessToken, error) +} + +// NewAzureWIAuthProvider is defined to enable mocking of some of the function in unit tests +func NewAzureMIAuthProvider() *MIAuthProvider { + return &MIAuthProvider{ + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) + if err != nil { + return nil, err + } + return &AuthenticationClientWrapper{client: client}, nil + }, + getRegistryHost: provider.GetRegistryHostName, + getManagedIdentityToken: getManagedIdentityToken, + } } type azureManagedIdentityAuthProviderConf struct { @@ -53,7 +71,7 @@ func init() { provider.Register(azureManagedIdentityAuthProviderName, &azureManagedIdentityProviderFactory{}) } -// Create returns an azureManagedIdentityAuthProvider +// Create returns an MIAuthProvider func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider.AuthProviderConfig) (provider.AuthProvider, error) { conf := azureManagedIdentityAuthProviderConf{} authProviderConfigBytes, err := json.Marshal(authProviderConfig) @@ -85,7 +103,7 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "", re.HideStackTrace) } - return &azureManagedIdentityAuthProvider{ + return &MIAuthProvider{ identityToken: token, clientID: client, tenantID: tenant, @@ -93,7 +111,7 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider } // Enabled checks for non empty tenant ID and AAD access token -func (d *azureManagedIdentityAuthProvider) Enabled(_ context.Context) bool { +func (d *MIAuthProvider) Enabled(_ context.Context) bool { if d.clientID == "" { return false } @@ -112,36 +130,39 @@ func (d *azureManagedIdentityAuthProvider) Enabled(_ context.Context) bool { // Provide returns the credentials for a specified artifact. // Uses Managed Identity to retrieve an AAD access token which can be // exchanged for a valid ACR refresh token for login. -func (d *azureManagedIdentityAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { +func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider.AuthConfig, error) { if !d.Enabled(ctx) { return provider.AuthConfig{}, fmt.Errorf("azure managed identity provider is not properly enabled") } + // parse the artifact reference string to extract the registry host name - artifactHostName, err := provider.GetRegistryHostName(artifact) + artifactHostName, err := d.getRegistryHost(artifact) if err != nil { - return provider.AuthConfig{}, err + return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.identityToken.ExpiresOn) { - newToken, err := getManagedIdentityToken(ctx, d.clientID) + newToken, err := d.getManagedIdentityToken(ctx, d.clientID) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "could not refresh azure managed identity token", re.HideStackTrace) } d.identityToken = newToken logger.GetLogger(ctx, logOpt).Info("successfully refreshed azure managed identity token") } + // add protocol to generate complete URI serverURL := "https://" + artifactHostName - // create registry client and exchange AAD token for registry refresh token - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, nil) // &AuthenticationClientOptions{ClientOptions: options}) + // TODO: Consider adding authentication client options for multicloud scenarios + var options *azcontainerregistry.AuthenticationClientOptions + client, err := d.authClientFactory(serverURL, options) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) } - // refreshTokenClient := containerregistry.NewRefreshTokensClient(serverURL) - rt, err := client.ExchangeAADAccessTokenForACRRefreshToken( - context.Background(), + + response, err := client.ExchangeAADAccessTokenForACRRefreshToken( + ctx, "access_token", artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ @@ -149,18 +170,17 @@ func (d *azureManagedIdentityAuthProvider) Provide(ctx context.Context, artifact Tenant: &d.tenantID, }, ) - // rt, err := refreshTokenClient.GetFromExchange(ctx, "access_token", artifactHostName, d.tenantID, "", d.identityToken.Token) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "failed to get refresh token for container registry by azure managed identity token", re.HideStackTrace) } + rt := response.ACRRefreshToken - expiresOn := getACRExpiryIfEarlier(d.identityToken.ExpiresOn) - + refreshTokenExpiry := getACRExpiryIfEarlier(d.identityToken.ExpiresOn) authConfig := provider.AuthConfig{ Username: dockerTokenLoginUsernameGUID, Password: *rt.RefreshToken, Provider: d, - ExpiresOn: expiresOn, + ExpiresOn: refreshTokenExpiry, } return authConfig, nil diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 472e704b9..680b6b489 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -20,15 +20,28 @@ import ( "errors" "os" "testing" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) +type MockGetManagedIdentityToken struct { + mock.Mock +} + +func (m *MockGetManagedIdentityToken) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { + args := m.Called(ctx, clientID) + return args.Get(0).(azcore.AccessToken), args.Error(1) +} + // Verifies that Enabled checks if tenantID is empty or AAD token is empty func TestAzureMSIEnabled_ExpectedResults(t *testing.T) { - azAuthProvider := azureManagedIdentityAuthProvider{ + azAuthProvider := MIAuthProvider{ tenantID: "test_tenant", clientID: "test_client", identityToken: azcore.AccessToken{ @@ -89,3 +102,144 @@ func TestAzureMSIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err) } } + +func TestNewAzureMIAuthProvider_AuthenticationClientError(t *testing.T) { + // Create a new mock client factory + mockFactory := new(MockAuthClientFactory) + + // Setup mock to return an error + mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). + Return(nil, errors.New("failed to create authentication client")) + + // Create a new WIAuthProvider instance + provider := NewAzureMIAuthProvider() + provider.authClientFactory = mockFactory.NewAuthenticationClient + + // Call authClientFactory to test error handling + _, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) + + // Assert that an error is returned + assert.Error(t, err) + assert.Equal(t, "failed to create authentication client", err.Error()) + + // Verify that the mock was called + mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) +} + +func TestNewAzureMIAuthProvider_Success(t *testing.T) { + // Create a new mock client factory + mockFactory := new(MockAuthClientFactory) + + // Create a mock auth client to return from the factory + mockAuthClient := new(MockAuthClient) + + // Setup mock to return a successful auth client + mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). + Return(mockAuthClient, nil) + + // Create a new WIAuthProvider instance + provider := NewAzureMIAuthProvider() + + // Replace authClientFactory with the mock factory + provider.authClientFactory = mockFactory.NewAuthenticationClient + + // Call authClientFactory to test successful return + client, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) + + // Assert that the client is returned without an error + assert.NoError(t, err) + assert.NotNil(t, client) + + // Assert that the returned client is of the expected type + _, ok := client.(*MockAuthClient) + assert.True(t, ok, "expected client to be of type *MockAuthClient") + + // Verify that the mock was called + mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) +} + +func TestMIProvide_Success(t *testing.T) { + const registryHost = "myregistry.azurecr.io" + mockClient := new(MockAuthClient) + expectedRefreshToken := "mocked_refresh_token" + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", registryHost, mock.Anything). + Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, + }, nil) + + provider := &MIAuthProvider{ + identityToken: azcore.AccessToken{ + Token: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, + tenantID: "mockTenantID", + clientID: "mockClientID", + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return mockClient, nil + }, + getRegistryHost: func(_ string) (string, error) { + return registryHost, nil + }, + getManagedIdentityToken: func(_ context.Context, _ string) (azcore.AccessToken, error) { + return azcore.AccessToken{ + Token: "mockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, nil + }, + } + + authConfig, err := provider.Provide(context.Background(), "artifact") + + assert.NoError(t, err) + // Assert that getManagedIdentityToken was not called + mockClient.AssertNotCalled(t, "getManagedIdentityToken", mock.Anything, mock.Anything) + // Assert that the returned refresh token matches the expected one + assert.Equal(t, expectedRefreshToken, authConfig.Password) +} + +func TestMIProvide_RefreshAAD(t *testing.T) { + const registryHost = "myregistry.azurecr.io" + // Arrange + mockClient := new(MockAuthClient) + + // Create a mock function for getManagedIdentityToken + mockGetManagedIdentityToken := new(MockGetManagedIdentityToken) + + provider := &MIAuthProvider{ + identityToken: azcore.AccessToken{ + Token: "mockToken", + ExpiresOn: time.Now(), // Expired token to force a refresh + }, + tenantID: "mockTenantID", + clientID: "mockClientID", + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return mockClient, nil + }, + getRegistryHost: func(_ string) (string, error) { + return registryHost, nil + }, + getManagedIdentityToken: mockGetManagedIdentityToken.GetManagedIdentityToken, // Use the mock + } + + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", registryHost, mock.Anything). + Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: new(string)}, + }, nil) + + // Set up the expectation for the mocked method + mockGetManagedIdentityToken.On("GetManagedIdentityToken", mock.Anything, "mockClientID"). + Return(azcore.AccessToken{ + Token: "newMockToken", + ExpiresOn: time.Now().Add(time.Hour), + }, nil) + + ctx := context.TODO() + artifact := "testArtifact" + + // Act + _, err := provider.Provide(ctx, artifact) + + // Assert + assert.NoError(t, err) + mockGetManagedIdentityToken.AssertCalled(t, "GetManagedIdentityToken", mock.Anything, "mockClientID") // Assert that getManagedIdentityToken was called +} diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index df96cc0ba..77744622b 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -36,32 +36,33 @@ type WIAuthProvider struct { aadToken confidential.AuthResult tenantID string clientID string - authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) + authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) getRegistryHost func(artifact string) (string, error) getAADAccessToken func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) reportMetrics func(ctx context.Context, duration int64, artifactHostName string) } -type authenticationClientWrapper struct { +type AuthenticationClientWrapper struct { client *azcontainerregistry.AuthenticationClient } -func (w *authenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { +func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) } -type authClient interface { +type AuthClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } +// NewAzureWIAuthProvider is defined to enable mocking of some of the function in unit tests func NewAzureWIAuthProvider() *WIAuthProvider { return &WIAuthProvider{ - authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { return nil, err } - return &authenticationClientWrapper{client: client}, nil + return &AuthenticationClientWrapper{client: client}, nil }, getRegistryHost: provider.GetRegistryHostName, getAADAccessToken: azureauth.GetAADAccessToken, @@ -161,7 +162,6 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider // add protocol to generate complete URI serverURL := "https://" + artifactHostName - // create registry client and exchange AAD token for registry refresh token // TODO: Consider adding authentication client options for multicloud scenarios var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory(serverURL, options) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index b4a9a1f7c..1f6d3743b 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -30,6 +30,36 @@ import ( "github.com/stretchr/testify/mock" ) +type MockAuthClient struct { + mock.Mock +} + +type MockAzureAuth struct { + mock.Mock +} + +type MockAuthClientFactory struct { + mock.Mock +} + +func (m *MockAuthClientFactory) NewAuthenticationClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + args := m.Called(serverURL, options) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(AuthClient), args.Error(1) +} + +func (m *MockAzureAuth) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + args := m.Called(ctx, tenantID, clientID, resource) + return args.Get(0).(confidential.AuthResult), args.Error(1) +} + +func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + args := m.Called(ctx, grantType, service, options) + return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) +} + // Verifies that Enabled checks if tenantID is empty or AAD token is empty func TestAzureWIEnabled_ExpectedResults(t *testing.T) { azAuthProvider := WIAuthProvider{ @@ -135,17 +165,63 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { } } -type mockAuthClient struct { - mock.Mock +func TestNewAzureWIAuthProvider_AuthenticationClientError(t *testing.T) { + // Create a new mock client factory + mockFactory := new(MockAuthClientFactory) + + // Setup mock to return an error + mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). + Return(nil, errors.New("failed to create authentication client")) + + // Create a new WIAuthProvider instance + provider := NewAzureWIAuthProvider() + provider.authClientFactory = mockFactory.NewAuthenticationClient + + // Call authClientFactory to test error handling + _, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) + + // Assert that an error is returned + assert.Error(t, err) + assert.Equal(t, "failed to create authentication client", err.Error()) + + // Verify that the mock was called + mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) } -func (m *mockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { - args := m.Called(ctx, grantType, service, options) - return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) +func TestNewAzureWIAuthProvider_Success(t *testing.T) { + // Create a new mock client factory + mockFactory := new(MockAuthClientFactory) + + // Create a mock auth client to return from the factory + mockAuthClient := new(MockAuthClient) + + // Setup mock to return a successful auth client + mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). + Return(mockAuthClient, nil) + + // Create a new WIAuthProvider instance + provider := NewAzureWIAuthProvider() + + // Replace authClientFactory with the mock factory + provider.authClientFactory = mockFactory.NewAuthenticationClient + + // Call authClientFactory to test successful return + client, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) + + // Assert that the client is returned without an error + assert.NoError(t, err) + assert.NotNil(t, client) + + // Assert that the returned client is of the expected type + _, ok := client.(*MockAuthClient) + assert.True(t, ok, "expected client to be of type *MockAuthClient") + + // Verify that the mock was called + mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) } -func TestProvide_Success(t *testing.T) { - mockClient := new(mockAuthClient) +func TestWIProvide_Success(t *testing.T) { + mockClient := new(MockAuthClient) expectedRefreshToken := "mocked_refresh_token" mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ @@ -159,7 +235,7 @@ func TestProvide_Success(t *testing.T) { }, tenantID: "mockTenantID", clientID: "mockClientID", - authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (authClient, error) { + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { return mockClient, nil }, getRegistryHost: func(_ string) (string, error) { @@ -177,10 +253,53 @@ func TestProvide_Success(t *testing.T) { authConfig, err := provider.Provide(context.Background(), "artifact") assert.NoError(t, err) + // Assert that GetAADAccessToken was not called + mockClient.AssertNotCalled(t, "GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything) // Assert that the returned refresh token matches the expected one assert.Equal(t, expectedRefreshToken, authConfig.Password) } +func TestWIProvide_RefreshAAD(t *testing.T) { + // Arrange + mockAzureAuth := new(MockAzureAuth) + mockClient := new(MockAuthClient) + + provider := &WIAuthProvider{ + aadToken: confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now(), + }, + tenantID: "mockTenantID", + clientID: "mockClientID", + authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return mockClient, nil + }, + getRegistryHost: func(_ string) (string, error) { + return "myregistry.azurecr.io", nil + }, + getAADAccessToken: mockAzureAuth.GetAADAccessToken, + reportMetrics: func(_ context.Context, _ int64, _ string) {}, + } + + mockAzureAuth.On("GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(confidential.AuthResult{AccessToken: "newAccessToken", ExpiresOn: time.Now().Add(time.Hour)}, nil) + + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). + Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: new(string)}, + }, nil) + + ctx := context.TODO() + artifact := "testArtifact" + + // Act + _, err := provider.Provide(ctx, artifact) + + assert.NoError(t, err) + // Assert that GetAADAccessToken was not called + mockAzureAuth.AssertCalled(t, "GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything) +} + func TestProvide_Failure_InvalidHostName(t *testing.T) { provider := &WIAuthProvider{ getRegistryHost: func(_ string) (string, error) { From cf5e7f0123afc51b0b4a1c9b368f222eda6630ac Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Thu, 3 Oct 2024 12:21:40 +1000 Subject: [PATCH 44/65] chore: remove unnecessary functions Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 15 ----- .../authprovider/azure/azureidentity_test.go | 66 ++++--------------- .../azure/azureworkloadidentity.go | 17 ----- .../azure/azureworkloadidentity_test.go | 57 +--------------- 4 files changed, 12 insertions(+), 143 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 5cfc21b3c..380f692ad 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -42,21 +42,6 @@ type MIAuthProvider struct { getManagedIdentityToken func(ctx context.Context, clientID string) (azcore.AccessToken, error) } -// NewAzureWIAuthProvider is defined to enable mocking of some of the function in unit tests -func NewAzureMIAuthProvider() *MIAuthProvider { - return &MIAuthProvider{ - authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) - if err != nil { - return nil, err - } - return &AuthenticationClientWrapper{client: client}, nil - }, - getRegistryHost: provider.GetRegistryHostName, - getManagedIdentityToken: getManagedIdentityToken, - } -} - type azureManagedIdentityAuthProviderConf struct { Name string `json:"name"` ClientID string `json:"clientID"` diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 680b6b489..af33afe44 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -103,61 +103,6 @@ func TestAzureMSIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { } } -func TestNewAzureMIAuthProvider_AuthenticationClientError(t *testing.T) { - // Create a new mock client factory - mockFactory := new(MockAuthClientFactory) - - // Setup mock to return an error - mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). - Return(nil, errors.New("failed to create authentication client")) - - // Create a new WIAuthProvider instance - provider := NewAzureMIAuthProvider() - provider.authClientFactory = mockFactory.NewAuthenticationClient - - // Call authClientFactory to test error handling - _, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) - - // Assert that an error is returned - assert.Error(t, err) - assert.Equal(t, "failed to create authentication client", err.Error()) - - // Verify that the mock was called - mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) -} - -func TestNewAzureMIAuthProvider_Success(t *testing.T) { - // Create a new mock client factory - mockFactory := new(MockAuthClientFactory) - - // Create a mock auth client to return from the factory - mockAuthClient := new(MockAuthClient) - - // Setup mock to return a successful auth client - mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). - Return(mockAuthClient, nil) - - // Create a new WIAuthProvider instance - provider := NewAzureMIAuthProvider() - - // Replace authClientFactory with the mock factory - provider.authClientFactory = mockFactory.NewAuthenticationClient - - // Call authClientFactory to test successful return - client, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) - - // Assert that the client is returned without an error - assert.NoError(t, err) - assert.NotNil(t, client) - - // Assert that the returned client is of the expected type - _, ok := client.(*MockAuthClient) - assert.True(t, ok, "expected client to be of type *MockAuthClient") - - // Verify that the mock was called - mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) -} - func TestMIProvide_Success(t *testing.T) { const registryHost = "myregistry.azurecr.io" mockClient := new(MockAuthClient) @@ -243,3 +188,14 @@ func TestMIProvide_RefreshAAD(t *testing.T) { assert.NoError(t, err) mockGetManagedIdentityToken.AssertCalled(t, "GetManagedIdentityToken", mock.Anything, "mockClientID") // Assert that getManagedIdentityToken was called } + +func TestMIProvide_Failure_InvalidHostName(t *testing.T) { + provider := &MIAuthProvider{ + getRegistryHost: func(_ string) (string, error) { + return "", errors.New("invalid hostname") + }, + } + + _, err := provider.Provide(context.Background(), "artifact") + assert.Error(t, err) +} diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 77744622b..a5ed6d9f2 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -25,7 +25,6 @@ import ( re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" - "github.com/ratify-project/ratify/pkg/metrics" "github.com/ratify-project/ratify/pkg/utils/azureauth" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" @@ -54,22 +53,6 @@ type AuthClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } -// NewAzureWIAuthProvider is defined to enable mocking of some of the function in unit tests -func NewAzureWIAuthProvider() *WIAuthProvider { - return &WIAuthProvider{ - authClientFactory: func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) - if err != nil { - return nil, err - } - return &AuthenticationClientWrapper{client: client}, nil - }, - getRegistryHost: provider.GetRegistryHostName, - getAADAccessToken: azureauth.GetAADAccessToken, - reportMetrics: metrics.ReportACRExchangeDuration, - } -} - type azureWIAuthProviderConf struct { Name string `json:"name"` ClientID string `json:"clientID,omitempty"` diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 1f6d3743b..bbfd29eb9 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -165,61 +165,6 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { } } -func TestNewAzureWIAuthProvider_AuthenticationClientError(t *testing.T) { - // Create a new mock client factory - mockFactory := new(MockAuthClientFactory) - - // Setup mock to return an error - mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). - Return(nil, errors.New("failed to create authentication client")) - - // Create a new WIAuthProvider instance - provider := NewAzureWIAuthProvider() - provider.authClientFactory = mockFactory.NewAuthenticationClient - - // Call authClientFactory to test error handling - _, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) - - // Assert that an error is returned - assert.Error(t, err) - assert.Equal(t, "failed to create authentication client", err.Error()) - - // Verify that the mock was called - mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) -} - -func TestNewAzureWIAuthProvider_Success(t *testing.T) { - // Create a new mock client factory - mockFactory := new(MockAuthClientFactory) - - // Create a mock auth client to return from the factory - mockAuthClient := new(MockAuthClient) - - // Setup mock to return a successful auth client - mockFactory.On("NewAuthenticationClient", mock.Anything, mock.Anything). - Return(mockAuthClient, nil) - - // Create a new WIAuthProvider instance - provider := NewAzureWIAuthProvider() - - // Replace authClientFactory with the mock factory - provider.authClientFactory = mockFactory.NewAuthenticationClient - - // Call authClientFactory to test successful return - client, err := provider.authClientFactory("https://myregistry.azurecr.io", nil) - - // Assert that the client is returned without an error - assert.NoError(t, err) - assert.NotNil(t, client) - - // Assert that the returned client is of the expected type - _, ok := client.(*MockAuthClient) - assert.True(t, ok, "expected client to be of type *MockAuthClient") - - // Verify that the mock was called - mockFactory.AssertCalled(t, "NewAuthenticationClient", "https://myregistry.azurecr.io", mock.Anything) -} - func TestWIProvide_Success(t *testing.T) { mockClient := new(MockAuthClient) expectedRefreshToken := "mocked_refresh_token" @@ -300,7 +245,7 @@ func TestWIProvide_RefreshAAD(t *testing.T) { mockAzureAuth.AssertCalled(t, "GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything) } -func TestProvide_Failure_InvalidHostName(t *testing.T) { +func TestWIProvide_Failure_InvalidHostName(t *testing.T) { provider := &WIAuthProvider{ getRegistryHost: func(_ string) (string, error) { return "", errors.New("invalid hostname") From f213c5f6ce9bc510b054c92a2b6dfe48f6554263 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Thu, 3 Oct 2024 14:37:22 +1000 Subject: [PATCH 45/65] fix: fix the bugs in the unit tests Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureidentity_test.go | 5 +++++ .../oras/authprovider/azure/azureworkloadidentity_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index af33afe44..11fb48f5f 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -191,6 +191,11 @@ func TestMIProvide_RefreshAAD(t *testing.T) { func TestMIProvide_Failure_InvalidHostName(t *testing.T) { provider := &MIAuthProvider{ + tenantID: "test_tenant", + clientID: "test_client", + identityToken: azcore.AccessToken{ + Token: "test_token", + }, getRegistryHost: func(_ string) (string, error) { return "", errors.New("invalid hostname") }, diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index bbfd29eb9..1b58e18fe 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -247,6 +247,11 @@ func TestWIProvide_RefreshAAD(t *testing.T) { func TestWIProvide_Failure_InvalidHostName(t *testing.T) { provider := &WIAuthProvider{ + aadToken: confidential.AuthResult{ + AccessToken: "mockToken", + ExpiresOn: time.Now(), + }, + tenantID: "mockTenantID", getRegistryHost: func(_ string) (string, error) { return "", errors.New("invalid hostname") }, From 3ec6a2d5361e0367f8e2f481921bea1299079790 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Fri, 4 Oct 2024 12:07:46 +1000 Subject: [PATCH 46/65] fix: provide default implementation of the functions Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 8 +++--- .../azure/azureworkloadidentity.go | 25 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 380f692ad..85c7cf7ad 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -89,9 +89,11 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider } return &MIAuthProvider{ - identityToken: token, - clientID: client, - tenantID: tenant, + identityToken: token, + clientID: client, + tenantID: tenant, + authClientFactory: defaultAuthClientFactory, + getManagedIdentityToken: getManagedIdentityToken, }, nil } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index a5ed6d9f2..c0cbf8377 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -41,6 +41,22 @@ type WIAuthProvider struct { reportMetrics func(ctx context.Context, duration int64, artifactHostName string) } +func defaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) + if err != nil { + return nil, err + } + return &AuthenticationClientWrapper{client: client}, nil +} + +func defaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) +} + +func defaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) +} + type AuthenticationClientWrapper struct { client *azcontainerregistry.AuthenticationClient } @@ -99,9 +115,12 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } return &WIAuthProvider{ - aadToken: token, - tenantID: tenant, - clientID: clientID, + aadToken: token, + tenantID: tenant, + clientID: clientID, + authClientFactory: defaultAuthClientFactory, + getAADAccessToken: defaultGetAADAccessToken, + reportMetrics: defaultReportMetrics, }, nil } From 23cf3d2a68de48a7b96ce3b6f66ebf8ef3a0971b Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Wed, 9 Oct 2024 10:25:23 +1000 Subject: [PATCH 47/65] chore: refactor Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 2 +- .../azure/azureworkloadidentity.go | 34 ++---------- .../azure/azureworkloadidentity_test.go | 12 ----- pkg/common/oras/authprovider/azure/helper.go | 53 +++++++++++++++++++ 4 files changed, 57 insertions(+), 44 deletions(-) create mode 100644 pkg/common/oras/authprovider/azure/helper.go diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 85c7cf7ad..f9f4adb67 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -92,7 +92,7 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider identityToken: token, clientID: client, tenantID: tenant, - authClientFactory: defaultAuthClientFactory, + authClientFactory: DefaultAuthClientFactory, getManagedIdentityToken: getManagedIdentityToken, }, nil } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index c0cbf8377..57033100b 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -41,34 +41,6 @@ type WIAuthProvider struct { reportMetrics func(ctx context.Context, duration int64, artifactHostName string) } -func defaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) - if err != nil { - return nil, err - } - return &AuthenticationClientWrapper{client: client}, nil -} - -func defaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { - return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) -} - -func defaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { - logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) -} - -type AuthenticationClientWrapper struct { - client *azcontainerregistry.AuthenticationClient -} - -func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { - return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) -} - -type AuthClient interface { - ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) -} - type azureWIAuthProviderConf struct { Name string `json:"name"` ClientID string `json:"clientID,omitempty"` @@ -118,9 +90,9 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider aadToken: token, tenantID: tenant, clientID: clientID, - authClientFactory: defaultAuthClientFactory, - getAADAccessToken: defaultGetAADAccessToken, - reportMetrics: defaultReportMetrics, + authClientFactory: DefaultAuthClientFactory, + getAADAccessToken: DefaultGetAADAccessToken, + reportMetrics: DefaultReportMetrics, }, nil } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 1b58e18fe..17ce740c8 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -38,18 +38,6 @@ type MockAzureAuth struct { mock.Mock } -type MockAuthClientFactory struct { - mock.Mock -} - -func (m *MockAuthClientFactory) NewAuthenticationClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - args := m.Called(serverURL, options) - if args.Get(0) == nil { - return nil, args.Error(1) - } - return args.Get(0).(AuthClient), args.Error(1) -} - func (m *MockAzureAuth) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { args := m.Called(ctx, tenantID, clientID, resource) return args.Get(0).(confidential.AuthResult), args.Error(1) diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go new file mode 100644 index 000000000..db679feb6 --- /dev/null +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -0,0 +1,53 @@ +/* +Copyright The Ratify 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. +*/ + +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/utils/azureauth" +) + +func DefaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) + if err != nil { + return nil, err + } + return &AuthenticationClientWrapper{client: client}, nil +} + +func DefaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) +} + +func DefaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) +} + +type AuthenticationClientWrapper struct { + client *azcontainerregistry.AuthenticationClient +} + +func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) +} + +type AuthClient interface { + ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) +} From 598ff9e521369ac4a190e00f1d830af9e344009a Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Thu, 10 Oct 2024 11:51:58 +1000 Subject: [PATCH 48/65] chore: refactor azureworkloadidentity Signed-off-by: Shahram Kalantari --- .../azure/azureworkloadidentity.go | 76 +++- .../azure/azureworkloadidentity_test.go | 420 +++++++++++++----- 2 files changed, 382 insertions(+), 114 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 57033100b..827740d34 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -25,20 +25,69 @@ import ( re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" - "github.com/ratify-project/ratify/pkg/utils/azureauth" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) +// AuthClientFactory defines an interface for creating an authentication client. +type AuthClientFactory interface { + CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) +} + +// RegistryHostGetter defines an interface for getting the registry host. +type RegistryHostGetter interface { + GetRegistryHost(artifact string) (string, error) +} + +// AADAccessTokenGetter defines an interface for getting an AAD access token. +type AADAccessTokenGetter interface { + GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) +} + +// MetricsReporter defines an interface for reporting metrics. +type MetricsReporter interface { + ReportMetrics(ctx context.Context, duration int64, artifactHostName string) +} + +// DefaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. +type DefaultAuthClientFactoryImpl struct{} + +func (f *DefaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return DefaultAuthClientFactory(serverURL, options) +} + +// DefaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. +type DefaultRegistryHostGetterImpl struct{} + +func (g *DefaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { + // Implement the logic to get the registry host + return provider.GetRegistryHostName(artifact) + // return artifactHost, nil // Replace with actual logic +} + +// DefaultAADAccessTokenGetterImpl is the default implementation of AADAccessTokenGetter. +type DefaultAADAccessTokenGetterImpl struct{} + +func (g *DefaultAADAccessTokenGetterImpl) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return DefaultGetAADAccessToken(ctx, tenantID, clientID, resource) +} + +// DefaultMetricsReporterImpl is the default implementation of MetricsReporter. +type DefaultMetricsReporterImpl struct{} + +func (r *DefaultMetricsReporterImpl) ReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + DefaultReportMetrics(ctx, duration, artifactHostName) +} + type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name type WIAuthProvider struct { aadToken confidential.AuthResult tenantID string clientID string - authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) - getRegistryHost func(artifact string) (string, error) - getAADAccessToken func(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) - reportMetrics func(ctx context.Context, duration int64, artifactHostName string) + authClientFactory AuthClientFactory + getRegistryHost RegistryHostGetter + getAADAccessToken AADAccessTokenGetter + reportMetrics MetricsReporter } type azureWIAuthProviderConf struct { @@ -81,7 +130,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } // retrieve an AAD Access token - token, err := azureauth.GetAADAccessToken(context.Background(), tenant, clientID, AADResource) + token, err := DefaultGetAADAccessToken(context.Background(), tenant, clientID, AADResource) if err != nil { return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "", re.HideStackTrace) } @@ -90,9 +139,10 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider aadToken: token, tenantID: tenant, clientID: clientID, - authClientFactory: DefaultAuthClientFactory, - getAADAccessToken: DefaultGetAADAccessToken, - reportMetrics: DefaultReportMetrics, + authClientFactory: &DefaultAuthClientFactoryImpl{}, // Concrete implementation + getRegistryHost: &DefaultRegistryHostGetterImpl{}, // Concrete implementation + getAADAccessToken: &DefaultAADAccessTokenGetterImpl{}, // Concrete implementation + reportMetrics: &DefaultMetricsReporterImpl{}, }, nil } @@ -118,14 +168,14 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider } // parse the artifact reference string to extract the registry host name - artifactHostName, err := d.getRegistryHost(artifact) + artifactHostName, err := d.getRegistryHost.GetRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.aadToken.ExpiresOn) { - newToken, err := d.getAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) + newToken, err := d.getAADAccessToken.GetAADAccessToken(ctx, d.tenantID, d.clientID, AADResource) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, nil, "could not refresh AAD token", re.HideStackTrace) } @@ -138,7 +188,7 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider // TODO: Consider adding authentication client options for multicloud scenarios var options *azcontainerregistry.AuthenticationClientOptions - client, err := d.authClientFactory(serverURL, options) + client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) } @@ -158,7 +208,7 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider } rt := response.ACRRefreshToken - d.reportMetrics(ctx, time.Since(startTime).Milliseconds(), artifactHostName) + d.reportMetrics.ReportMetrics(ctx, time.Since(startTime).Milliseconds(), artifactHostName) refreshTokenExpiry := getACRExpiryIfEarlier(d.aadToken.ExpiresOn) authConfig := provider.AuthConfig{ diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 17ce740c8..c8be41b6f 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -22,32 +22,346 @@ import ( "testing" "time" - "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" - "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + + azcontainerregistry "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) -type MockAuthClient struct { +// MockAuthClientFactory for creating AuthClient +type MockAuthClientFactory struct { mock.Mock } -type MockAzureAuth struct { +func (m *MockAuthClientFactory) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + args := m.Called(serverURL, options) + return args.Get(0).(AuthClient), args.Error(1) +} + +// MockRegistryHostGetter for retrieving registry host +type MockRegistryHostGetter struct { mock.Mock } -func (m *MockAzureAuth) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { +func (m *MockRegistryHostGetter) GetRegistryHost(artifact string) (string, error) { + args := m.Called(artifact) + return args.String(0), args.Error(1) +} + +// MockAADAccessTokenGetter for retrieving AAD access token +type MockAADAccessTokenGetter struct { + mock.Mock +} + +func (m *MockAADAccessTokenGetter) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { args := m.Called(ctx, tenantID, clientID, resource) return args.Get(0).(confidential.AuthResult), args.Error(1) } +// MockMetricsReporter for reporting metrics +type MockMetricsReporter struct { + mock.Mock +} + +func (m *MockMetricsReporter) ReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + m.Called(ctx, duration, artifactHostName) +} + +// MockAuthClient for the Azure auth client +type MockAuthClient struct { + mock.Mock +} + func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { args := m.Called(ctx, grantType, service, options) return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) } +// Test for successful Provide function +func TestWIAuthProvider_Provide_Success(t *testing.T) { + // Mock all dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + mockAuthClient := new(MockAuthClient) + + // Mock AAD token + initialToken := confidential.AuthResult{AccessToken: "initial_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + refreshTokenString := "new_refresh_token" + refreshToken := azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &refreshTokenString}, + } + + // Set expectations for mocked functions + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(initialToken, nil) + mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() + + // Create WIAuthProvider + provider := WIAuthProvider{ + aadToken: initialToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + authConfig, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.NoError(t, err) + assert.Equal(t, "new_refresh_token", authConfig.Password) +} + +// Test for AAD token refresh logic +func TestWIAuthProvider_Provide_RefreshToken(t *testing.T) { + // Mock all dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + mockAuthClient := new(MockAuthClient) + + // Mock expired AAD token, and refreshed token + expiredToken := confidential.AuthResult{AccessToken: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + newToken := confidential.AuthResult{AccessToken: "new_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + refreshTokenString := "refreshed_token" + refreshToken := azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &refreshTokenString}, + } + + // Set expectations for mocked functions + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) + mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() + + // Create WIAuthProvider with expired token + provider := WIAuthProvider{ + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + authConfig, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.NoError(t, err) + assert.Equal(t, "refreshed_token", authConfig.Password) +} + +// Test for failure when GetAADAccessToken fails +func TestWIAuthProvider_Provide_AADTokenFailure(t *testing.T) { + // Mock all dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + + // Mock expired AAD token, and failure to refresh + expiredToken := confidential.AuthResult{AccessToken: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + + // Set expectations for mocked functions + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(confidential.AuthResult{}, errors.New("token refresh failed")) + + // Create WIAuthProvider with expired token + provider := WIAuthProvider{ + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + _, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not refresh AAD token") +} + +// Test when tenant ID is missing from the environment +func TestAzureWIProviderFactory_Create_NoTenantID(t *testing.T) { + // Clear the tenant ID environment variable + t.Setenv("AZURE_TENANT_ID", "") + + // Initialize provider factory + factory := &AzureWIProviderFactory{} + + // Call Create with minimal configuration + _, err := factory.Create(map[string]interface{}{}) + + // Expect error related to missing tenant ID + assert.Error(t, err) + assert.Contains(t, err.Error(), "azure tenant id environment variable is empty") +} + +// Test when client ID is missing from the environment +func TestAzureWIProviderFactory_Create_NoClientID(t *testing.T) { + // Set tenant ID but leave client ID empty + t.Setenv("AZURE_TENANT_ID", "tenantID") + t.Setenv("AZURE_CLIENT_ID", "") + + // Initialize provider factory + factory := &AzureWIProviderFactory{} + + // Call Create with minimal configuration + _, err := factory.Create(map[string]interface{}{}) + + // Expect error related to missing client ID + assert.Error(t, err) + assert.Contains(t, err.Error(), "no client ID provided and AZURE_CLIENT_ID environment variable is empty") +} + +// Test for successful token refresh +func TestWIAuthProvider_Provide_TokenRefresh_Success(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + mockAuthClient := new(MockAuthClient) + + // Mock expired AAD token and refreshed token + expiredToken := confidential.AuthResult{AccessToken: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + refreshTokenString := "refreshed_token" + newToken := confidential.AuthResult{AccessToken: "new_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + refreshToken := azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &refreshTokenString}, + } + + // Set expectations + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) + mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() + + // Create WIAuthProvider with expired token + provider := WIAuthProvider{ + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + authConfig, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.NoError(t, err) + assert.Equal(t, "refreshed_token", authConfig.Password) +} + +// Test when token refresh fails +func TestWIAuthProvider_Provide_TokenRefreshFailure(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + + // Mock expired AAD token and failure to refresh + expiredToken := confidential.AuthResult{AccessToken: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + + // Set expectations + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(confidential.AuthResult{}, errors.New("token refresh failed")) + + // Create WIAuthProvider with expired token + provider := WIAuthProvider{ + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + _, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not refresh AAD token") +} + +// Test for handling empty AccessToken +func TestWIAuthProvider_Enabled_NoAccessToken(t *testing.T) { + // Create a provider with no AccessToken + provider := WIAuthProvider{ + tenantID: "tenantID", + clientID: "clientID", + aadToken: confidential.AuthResult{AccessToken: ""}, + } + + // Assert that provider is not enabled + enabled := provider.Enabled(context.Background()) + assert.False(t, enabled) +} + +// Test for invalid hostname retrieval +func TestWIAuthProvider_Provide_InvalidHostName(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockAADAccessTokenGetter := new(MockAADAccessTokenGetter) + mockMetricsReporter := new(MockMetricsReporter) + + // Mock valid AAD token + validToken := confidential.AuthResult{AccessToken: "valid_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + + // Set expectations for an invalid hostname + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("", errors.New("invalid hostname")) + + // Create WIAuthProvider with valid token + provider := WIAuthProvider{ + aadToken: validToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, + } + + // Call Provide method + ctx := context.Background() + _, err := provider.Provide(ctx, "artifact_name") + + // Assertions + assert.Error(t, err) + assert.Contains(t, err.Error(), "HOST_NAME_INVALID") +} + // Verifies that Enabled checks if tenantID is empty or AAD token is empty func TestAzureWIEnabled_ExpectedResults(t *testing.T) { azAuthProvider := WIAuthProvider{ @@ -152,99 +466,3 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err) } } - -func TestWIProvide_Success(t *testing.T) { - mockClient := new(MockAuthClient) - expectedRefreshToken := "mocked_refresh_token" - mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). - Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ - ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, - }, nil) - - provider := &WIAuthProvider{ - aadToken: confidential.AuthResult{ - AccessToken: "mockToken", - ExpiresOn: time.Now().Add(time.Hour), - }, - tenantID: "mockTenantID", - clientID: "mockClientID", - authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return mockClient, nil - }, - getRegistryHost: func(_ string) (string, error) { - return "myregistry.azurecr.io", nil - }, - getAADAccessToken: func(_ context.Context, _, _, _ string) (confidential.AuthResult, error) { - return confidential.AuthResult{ - AccessToken: "mockToken", - ExpiresOn: time.Now().Add(time.Hour), - }, nil - }, - reportMetrics: func(_ context.Context, _ int64, _ string) {}, - } - - authConfig, err := provider.Provide(context.Background(), "artifact") - - assert.NoError(t, err) - // Assert that GetAADAccessToken was not called - mockClient.AssertNotCalled(t, "GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything) - // Assert that the returned refresh token matches the expected one - assert.Equal(t, expectedRefreshToken, authConfig.Password) -} - -func TestWIProvide_RefreshAAD(t *testing.T) { - // Arrange - mockAzureAuth := new(MockAzureAuth) - mockClient := new(MockAuthClient) - - provider := &WIAuthProvider{ - aadToken: confidential.AuthResult{ - AccessToken: "mockToken", - ExpiresOn: time.Now(), - }, - tenantID: "mockTenantID", - clientID: "mockClientID", - authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return mockClient, nil - }, - getRegistryHost: func(_ string) (string, error) { - return "myregistry.azurecr.io", nil - }, - getAADAccessToken: mockAzureAuth.GetAADAccessToken, - reportMetrics: func(_ context.Context, _ int64, _ string) {}, - } - - mockAzureAuth.On("GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(confidential.AuthResult{AccessToken: "newAccessToken", ExpiresOn: time.Now().Add(time.Hour)}, nil) - - mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "myregistry.azurecr.io", mock.Anything). - Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ - ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: new(string)}, - }, nil) - - ctx := context.TODO() - artifact := "testArtifact" - - // Act - _, err := provider.Provide(ctx, artifact) - - assert.NoError(t, err) - // Assert that GetAADAccessToken was not called - mockAzureAuth.AssertCalled(t, "GetAADAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything) -} - -func TestWIProvide_Failure_InvalidHostName(t *testing.T) { - provider := &WIAuthProvider{ - aadToken: confidential.AuthResult{ - AccessToken: "mockToken", - ExpiresOn: time.Now(), - }, - tenantID: "mockTenantID", - getRegistryHost: func(_ string) (string, error) { - return "", errors.New("invalid hostname") - }, - } - - _, err := provider.Provide(context.Background(), "artifact") - assert.Error(t, err) -} From 03688b033d674747ddd9509909e7f21bc525d418 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Thu, 10 Oct 2024 14:22:46 +1000 Subject: [PATCH 49/65] chore: refactor azureidentity.go Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 29 ++- .../authprovider/azure/azureidentity_test.go | 207 +++++++++++------- 2 files changed, 143 insertions(+), 93 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index f9f4adb67..8f89a0f9d 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -33,13 +33,26 @@ import ( ) type azureManagedIdentityProviderFactory struct{} + +// ManagedIdentityTokenGetter defines an interface for getting a managed identity token. +type ManagedIdentityTokenGetter interface { + GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) +} + +// DefaultManagedIdentityTokenGetterImpl is the default implementation of AADAccessTokenGetter. +type DefaultManagedIdentityTokenGetterImpl struct{} + +func (g *DefaultManagedIdentityTokenGetterImpl) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { + return getManagedIdentityToken(ctx, clientID) +} + type MIAuthProvider struct { identityToken azcore.AccessToken clientID string tenantID string - authClientFactory func(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) - getRegistryHost func(artifact string) (string, error) - getManagedIdentityToken func(ctx context.Context, clientID string) (azcore.AccessToken, error) + authClientFactory AuthClientFactory + getRegistryHost RegistryHostGetter + getManagedIdentityToken ManagedIdentityTokenGetter } type azureManagedIdentityAuthProviderConf struct { @@ -92,8 +105,8 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider identityToken: token, clientID: client, tenantID: tenant, - authClientFactory: DefaultAuthClientFactory, - getManagedIdentityToken: getManagedIdentityToken, + authClientFactory: &DefaultAuthClientFactoryImpl{}, // Concrete implementation + getManagedIdentityToken: &DefaultManagedIdentityTokenGetterImpl{}, // Concrete implementation }, nil } @@ -123,14 +136,14 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider } // parse the artifact reference string to extract the registry host name - artifactHostName, err := d.getRegistryHost(artifact) + artifactHostName, err := d.getRegistryHost.GetRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } // need to refresh AAD token if it's expired if time.Now().Add(time.Minute * 5).After(d.identityToken.ExpiresOn) { - newToken, err := d.getManagedIdentityToken(ctx, d.clientID) + newToken, err := d.getManagedIdentityToken.GetManagedIdentityToken(ctx, d.clientID) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "could not refresh azure managed identity token", re.HideStackTrace) } @@ -143,7 +156,7 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider // TODO: Consider adding authentication client options for multicloud scenarios var options *azcontainerregistry.AuthenticationClientOptions - client, err := d.authClientFactory(serverURL, options) + client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) } diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 11fb48f5f..8cb08b419 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -23,18 +23,20 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + azcontainerregistry "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) -type MockGetManagedIdentityToken struct { +// Mock types for external dependencies +type MockManagedIdentityTokenGetter struct { mock.Mock } -func (m *MockGetManagedIdentityToken) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { +// Mock ManagedIdentityTokenGetter.GetManagedIdentityToken +func (m *MockManagedIdentityTokenGetter) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { args := m.Called(ctx, clientID) return args.Get(0).(azcore.AccessToken), args.Error(1) } @@ -103,104 +105,139 @@ func TestAzureMSIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) { } } -func TestMIProvide_Success(t *testing.T) { - const registryHost = "myregistry.azurecr.io" - mockClient := new(MockAuthClient) - expectedRefreshToken := "mocked_refresh_token" - mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", registryHost, mock.Anything). - Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ - ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &expectedRefreshToken}, - }, nil) +// Test for invalid configuration when tenant ID is missing +func TestAzureManagedIdentityProviderFactory_Create_NoTenantID(t *testing.T) { + t.Setenv("AZURE_TENANT_ID", "") - provider := &MIAuthProvider{ - identityToken: azcore.AccessToken{ - Token: "mockToken", - ExpiresOn: time.Now().Add(time.Hour), - }, - tenantID: "mockTenantID", - clientID: "mockClientID", - authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return mockClient, nil - }, - getRegistryHost: func(_ string) (string, error) { - return registryHost, nil - }, - getManagedIdentityToken: func(_ context.Context, _ string) (azcore.AccessToken, error) { - return azcore.AccessToken{ - Token: "mockToken", - ExpiresOn: time.Now().Add(time.Hour), - }, nil - }, - } + // Initialize factory + factory := &azureManagedIdentityProviderFactory{} - authConfig, err := provider.Provide(context.Background(), "artifact") + // Attempt to create MIAuthProvider with empty configuration + _, err := factory.Create(map[string]interface{}{}) - assert.NoError(t, err) - // Assert that getManagedIdentityToken was not called - mockClient.AssertNotCalled(t, "getManagedIdentityToken", mock.Anything, mock.Anything) - // Assert that the returned refresh token matches the expected one - assert.Equal(t, expectedRefreshToken, authConfig.Password) + // Validate the error + assert.Error(t, err) + assert.Contains(t, err.Error(), "AZURE_TENANT_ID environment variable is empty") } -func TestMIProvide_RefreshAAD(t *testing.T) { - const registryHost = "myregistry.azurecr.io" - // Arrange - mockClient := new(MockAuthClient) +// Test for missing client ID +func TestAzureManagedIdentityProviderFactory_Create_NoClientID(t *testing.T) { + t.Setenv("AZURE_TENANT_ID", "tenantID") + t.Setenv("AZURE_CLIENT_ID", "") - // Create a mock function for getManagedIdentityToken - mockGetManagedIdentityToken := new(MockGetManagedIdentityToken) + // Initialize factory + factory := &azureManagedIdentityProviderFactory{} - provider := &MIAuthProvider{ - identityToken: azcore.AccessToken{ - Token: "mockToken", - ExpiresOn: time.Now(), // Expired token to force a refresh - }, - tenantID: "mockTenantID", - clientID: "mockClientID", - authClientFactory: func(_ string, _ *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return mockClient, nil - }, - getRegistryHost: func(_ string) (string, error) { - return registryHost, nil - }, - getManagedIdentityToken: mockGetManagedIdentityToken.GetManagedIdentityToken, // Use the mock - } + // Attempt to create MIAuthProvider with empty client ID + _, err := factory.Create(map[string]interface{}{}) - mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", registryHost, mock.Anything). - Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ - ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: new(string)}, - }, nil) + // Validate the error + assert.Error(t, err) + assert.Contains(t, err.Error(), "AZURE_CLIENT_ID environment variable is empty") +} - // Set up the expectation for the mocked method - mockGetManagedIdentityToken.On("GetManagedIdentityToken", mock.Anything, "mockClientID"). - Return(azcore.AccessToken{ - Token: "newMockToken", - ExpiresOn: time.Now().Add(time.Hour), - }, nil) +// Test successful token refresh +func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockManagedIdentityTokenGetter := new(MockManagedIdentityTokenGetter) + mockAuthClient := new(MockAuthClient) + + // Define token values + expiredToken := azcore.AccessToken{Token: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + newTokenString := "refreshed_token" + newAADToken := azcore.AccessToken{Token: "new_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + refreshToken := azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ + ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &newTokenString}, + } - ctx := context.TODO() - artifact := "testArtifact" + // Setup mock expectations + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockManagedIdentityTokenGetter.On("GetManagedIdentityToken", mock.Anything, "clientID").Return(newAADToken, nil) + + // Initialize provider with expired token + provider := MIAuthProvider{ + identityToken: expiredToken, + clientID: "clientID", + tenantID: "tenantID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getManagedIdentityToken: mockManagedIdentityTokenGetter, + } - // Act - _, err := provider.Provide(ctx, artifact) + // Call Provide method + ctx := context.Background() + authConfig, err := provider.Provide(ctx, "artifact_name") - // Assert + // Validate success and token refresh assert.NoError(t, err) - mockGetManagedIdentityToken.AssertCalled(t, "GetManagedIdentityToken", mock.Anything, "mockClientID") // Assert that getManagedIdentityToken was called + assert.Equal(t, "refreshed_token", authConfig.Password) } -func TestMIProvide_Failure_InvalidHostName(t *testing.T) { - provider := &MIAuthProvider{ - tenantID: "test_tenant", - clientID: "test_client", - identityToken: azcore.AccessToken{ - Token: "test_token", - }, - getRegistryHost: func(_ string) (string, error) { - return "", errors.New("invalid hostname") - }, +// Test failed token refresh +func TestMIAuthProvider_Provide_TokenRefreshFailure(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockManagedIdentityTokenGetter := new(MockManagedIdentityTokenGetter) + + // Define token values + expiredToken := azcore.AccessToken{Token: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} + + // Setup mock expectations + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) + mockManagedIdentityTokenGetter.On("GetManagedIdentityToken", mock.Anything, "clientID").Return(azcore.AccessToken{}, errors.New("token refresh failed")) + + // Initialize provider with expired token + provider := MIAuthProvider{ + identityToken: expiredToken, + clientID: "clientID", + tenantID: "tenantID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getManagedIdentityToken: mockManagedIdentityTokenGetter, + } + + // Call Provide method + ctx := context.Background() + _, err := provider.Provide(ctx, "artifact_name") + + // Validate failure + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not refresh azure managed identity token") +} + +// Test for invalid hostname retrieval +func TestMIAuthProvider_Provide_InvalidHostName(t *testing.T) { + // Mock dependencies + mockAuthClientFactory := new(MockAuthClientFactory) + mockRegistryHostGetter := new(MockRegistryHostGetter) + mockManagedIdentityTokenGetter := new(MockManagedIdentityTokenGetter) + + // Define valid token + validToken := azcore.AccessToken{Token: "valid_token", ExpiresOn: time.Now().Add(10 * time.Minute)} + + // Setup mock expectations for invalid hostname + mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("", errors.New("invalid hostname")) + + // Initialize provider with valid token + provider := MIAuthProvider{ + identityToken: validToken, + clientID: "clientID", + tenantID: "tenantID", + authClientFactory: mockAuthClientFactory, + getRegistryHost: mockRegistryHostGetter, + getManagedIdentityToken: mockManagedIdentityTokenGetter, } - _, err := provider.Provide(context.Background(), "artifact") + // Call Provide method + ctx := context.Background() + _, err := provider.Provide(ctx, "artifact_name") + + // Validate failure assert.Error(t, err) + assert.Contains(t, err.Error(), "HOST_NAME_INVALID") } From 2a0c8d86715cecb60d64457abfb3032ed9d2b814 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Fri, 11 Oct 2024 14:30:41 +1000 Subject: [PATCH 50/65] chore: move common code to helper.go Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 34 ++--- .../authprovider/azure/azureidentity_test.go | 4 +- .../azure/azureworkloadidentity.go | 46 ++---- .../azure/azureworkloadidentity_test.go | 30 ---- pkg/common/oras/authprovider/azure/helper.go | 37 +++-- .../oras/authprovider/azure/helper_test.go | 136 ++++++++++++++++++ 6 files changed, 196 insertions(+), 91 deletions(-) create mode 100644 pkg/common/oras/authprovider/azure/helper_test.go diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 8f89a0f9d..fd8466762 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -32,20 +32,34 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ) -type azureManagedIdentityProviderFactory struct{} - // ManagedIdentityTokenGetter defines an interface for getting a managed identity token. type ManagedIdentityTokenGetter interface { GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) } -// DefaultManagedIdentityTokenGetterImpl is the default implementation of AADAccessTokenGetter. +// DefaultManagedIdentityTokenGetterImpl is the default implementation of getManagedIdentityToken. type DefaultManagedIdentityTokenGetterImpl struct{} func (g *DefaultManagedIdentityTokenGetterImpl) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { return getManagedIdentityToken(ctx, clientID) } +func getManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { + id := azidentity.ClientID(clientID) + opts := azidentity.ManagedIdentityCredentialOptions{ID: id} + cred, err := azidentity.NewManagedIdentityCredential(&opts) + if err != nil { + return azcore.AccessToken{}, err + } + scopes := []string{AADResource} + if cred != nil { + return cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: scopes}) + } + return azcore.AccessToken{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("config is nil pointer for GetServicePrincipalToken") +} + +type azureManagedIdentityProviderFactory struct{} + type MIAuthProvider struct { identityToken azcore.AccessToken clientID string @@ -185,17 +199,3 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider return authConfig, nil } - -func getManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { - id := azidentity.ClientID(clientID) - opts := azidentity.ManagedIdentityCredentialOptions{ID: id} - cred, err := azidentity.NewManagedIdentityCredential(&opts) - if err != nil { - return azcore.AccessToken{}, err - } - scopes := []string{AADResource} - if cred != nil { - return cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: scopes}) - } - return azcore.AccessToken{}, re.ErrorCodeConfigInvalid.WithComponentType(re.AuthProvider).WithDetail("config is nil pointer for GetServicePrincipalToken") -} diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 8cb08b419..a47854252 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -146,7 +146,7 @@ func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { // Define token values expiredToken := azcore.AccessToken{Token: "expired_token", ExpiresOn: time.Now().Add(-10 * time.Minute)} - newTokenString := "refreshed_token" + newTokenString := "refreshed" newAADToken := azcore.AccessToken{Token: "new_token", ExpiresOn: time.Now().Add(10 * time.Minute)} refreshToken := azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{ ACRRefreshToken: azcontainerregistry.ACRRefreshToken{RefreshToken: &newTokenString}, @@ -174,7 +174,7 @@ func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { // Validate success and token refresh assert.NoError(t, err) - assert.Equal(t, "refreshed_token", authConfig.Password) + assert.Equal(t, "refreshed", authConfig.Password) } // Test failed token refresh diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 827740d34..bad1ca315 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -25,46 +25,16 @@ import ( re "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/internal/logger" provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + "github.com/ratify-project/ratify/pkg/utils/azureauth" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) -// AuthClientFactory defines an interface for creating an authentication client. -type AuthClientFactory interface { - CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) -} - -// RegistryHostGetter defines an interface for getting the registry host. -type RegistryHostGetter interface { - GetRegistryHost(artifact string) (string, error) -} - // AADAccessTokenGetter defines an interface for getting an AAD access token. type AADAccessTokenGetter interface { GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) } -// MetricsReporter defines an interface for reporting metrics. -type MetricsReporter interface { - ReportMetrics(ctx context.Context, duration int64, artifactHostName string) -} - -// DefaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. -type DefaultAuthClientFactoryImpl struct{} - -func (f *DefaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return DefaultAuthClientFactory(serverURL, options) -} - -// DefaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. -type DefaultRegistryHostGetterImpl struct{} - -func (g *DefaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { - // Implement the logic to get the registry host - return provider.GetRegistryHostName(artifact) - // return artifactHost, nil // Replace with actual logic -} - // DefaultAADAccessTokenGetterImpl is the default implementation of AADAccessTokenGetter. type DefaultAADAccessTokenGetterImpl struct{} @@ -72,6 +42,15 @@ func (g *DefaultAADAccessTokenGetterImpl) GetAADAccessToken(ctx context.Context, return DefaultGetAADAccessToken(ctx, tenantID, clientID, resource) } +func DefaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) +} + +// MetricsReporter defines an interface for reporting metrics. +type MetricsReporter interface { + ReportMetrics(ctx context.Context, duration int64, artifactHostName string) +} + // DefaultMetricsReporterImpl is the default implementation of MetricsReporter. type DefaultMetricsReporterImpl struct{} @@ -79,7 +58,12 @@ func (r *DefaultMetricsReporterImpl) ReportMetrics(ctx context.Context, duration DefaultReportMetrics(ctx, duration, artifactHostName) } +func DefaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) +} + type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name + type WIAuthProvider struct { aadToken confidential.AuthResult tenantID string diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index c8be41b6f..7924600ba 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -31,26 +31,6 @@ import ( "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" ) -// MockAuthClientFactory for creating AuthClient -type MockAuthClientFactory struct { - mock.Mock -} - -func (m *MockAuthClientFactory) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - args := m.Called(serverURL, options) - return args.Get(0).(AuthClient), args.Error(1) -} - -// MockRegistryHostGetter for retrieving registry host -type MockRegistryHostGetter struct { - mock.Mock -} - -func (m *MockRegistryHostGetter) GetRegistryHost(artifact string) (string, error) { - args := m.Called(artifact) - return args.String(0), args.Error(1) -} - // MockAADAccessTokenGetter for retrieving AAD access token type MockAADAccessTokenGetter struct { mock.Mock @@ -70,16 +50,6 @@ func (m *MockMetricsReporter) ReportMetrics(ctx context.Context, duration int64, m.Called(ctx, duration, artifactHostName) } -// MockAuthClient for the Azure auth client -type MockAuthClient struct { - mock.Mock -} - -func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { - args := m.Called(ctx, grantType, service, options) - return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) -} - // Test for successful Provide function func TestWIAuthProvider_Provide_Success(t *testing.T) { // Mock all dependencies diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go index db679feb6..2e9da285f 100644 --- a/pkg/common/oras/authprovider/azure/helper.go +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -19,11 +19,21 @@ import ( "context" "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" - "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" - "github.com/ratify-project/ratify/internal/logger" - "github.com/ratify-project/ratify/pkg/utils/azureauth" + provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" ) +// AuthClientFactory defines an interface for creating an authentication client. +type AuthClientFactory interface { + CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) +} + +// DefaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. +type DefaultAuthClientFactoryImpl struct{} + +func (f *DefaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return DefaultAuthClientFactory(serverURL, options) +} + func DefaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { @@ -32,14 +42,6 @@ func DefaultAuthClientFactory(serverURL string, options *azcontainerregistry.Aut return &AuthenticationClientWrapper{client: client}, nil } -func DefaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { - return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) -} - -func DefaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { - logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) -} - type AuthenticationClientWrapper struct { client *azcontainerregistry.AuthenticationClient } @@ -51,3 +53,16 @@ func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(c type AuthClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } + +// RegistryHostGetter defines an interface for getting the registry host. +type RegistryHostGetter interface { + GetRegistryHost(artifact string) (string, error) +} + +// DefaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. +type DefaultRegistryHostGetterImpl struct{} + +func (g *DefaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { + // Implement the logic to get the registry host + return provider.GetRegistryHostName(artifact) +} diff --git a/pkg/common/oras/authprovider/azure/helper_test.go b/pkg/common/oras/authprovider/azure/helper_test.go new file mode 100644 index 000000000..d7d9b330f --- /dev/null +++ b/pkg/common/oras/authprovider/azure/helper_test.go @@ -0,0 +1,136 @@ +/* +Copyright The Ratify 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. +*/ + +package azure + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + "github.com/stretchr/testify/mock" +) + +// MockAuthClient is a mock implementation of AuthClient. +type MockAuthClient struct { + mock.Mock +} + +// Mock method for ExchangeAADAccessTokenForACRRefreshToken +func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + args := m.Called(ctx, grantType, service, options) + return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) +} + +// MockAuthClientFactory is a mock implementation of AuthClientFactory. +type MockAuthClientFactory struct { + mock.Mock +} + +// Mock method for CreateAuthClient +func (m *MockAuthClientFactory) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + args := m.Called(serverURL, options) + return args.Get(0).(AuthClient), args.Error(1) +} + +// MockRegistryHostGetter is a mock implementation of RegistryHostGetter. +type MockRegistryHostGetter struct { + mock.Mock +} + +// Mock method for GetRegistryHost +func (m *MockRegistryHostGetter) GetRegistryHost(artifact string) (string, error) { + args := m.Called(artifact) + return args.String(0), args.Error(1) +} + +// // TestDefaultAuthClientFactoryImpl tests the default factory implementation. +// func TestDefaultAuthClientFactoryImpl(t *testing.T) { +// mockFactory := new(MockAuthClientFactory) +// mockAuthClient := new(MockAuthClient) + +// serverURL := "https://example.azurecr.io" +// options := &azcontainerregistry.AuthenticationClientOptions{} + +// // Set up expectations +// mockFactory.On("CreateAuthClient", serverURL, options).Return(mockAuthClient, nil) + +// factory := &DefaultAuthClientFactoryImpl{} +// client, err := factory.CreateAuthClient(serverURL, options) + +// // Verify expectations +// mockFactory.AssertCalled(t, "CreateAuthClient", serverURL, options) +// assert.NoError(t, err) +// assert.NotNil(t, client) +// } + +// // TestDefaultAuthClientFactory_Error tests error handling during client creation. +// func TestDefaultAuthClientFactory_Error(t *testing.T) { +// mockFactory := new(MockAuthClientFactory) + +// serverURL := "https://example.azurecr.io" +// options := &azcontainerregistry.AuthenticationClientOptions{} +// expectedError := errors.New("failed to create client") + +// // Set up expectations +// mockFactory.On("CreateAuthClient", serverURL, options).Return(nil, expectedError) + +// factory := &DefaultAuthClientFactoryImpl{} +// client, err := factory.CreateAuthClient(serverURL, options) + +// // Verify expectations +// mockFactory.AssertCalled(t, "CreateAuthClient", serverURL, options) +// assert.Error(t, err) +// assert.Nil(t, client) +// assert.Equal(t, expectedError, err) +// } + +// // TestGetRegistryHost tests the GetRegistryHost function. +// func TestGetRegistryHost(t *testing.T) { +// mockGetter := new(MockRegistryHostGetter) + +// artifact := "test/artifact" +// expectedHost := "example.azurecr.io" + +// // Set up expectations +// mockGetter.On("GetRegistryHost", artifact).Return(expectedHost, nil) + +// getter := &DefaultRegistryHostGetterImpl{} +// host, err := getter.GetRegistryHost(artifact) + +// // Verify expectations +// mockGetter.AssertCalled(t, "GetRegistryHost", artifact) +// assert.NoError(t, err) +// assert.Equal(t, expectedHost, host) +// } + +// // TestGetRegistryHost_Error tests error handling in GetRegistryHost. +// func TestGetRegistryHost_Error(t *testing.T) { +// mockGetter := new(MockRegistryHostGetter) + +// artifact := "test/artifact" +// expectedError := errors.New("failed to get registry host") + +// // Set up expectations +// mockGetter.On("GetRegistryHost", artifact).Return("", expectedError) + +// getter := &DefaultRegistryHostGetterImpl{} +// host, err := getter.GetRegistryHost(artifact) + +// // Verify expectations +// mockGetter.AssertCalled(t, "GetRegistryHost", artifact) +// assert.Error(t, err) +// assert.Empty(t, host) +// assert.Equal(t, expectedError, err) +// } From 947f28ca499014f38566833cae08cd68228168be Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Mon, 14 Oct 2024 09:24:53 +1000 Subject: [PATCH 51/65] chore: unit tests for the helper.go file Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 10 +- .../authprovider/azure/azureidentity_test.go | 31 ++++- .../azure/azureworkloadidentity.go | 2 +- .../azure/azureworkloadidentity_test.go | 6 +- pkg/common/oras/authprovider/azure/helper.go | 13 ++- .../oras/authprovider/azure/helper_test.go | 108 ++++++------------ 6 files changed, 84 insertions(+), 86 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index fd8466762..eea251325 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -41,13 +41,13 @@ type ManagedIdentityTokenGetter interface { type DefaultManagedIdentityTokenGetterImpl struct{} func (g *DefaultManagedIdentityTokenGetterImpl) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { - return getManagedIdentityToken(ctx, clientID) + return getManagedIdentityToken(ctx, clientID, azidentity.NewManagedIdentityCredential) } -func getManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { +func getManagedIdentityToken(ctx context.Context, clientID string, newCredentialFunc func(opts *azidentity.ManagedIdentityCredentialOptions) (*azidentity.ManagedIdentityCredential, error)) (azcore.AccessToken, error) { id := azidentity.ClientID(clientID) opts := azidentity.ManagedIdentityCredentialOptions{ID: id} - cred, err := azidentity.NewManagedIdentityCredential(&opts) + cred, err := newCredentialFunc(&opts) if err != nil { return azcore.AccessToken{}, err } @@ -110,7 +110,7 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider return nil, err } // retrieve an AAD Access token - token, err := getManagedIdentityToken(context.Background(), client) + token, err := getManagedIdentityToken(context.Background(), client, azidentity.NewManagedIdentityCredential) if err != nil { return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureManagedIdentityLink, err, "", re.HideStackTrace) } @@ -177,7 +177,7 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider response, err := client.ExchangeAADAccessTokenForACRRefreshToken( ctx, - "access_token", + azcontainerregistry.PostContentSchemaGrantType("access_token"), artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ AccessToken: &d.identityToken.Token, diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index a47854252..1ab6ff820 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" azcontainerregistry "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/ratify-project/ratify/pkg/common/oras/authprovider" @@ -155,7 +156,7 @@ func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { // Setup mock expectations mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockManagedIdentityTokenGetter.On("GetManagedIdentityToken", mock.Anything, "clientID").Return(newAADToken, nil) // Initialize provider with expired token @@ -241,3 +242,31 @@ func TestMIAuthProvider_Provide_InvalidHostName(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "HOST_NAME_INVALID") } + +// Unit tests +func TestGetManagedIdentityToken(t *testing.T) { + ctx := context.Background() + clientID := "test-client-id" + expectedToken := azcore.AccessToken{Token: "test-token", ExpiresOn: time.Now().Add(time.Hour)} + + mockGetter := new(MockManagedIdentityTokenGetter) + mockGetter.On("GetManagedIdentityToken", ctx, clientID).Return(expectedToken, nil) + + token, err := mockGetter.GetManagedIdentityToken(ctx, clientID) + assert.Nil(t, err) + assert.Equal(t, expectedToken, token) +} + +func TestGetManagedIdentityToken_Error(t *testing.T) { + ctx := context.Background() + clientID := "test-client-id" + + // Mock the newCredentialFunc to return an error + mockNewCredentialFunc := func(_ *azidentity.ManagedIdentityCredentialOptions) (*azidentity.ManagedIdentityCredential, error) { + return nil, assert.AnError + } + + token, err := getManagedIdentityToken(ctx, clientID, mockNewCredentialFunc) + assert.NotNil(t, err) + assert.Equal(t, azcore.AccessToken{}, token) +} diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index bad1ca315..88c67f7d7 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -180,7 +180,7 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider startTime := time.Now() response, err := client.ExchangeAADAccessTokenForACRRefreshToken( ctx, - "access_token", + azcontainerregistry.PostContentSchemaGrantType("access_token"), artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ AccessToken: &d.aadToken.AccessToken, diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 7924600ba..4a7ccdf7e 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -69,7 +69,7 @@ func TestWIAuthProvider_Provide_Success(t *testing.T) { // Set expectations for mocked functions mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(initialToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() @@ -113,7 +113,7 @@ func TestWIAuthProvider_Provide_RefreshToken(t *testing.T) { // Set expectations for mocked functions mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() @@ -225,7 +225,7 @@ func TestWIAuthProvider_Provide_TokenRefresh_Success(t *testing.T) { // Set expectations mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, "access_token", "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go index 2e9da285f..3ca75a5a6 100644 --- a/pkg/common/oras/authprovider/azure/helper.go +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -42,16 +42,21 @@ func DefaultAuthClientFactory(serverURL string, options *azcontainerregistry.Aut return &AuthenticationClientWrapper{client: client}, nil } +// Define the interface for azcontainerregistry.AuthenticationClient methods used +type AuthenticationClientInterface interface { + ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) +} + type AuthenticationClientWrapper struct { - client *azcontainerregistry.AuthenticationClient + client AuthenticationClientInterface } -func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { - return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, azcontainerregistry.PostContentSchemaGrantType(grantType), service, options) +func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { + return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, grantType, service, options) } type AuthClient interface { - ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) + ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } // RegistryHostGetter defines an interface for getting the registry host. diff --git a/pkg/common/oras/authprovider/azure/helper_test.go b/pkg/common/oras/authprovider/azure/helper_test.go index d7d9b330f..365c681c3 100644 --- a/pkg/common/oras/authprovider/azure/helper_test.go +++ b/pkg/common/oras/authprovider/azure/helper_test.go @@ -17,8 +17,10 @@ package azure import ( "context" + "testing" "github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -28,7 +30,7 @@ type MockAuthClient struct { } // Mock method for ExchangeAADAccessTokenForACRRefreshToken -func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { +func (m *MockAuthClient) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { args := m.Called(ctx, grantType, service, options) return args.Get(0).(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse), args.Error(1) } @@ -55,82 +57,44 @@ func (m *MockRegistryHostGetter) GetRegistryHost(artifact string) (string, error return args.String(0), args.Error(1) } -// // TestDefaultAuthClientFactoryImpl tests the default factory implementation. -// func TestDefaultAuthClientFactoryImpl(t *testing.T) { -// mockFactory := new(MockAuthClientFactory) -// mockAuthClient := new(MockAuthClient) +func TestDefaultAuthClientFactoryImpl_CreateAuthClient(t *testing.T) { + factory := &DefaultAuthClientFactoryImpl{} + serverURL := "https://example.com" + options := &azcontainerregistry.AuthenticationClientOptions{} -// serverURL := "https://example.azurecr.io" -// options := &azcontainerregistry.AuthenticationClientOptions{} - -// // Set up expectations -// mockFactory.On("CreateAuthClient", serverURL, options).Return(mockAuthClient, nil) - -// factory := &DefaultAuthClientFactoryImpl{} -// client, err := factory.CreateAuthClient(serverURL, options) - -// // Verify expectations -// mockFactory.AssertCalled(t, "CreateAuthClient", serverURL, options) -// assert.NoError(t, err) -// assert.NotNil(t, client) -// } - -// // TestDefaultAuthClientFactory_Error tests error handling during client creation. -// func TestDefaultAuthClientFactory_Error(t *testing.T) { -// mockFactory := new(MockAuthClientFactory) - -// serverURL := "https://example.azurecr.io" -// options := &azcontainerregistry.AuthenticationClientOptions{} -// expectedError := errors.New("failed to create client") - -// // Set up expectations -// mockFactory.On("CreateAuthClient", serverURL, options).Return(nil, expectedError) - -// factory := &DefaultAuthClientFactoryImpl{} -// client, err := factory.CreateAuthClient(serverURL, options) - -// // Verify expectations -// mockFactory.AssertCalled(t, "CreateAuthClient", serverURL, options) -// assert.Error(t, err) -// assert.Nil(t, client) -// assert.Equal(t, expectedError, err) -// } - -// // TestGetRegistryHost tests the GetRegistryHost function. -// func TestGetRegistryHost(t *testing.T) { -// mockGetter := new(MockRegistryHostGetter) - -// artifact := "test/artifact" -// expectedHost := "example.azurecr.io" - -// // Set up expectations -// mockGetter.On("GetRegistryHost", artifact).Return(expectedHost, nil) + client, err := factory.CreateAuthClient(serverURL, options) + assert.Nil(t, err) + assert.NotNil(t, client) +} -// getter := &DefaultRegistryHostGetterImpl{} -// host, err := getter.GetRegistryHost(artifact) +func TestDefaultAuthClientFactory(t *testing.T) { + serverURL := "https://example.com" + options := &azcontainerregistry.AuthenticationClientOptions{} -// // Verify expectations -// mockGetter.AssertCalled(t, "GetRegistryHost", artifact) -// assert.NoError(t, err) -// assert.Equal(t, expectedHost, host) -// } + client, err := DefaultAuthClientFactory(serverURL, options) + assert.Nil(t, err) + assert.NotNil(t, client) +} -// // TestGetRegistryHost_Error tests error handling in GetRegistryHost. -// func TestGetRegistryHost_Error(t *testing.T) { -// mockGetter := new(MockRegistryHostGetter) +func TestDefaultRegistryHostGetterImpl_GetRegistryHost(t *testing.T) { + getter := &DefaultRegistryHostGetterImpl{} + artifact := "example.azurecr.io/myArtifact" -// artifact := "test/artifact" -// expectedError := errors.New("failed to get registry host") + host, err := getter.GetRegistryHost(artifact) + assert.Nil(t, err) + assert.Equal(t, "example.azurecr.io", host) +} -// // Set up expectations -// mockGetter.On("GetRegistryHost", artifact).Return("", expectedError) +func TestAuthenticationClientWrapper_ExchangeAADAccessTokenForACRRefreshToken(t *testing.T) { + mockClient := new(MockAuthClient) + wrapper := &AuthenticationClientWrapper{client: mockClient} + ctx := context.Background() + grantType := azcontainerregistry.PostContentSchemaGrantType("grantType") + service := "service" + options := &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{} -// getter := &DefaultRegistryHostGetterImpl{} -// host, err := getter.GetRegistryHost(artifact) + mockClient.On("ExchangeAADAccessTokenForACRRefreshToken", ctx, grantType, service, options).Return(azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse{}, nil) -// // Verify expectations -// mockGetter.AssertCalled(t, "GetRegistryHost", artifact) -// assert.Error(t, err) -// assert.Empty(t, host) -// assert.Equal(t, expectedError, err) -// } + _, err := wrapper.ExchangeAADAccessTokenForACRRefreshToken(ctx, grantType, service, options) + assert.Nil(t, err) +} From d50d3068ac792ad1d231021e779ad979824b0b4c Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 15 Oct 2024 14:29:39 +1000 Subject: [PATCH 52/65] chore: create a const from the repetitive string Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureidentity.go | 2 +- pkg/common/oras/authprovider/azure/azureidentity_test.go | 2 +- pkg/common/oras/authprovider/azure/azureworkloadidentity.go | 2 +- .../oras/authprovider/azure/azureworkloadidentity_test.go | 6 +++--- pkg/common/oras/authprovider/azure/helper.go | 2 ++ 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index eea251325..b6304b4b4 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -177,7 +177,7 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider response, err := client.ExchangeAADAccessTokenForACRRefreshToken( ctx, - azcontainerregistry.PostContentSchemaGrantType("access_token"), + azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ AccessToken: &d.identityToken.Token, diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 1ab6ff820..eab8e66e7 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -156,7 +156,7 @@ func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { // Setup mock expectations mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockManagedIdentityTokenGetter.On("GetManagedIdentityToken", mock.Anything, "clientID").Return(newAADToken, nil) // Initialize provider with expired token diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 88c67f7d7..096473971 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -180,7 +180,7 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider startTime := time.Now() response, err := client.ExchangeAADAccessTokenForACRRefreshToken( ctx, - azcontainerregistry.PostContentSchemaGrantType("access_token"), + azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), artifactHostName, &azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions{ AccessToken: &d.aadToken.AccessToken, diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 4a7ccdf7e..54577cc9b 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -69,7 +69,7 @@ func TestWIAuthProvider_Provide_Success(t *testing.T) { // Set expectations for mocked functions mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(initialToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() @@ -113,7 +113,7 @@ func TestWIAuthProvider_Provide_RefreshToken(t *testing.T) { // Set expectations for mocked functions mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() @@ -225,7 +225,7 @@ func TestWIAuthProvider_Provide_TokenRefresh_Success(t *testing.T) { // Set expectations mockRegistryHostGetter.On("GetRegistryHost", "artifact_name").Return("example.azurecr.io", nil) mockAuthClientFactory.On("CreateAuthClient", "https://example.azurecr.io", mock.Anything).Return(mockAuthClient, nil) - mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType("access_token"), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) + mockAuthClient.On("ExchangeAADAccessTokenForACRRefreshToken", mock.Anything, azcontainerregistry.PostContentSchemaGrantType(GrantTypeAccessToken), "example.azurecr.io", mock.Anything).Return(refreshToken, nil) mockAADAccessTokenGetter.On("GetAADAccessToken", mock.Anything, "tenantID", "clientID", mock.Anything).Return(newToken, nil) mockMetricsReporter.On("ReportMetrics", mock.Anything, mock.Anything, "example.azurecr.io").Return() diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go index 3ca75a5a6..a98561641 100644 --- a/pkg/common/oras/authprovider/azure/helper.go +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -22,6 +22,8 @@ import ( provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" ) +const GrantTypeAccessToken = "access_token" + // AuthClientFactory defines an interface for creating an authentication client. type AuthClientFactory interface { CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) From ac8e29e9966d661fcaadefc12f38097182661d70 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 15 Oct 2024 15:00:02 +1000 Subject: [PATCH 53/65] chore: address comments Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 10 +++---- .../azure/azureworkloadidentity.go | 30 +++++++++---------- pkg/common/oras/authprovider/azure/helper.go | 16 +++++----- .../oras/authprovider/azure/helper_test.go | 6 ++-- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index b6304b4b4..0485ec1fa 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -37,10 +37,10 @@ type ManagedIdentityTokenGetter interface { GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) } -// DefaultManagedIdentityTokenGetterImpl is the default implementation of getManagedIdentityToken. -type DefaultManagedIdentityTokenGetterImpl struct{} +// defaultManagedIdentityTokenGetterImpl is the default implementation of getManagedIdentityToken. +type defaultManagedIdentityTokenGetterImpl struct{} -func (g *DefaultManagedIdentityTokenGetterImpl) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { +func (g *defaultManagedIdentityTokenGetterImpl) GetManagedIdentityToken(ctx context.Context, clientID string) (azcore.AccessToken, error) { return getManagedIdentityToken(ctx, clientID, azidentity.NewManagedIdentityCredential) } @@ -119,8 +119,8 @@ func (s *azureManagedIdentityProviderFactory) Create(authProviderConfig provider identityToken: token, clientID: client, tenantID: tenant, - authClientFactory: &DefaultAuthClientFactoryImpl{}, // Concrete implementation - getManagedIdentityToken: &DefaultManagedIdentityTokenGetterImpl{}, // Concrete implementation + authClientFactory: &defaultAuthClientFactoryImpl{}, // Concrete implementation + getManagedIdentityToken: &defaultManagedIdentityTokenGetterImpl{}, // Concrete implementation }, nil } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 096473971..404f91670 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -35,14 +35,14 @@ type AADAccessTokenGetter interface { GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) } -// DefaultAADAccessTokenGetterImpl is the default implementation of AADAccessTokenGetter. -type DefaultAADAccessTokenGetterImpl struct{} +// defaultAADAccessTokenGetterImpl is the default implementation of AADAccessTokenGetter. +type defaultAADAccessTokenGetterImpl struct{} -func (g *DefaultAADAccessTokenGetterImpl) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { - return DefaultGetAADAccessToken(ctx, tenantID, clientID, resource) +func (g *defaultAADAccessTokenGetterImpl) GetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { + return defaultGetAADAccessToken(ctx, tenantID, clientID, resource) } -func DefaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { +func defaultGetAADAccessToken(ctx context.Context, tenantID, clientID, resource string) (confidential.AuthResult, error) { return azureauth.GetAADAccessToken(ctx, tenantID, clientID, resource) } @@ -51,14 +51,14 @@ type MetricsReporter interface { ReportMetrics(ctx context.Context, duration int64, artifactHostName string) } -// DefaultMetricsReporterImpl is the default implementation of MetricsReporter. -type DefaultMetricsReporterImpl struct{} +// defaultMetricsReporterImpl is the default implementation of MetricsReporter. +type defaultMetricsReporterImpl struct{} -func (r *DefaultMetricsReporterImpl) ReportMetrics(ctx context.Context, duration int64, artifactHostName string) { - DefaultReportMetrics(ctx, duration, artifactHostName) +func (r *defaultMetricsReporterImpl) ReportMetrics(ctx context.Context, duration int64, artifactHostName string) { + defaultReportMetrics(ctx, duration, artifactHostName) } -func DefaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { +func defaultReportMetrics(ctx context.Context, duration int64, artifactHostName string) { logger.GetLogger(ctx, logOpt).Infof("Metrics Report: Duration=%dms, Host=%s", duration, artifactHostName) } @@ -114,7 +114,7 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } // retrieve an AAD Access token - token, err := DefaultGetAADAccessToken(context.Background(), tenant, clientID, AADResource) + token, err := defaultGetAADAccessToken(context.Background(), tenant, clientID, AADResource) if err != nil { return nil, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "", re.HideStackTrace) } @@ -123,10 +123,10 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider aadToken: token, tenantID: tenant, clientID: clientID, - authClientFactory: &DefaultAuthClientFactoryImpl{}, // Concrete implementation - getRegistryHost: &DefaultRegistryHostGetterImpl{}, // Concrete implementation - getAADAccessToken: &DefaultAADAccessTokenGetterImpl{}, // Concrete implementation - reportMetrics: &DefaultMetricsReporterImpl{}, + authClientFactory: &defaultAuthClientFactoryImpl{}, // Concrete implementation + getRegistryHost: &defaultRegistryHostGetterImpl{}, // Concrete implementation + getAADAccessToken: &defaultAADAccessTokenGetterImpl{}, // Concrete implementation + reportMetrics: &defaultMetricsReporterImpl{}, }, nil } diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go index a98561641..b7fc61eba 100644 --- a/pkg/common/oras/authprovider/azure/helper.go +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -29,14 +29,14 @@ type AuthClientFactory interface { CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) } -// DefaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. -type DefaultAuthClientFactoryImpl struct{} +// defaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. +type defaultAuthClientFactoryImpl struct{} -func (f *DefaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { - return DefaultAuthClientFactory(serverURL, options) +func (f *defaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { + return defaultAuthClientFactory(serverURL, options) } -func DefaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { +func defaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { return nil, err @@ -66,10 +66,10 @@ type RegistryHostGetter interface { GetRegistryHost(artifact string) (string, error) } -// DefaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. -type DefaultRegistryHostGetterImpl struct{} +// defaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. +type defaultRegistryHostGetterImpl struct{} -func (g *DefaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { +func (g *defaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { // Implement the logic to get the registry host return provider.GetRegistryHostName(artifact) } diff --git a/pkg/common/oras/authprovider/azure/helper_test.go b/pkg/common/oras/authprovider/azure/helper_test.go index 365c681c3..49c811f0f 100644 --- a/pkg/common/oras/authprovider/azure/helper_test.go +++ b/pkg/common/oras/authprovider/azure/helper_test.go @@ -58,7 +58,7 @@ func (m *MockRegistryHostGetter) GetRegistryHost(artifact string) (string, error } func TestDefaultAuthClientFactoryImpl_CreateAuthClient(t *testing.T) { - factory := &DefaultAuthClientFactoryImpl{} + factory := &defaultAuthClientFactoryImpl{} serverURL := "https://example.com" options := &azcontainerregistry.AuthenticationClientOptions{} @@ -71,13 +71,13 @@ func TestDefaultAuthClientFactory(t *testing.T) { serverURL := "https://example.com" options := &azcontainerregistry.AuthenticationClientOptions{} - client, err := DefaultAuthClientFactory(serverURL, options) + client, err := defaultAuthClientFactory(serverURL, options) assert.Nil(t, err) assert.NotNil(t, client) } func TestDefaultRegistryHostGetterImpl_GetRegistryHost(t *testing.T) { - getter := &DefaultRegistryHostGetterImpl{} + getter := &defaultRegistryHostGetterImpl{} artifact := "example.azurecr.io/myArtifact" host, err := getter.GetRegistryHost(artifact) From 234492768e142643ea43fd04e8526c710935dea6 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Thu, 17 Oct 2024 18:58:15 +1000 Subject: [PATCH 54/65] chore: address comments Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureidentity.go | 3 ++- pkg/common/oras/authprovider/azure/azureworkloadidentity.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 0485ec1fa..0df0a2f8d 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -172,7 +172,8 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { - return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) + // return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.WithError(err).WithDetail("failed to create authentication client for container registry by azure managed identity token") } response, err := client.ExchangeAADAccessTokenForACRRefreshToken( diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 404f91670..a79c98cc8 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -174,7 +174,8 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { - return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) + // return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) + return provider.AuthConfig{}, re.ErrorCodeAuthDenied.WithError(err).WithDetail("failed to create authentication client for container registry by azure managed identity token") } startTime := time.Now() From 10ea3e29d5bcc07f17db3b3af4b06aad9c662dfb Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 22 Oct 2024 09:37:19 +1000 Subject: [PATCH 55/65] chore: address comments Signed-off-by: Shahram Kalantari --- .../oras/authprovider/azure/azureidentity.go | 4 +- .../authprovider/azure/azureidentity_test.go | 6 +- .../azure/azureworkloadidentity.go | 30 +++---- .../azure/azureworkloadidentity_test.go | 84 +++++++++---------- pkg/common/oras/authprovider/azure/helper.go | 11 ++- 5 files changed, 72 insertions(+), 63 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 0df0a2f8d..97a687ee0 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -65,7 +65,7 @@ type MIAuthProvider struct { clientID string tenantID string authClientFactory AuthClientFactory - getRegistryHost RegistryHostGetter + registryHostGetter RegistryHostGetter getManagedIdentityToken ManagedIdentityTokenGetter } @@ -150,7 +150,7 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider } // parse the artifact reference string to extract the registry host name - artifactHostName, err := d.getRegistryHost.GetRegistryHost(artifact) + artifactHostName, err := d.registryHostGetter.GetRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index eab8e66e7..8d466d3d1 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -165,7 +165,7 @@ func TestMIAuthProvider_Provide_TokenRefreshSuccess(t *testing.T) { clientID: "clientID", tenantID: "tenantID", authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, + registryHostGetter: mockRegistryHostGetter, getManagedIdentityToken: mockManagedIdentityTokenGetter, } @@ -198,7 +198,7 @@ func TestMIAuthProvider_Provide_TokenRefreshFailure(t *testing.T) { clientID: "clientID", tenantID: "tenantID", authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, + registryHostGetter: mockRegistryHostGetter, getManagedIdentityToken: mockManagedIdentityTokenGetter, } @@ -230,7 +230,7 @@ func TestMIAuthProvider_Provide_InvalidHostName(t *testing.T) { clientID: "clientID", tenantID: "tenantID", authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, + registryHostGetter: mockRegistryHostGetter, getManagedIdentityToken: mockManagedIdentityTokenGetter, } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index a79c98cc8..1ef2c490d 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -65,13 +65,13 @@ func defaultReportMetrics(ctx context.Context, duration int64, artifactHostName type AzureWIProviderFactory struct{} //nolint:revive // ignore linter to have unique type name type WIAuthProvider struct { - aadToken confidential.AuthResult - tenantID string - clientID string - authClientFactory AuthClientFactory - getRegistryHost RegistryHostGetter - getAADAccessToken AADAccessTokenGetter - reportMetrics MetricsReporter + aadToken confidential.AuthResult + tenantID string + clientID string + authClientFactory AuthClientFactory + registryHostGetter RegistryHostGetter + getAADAccessToken AADAccessTokenGetter + reportMetrics MetricsReporter } type azureWIAuthProviderConf struct { @@ -120,13 +120,13 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider } return &WIAuthProvider{ - aadToken: token, - tenantID: tenant, - clientID: clientID, - authClientFactory: &defaultAuthClientFactoryImpl{}, // Concrete implementation - getRegistryHost: &defaultRegistryHostGetterImpl{}, // Concrete implementation - getAADAccessToken: &defaultAADAccessTokenGetterImpl{}, // Concrete implementation - reportMetrics: &defaultMetricsReporterImpl{}, + aadToken: token, + tenantID: tenant, + clientID: clientID, + authClientFactory: &defaultAuthClientFactoryImpl{}, // Concrete implementation + registryHostGetter: &defaultRegistryHostGetterImpl{}, // Concrete implementation + getAADAccessToken: &defaultAADAccessTokenGetterImpl{}, // Concrete implementation + reportMetrics: &defaultMetricsReporterImpl{}, }, nil } @@ -152,7 +152,7 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider } // parse the artifact reference string to extract the registry host name - artifactHostName, err := d.getRegistryHost.GetRegistryHost(artifact) + artifactHostName, err := d.registryHostGetter.GetRegistryHost(artifact) if err != nil { return provider.AuthConfig{}, re.ErrorCodeHostNameInvalid.WithComponentType(re.AuthProvider) } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 54577cc9b..b2ffaa0cd 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -75,13 +75,13 @@ func TestWIAuthProvider_Provide_Success(t *testing.T) { // Create WIAuthProvider provider := WIAuthProvider{ - aadToken: initialToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: initialToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method @@ -119,13 +119,13 @@ func TestWIAuthProvider_Provide_RefreshToken(t *testing.T) { // Create WIAuthProvider with expired token provider := WIAuthProvider{ - aadToken: expiredToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method @@ -154,13 +154,13 @@ func TestWIAuthProvider_Provide_AADTokenFailure(t *testing.T) { // Create WIAuthProvider with expired token provider := WIAuthProvider{ - aadToken: expiredToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method @@ -231,13 +231,13 @@ func TestWIAuthProvider_Provide_TokenRefresh_Success(t *testing.T) { // Create WIAuthProvider with expired token provider := WIAuthProvider{ - aadToken: expiredToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method @@ -266,13 +266,13 @@ func TestWIAuthProvider_Provide_TokenRefreshFailure(t *testing.T) { // Create WIAuthProvider with expired token provider := WIAuthProvider{ - aadToken: expiredToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: expiredToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method @@ -314,13 +314,13 @@ func TestWIAuthProvider_Provide_InvalidHostName(t *testing.T) { // Create WIAuthProvider with valid token provider := WIAuthProvider{ - aadToken: validToken, - tenantID: "tenantID", - clientID: "clientID", - authClientFactory: mockAuthClientFactory, - getRegistryHost: mockRegistryHostGetter, - getAADAccessToken: mockAADAccessTokenGetter, - reportMetrics: mockMetricsReporter, + aadToken: validToken, + tenantID: "tenantID", + clientID: "clientID", + authClientFactory: mockAuthClientFactory, + registryHostGetter: mockRegistryHostGetter, + getAADAccessToken: mockAADAccessTokenGetter, + reportMetrics: mockMetricsReporter, } // Call Provide method diff --git a/pkg/common/oras/authprovider/azure/helper.go b/pkg/common/oras/authprovider/azure/helper.go index b7fc61eba..beafa4db0 100644 --- a/pkg/common/oras/authprovider/azure/helper.go +++ b/pkg/common/oras/authprovider/azure/helper.go @@ -32,10 +32,13 @@ type AuthClientFactory interface { // defaultAuthClientFactoryImpl is the default implementation of AuthClientFactory. type defaultAuthClientFactoryImpl struct{} +// creates an AuthClient using the default factory implementation. +// Return an AuthClient and an error if the client creation fails. func (f *defaultAuthClientFactoryImpl) CreateAuthClient(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { return defaultAuthClientFactory(serverURL, options) } +// Define a helper function that creates an instance of AuthenticationClientWrapper. func defaultAuthClientFactory(serverURL string, options *azcontainerregistry.AuthenticationClientOptions) (AuthClient, error) { client, err := azcontainerregistry.NewAuthenticationClient(serverURL, options) if err != nil { @@ -49,14 +52,19 @@ type AuthenticationClientInterface interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } +// Define the wrapper for AuthenticationClientInterface type AuthenticationClientWrapper struct { client AuthenticationClientInterface } +// A wrapper method that calls the underlying AuthenticationClientInterface's method. +// Exchanges an AAD access token for an ACR refresh token. func (w *AuthenticationClientWrapper) ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) { return w.client.ExchangeAADAccessTokenForACRRefreshToken(ctx, grantType, service, options) } +// define the interface for authentication operations. +// It includes the method for exchanging an AAD access token for an ACR refresh token. type AuthClient interface { ExchangeAADAccessTokenForACRRefreshToken(ctx context.Context, grantType azcontainerregistry.PostContentSchemaGrantType, service string, options *azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenOptions) (azcontainerregistry.AuthenticationClientExchangeAADAccessTokenForACRRefreshTokenResponse, error) } @@ -69,7 +77,8 @@ type RegistryHostGetter interface { // defaultRegistryHostGetterImpl is the default implementation of RegistryHostGetter. type defaultRegistryHostGetterImpl struct{} +// Retrieves the registry host name for a given artifact. +// It utilizes the provider's GetRegistryHostName function to perform the lookup. func (g *defaultRegistryHostGetterImpl) GetRegistryHost(artifact string) (string, error) { - // Implement the logic to get the registry host return provider.GetRegistryHostName(artifact) } From 94f899ded0d6ddb619f3e27871ff6e89f215c939 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Tue, 22 Oct 2024 09:48:34 +1000 Subject: [PATCH 56/65] fix: ran go mod tidy Signed-off-by: Shahram Kalantari --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c580fa570..be3607472 100644 --- a/go.mod +++ b/go.mod @@ -239,7 +239,7 @@ require ( golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.25.0 // indirect From 12b4446f1e9b7e117c601ecce6ed66f3a21ec400 Mon Sep 17 00:00:00 2001 From: Sushant Adhikari Date: Tue, 22 Oct 2024 11:25:41 +1100 Subject: [PATCH 57/65] docs: design proposal for tag and digest co-existing [ISSUE 1657] (#1793) --- docs/proposals/Tag-Digest-CoExist.md | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/proposals/Tag-Digest-CoExist.md diff --git a/docs/proposals/Tag-Digest-CoExist.md b/docs/proposals/Tag-Digest-CoExist.md new file mode 100644 index 000000000..1b8e6d5d7 --- /dev/null +++ b/docs/proposals/Tag-Digest-CoExist.md @@ -0,0 +1,93 @@ +# Ratify Mutation: mutual existence of tags and digest. + +## Problems + +Current User scenarios: +1. I want to see which version of my image is deployed but I can only see the digest in the pod image. + 1. This results in the engineer’s time being wasted on manually mapping between the image digest and the version from the image repository. + 1. All my observability dashboards rely on tags but now I need to manually know which tags belong to which digests and somehow also make my dashboards map between those. This is a big hassle. + +## Solution Overview + +The current solution has been chosen on the basis that Ratify is only meant to mutate from tags to digest as tags are mutable and digests are the ultimate source of truth. This solution is also prepared with the community recommendation of using digests instead of tags in mind with digests being the ultimate source of truth for artifact verification. + +However with the digest-only approach having altercations with broader software engineers NOT focused towards security, embedding digests alongside pre-existing tags in the K8s object spec during mutation as a debug-friendly and engineer friendly way forward seems feasible. As the end container orchestration framework such as `containerd` and ultimately `runc` still continue to rely on only the mutated digest to create containers, engineers on the other hand can rely on the pre-existing and untouched tag in the deployed object (Deployment, Pod, StatefulSet etc)’s image spec to know their source of truth for debugging purposes. + +As discussed in the corresponding [Github issue](https://github.com/ratify-project/ratify/issues/1657), having both tag & digest (`:@`) is NOT a recommended option but retains status for backward compatibility, new options with default configuration adhering to this shall be discussed in the design section. + +## Solution Design and Configurations / Proposed Changes + +This section discusses the various additions to the current Ratify Mutator and the corresponding Helm configuration that can help resolve this problem. We also discuss features that the mutator could incorporate to facilitate other additional problems but is a topic of further discussion and debate not within the realm and scope of the problem at hand. + +The solution proposes tweaks to the following sections to fix the problem at hand: +1. The helm chart shall be changed to add the corresponding new configs facilitating these new features for mutation. +1. The new configs shall be respectively passed to `/app/ratify serve` called through the `deployment.yaml` file to then handle the additional configs. +1. These configs then trickle down to the corresponding mutation code block to handle the mutations according to those config. + +### Configurations + +A new config block shall be added in the helm chart’s `values.yaml` which will be used respectively. + +This feature will be available through the new `provider.mutation` config block which will make the `provider.enableMutation` option obsolete. A new boolean sub-config of `provider.mutation.enable` will be added to facilitate this existing feature. + +A new sub config `mutationStyle` will be added to facilitate the type of mutation the user owuld want. +The following sub-options will be added to incorporate additional configuration during mutation. + +| `mutationStyle` | Implemented / Designed in the current solution? | Summary | Incoming Spec Condition | Upstream calls for Subject Descriptor? | Default Option | +| ----------- | ----------------------------------------------- | ------- | ----------------------- | -------------------------------------- | -------------- | +| `retain-mutated-tag` | Y | Retain the pre-existing tag during mutation / Do not strip the tag away if both tag & digest pre-exists |Contains Tag, Does not contain Digest / Contains Tag, Contains Digest | Y / N | false | +| `digest-only` | Y | Mutate tag to digest, stripping the tag away | Contains Tag, Does not contain Digest / Contains Tag, Contains Digest | Y / N | true | + +The options can work in conjunction to provide the required mutation output. +Here, + +`Latest` tag’s digest = `xxxx` + +`v1.2.4` tag’s digest = `yyyy` + + +| Config | Input | Output | +| ------ | ----- | ------ | +| `mutationStyle: "digest-only"` | docker.io/nginx | docker.io/nginx@sha256:xxxx | +| | docker.io/nginx:latest | docker.io/nginx@sha256:xxxx | +| | docker.io/nginx:v1.2.4 | docker.io/nginx@sha256:yyyy | +| | docker.io/nginx:latest@sha256:xxxx | docker.io/nginx@sha256:xxxx | +| | docker.io/nginx:v1.2.4@sha256:yyyy | docker.io/nginx@sha256:yyyy | +| | docker.io/nginx@sha256:xxxx | docker.io/nginx@sha256:xxxx | +| `mutationStyle: "retain-mutated-tag"` | docker.io/nginx | docker.io/nginx:latest@sha256:xxxx | +| | docker.io/nginx:v1.2.4 | docker.io/nginx:v1.2.4@sha256:yyyy | +| | docker.io/nginx:latest@sha256:xxxx | docker.io/nginx:latest@sha256:xxxx | +| | docker.io/nginx:v1.2.4@sha256:yyyy | docker.io/nginx:v1.2.4@sha256:yyyy | +| | docker.io/nginx@sha256:xxxx | docker.io/nginx@sha256:xxxx | + +An enum style config has been proposed so it does not overcrowd the `provider.mutation` block. Both, addition of new mutation styles as well as parsing on the code side will be easier with this approach. + +### Implementation + +The `mutationStyle` config will be implemented to retain the tag in the resulting spec image. The default option for this config will be `digest-only` to keep supporting the existing config parameter. + +Options of provider.mutation.enable and provider.mutation.retainMutatedTag shall be added into Helm. +Example: + +``` +provider: + tls: + crt: "" +... + mutation: + // enable: true + mutationStyle: "digest-only" // (default), other options are "retain-mutated-tag" + enableMutation: true // deprecated, enable and use mutation.enabled instead. If both are used, `mutation.enable` will be preferred +``` + +The `retain-mutated-tag` option will be available for anyone wanting to control if they want to completely remove tags (the default) or have both tags + digest in the resulting output. + +## Performance Impact +The solution should have very little performance impact considering addition of code will not have any network connectivity related feature. Addition of code mostly should adhere to if-else clauses and other small regex additions. + +## Security Considerations +As the change is purely beautification in nature, no security impact could be thought of. +As long as research suggests all major container orchestration frameworks (docker, podman, etc) support the `tag@digest`, however it’s not guaranteed that it’ll work with other smaller frameworks where this hasn’t yet been implemented. + +## Backward Compatibility +The added config won’t be backward compatible if mutation has been disabled, i.e `enableMutation:false` in the helm chart. This means that a new `provider.mutation.enable` will need to be added in the updated helm charts. From 5083cd4682e2b8c2359a3127e325afeee18f7202 Mon Sep 17 00:00:00 2001 From: Juncheng Zhu <74894646+junczhu@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:27:00 +0800 Subject: [PATCH 58/65] docs: add CRL Design (#1789) Signed-off-by: Juncheng Zhu --- docs/design/Certificate Revocation Lists.md | 217 ++++++++++++++++++++ docs/img/CRL/CRL-workflow.png | Bin 0 -> 79472 bytes 2 files changed, 217 insertions(+) create mode 100644 docs/design/Certificate Revocation Lists.md create mode 100644 docs/img/CRL/CRL-workflow.png diff --git a/docs/design/Certificate Revocation Lists.md b/docs/design/Certificate Revocation Lists.md new file mode 100644 index 000000000..40433aa66 --- /dev/null +++ b/docs/design/Certificate Revocation Lists.md @@ -0,0 +1,217 @@ +# CRL and CRL Cache Design + +## Intro + +Certificate validation is an essential step during signature validation. Currently Ratify supports checking for revoked certificates through OCSP supported by notation-go library. However, OCSP validation requires internet connection for each validation while CRL could be cached for better performance. As notary-project is adding the CRL support for notation signature validation, Ratify could utilize it. + +OCSP URLs can be obtained from the certificate's authority information access (AIA) extension as defined in RFC 6960. If the certificate contains multiple OCSP URLs, then each URL is invoked in sequential order, until a 2xx response is received for any of the URL. For each OCSP URL, wait for a default threshold of 2 seconds to receive an OCSP response. The user may be able to configure this threshold. If OCSP response is not available within the timeout threshold the revocation result will be "revocation unavailable". + +If both OCSP URLs and CDP URLs are present, then OCSP is preferred over CRLs. If revocation status cannot be determined using OCSP because of any reason such as unavailability then fallback to using CRLs for revocation check. + +## Goals + +CRL support, including CRL downloading, validation, and revocation list checks. + +- Define a cache provider interface for CRL +- Implement default file-based cache implementation for both CLI and K8S +- Implement preload CRL when cert added from KMP +- Test plan for the CRL feature and performance test between CRL w/o cache. +- Update CRL and CRL caching related documentation + +## Design Points + +**How to Get CRL** + +With no extra configuration, CRL download location (URL) can be obtained from the certificate's CRL Distribution Point (CDP) extension. If the CRL cannot be downloaded within the timeout threshold the revocation result will be "revocation unavailable". More details are showing in the Download CRL section + +**Why Caching** + +Preload CRL can help improve the performance verifier from download CRLs when a single CRL can be up to 32MiB. Prefer ratify cache for reuse the cache provider [interface](https://github.com/ratify-project/ratify/blob/dev/pkg/cache/api.go). Reusing interfaces reduces redundant expressions, helps you easily maintain application objects. + +This design prefer file-based cache over memory-based one to ensure cache would still be available after service restarted. And in CLI scenario memory-based cache would not be applied. +Besides, by using memory-based cache, memory consumption and further cache expiring would increase the design complexity. + +**Why Refresh CRL Cache** + +A CRL is considered expired if the current date is after the `NextUpdate` field in the CRL. Verify that the CRL is valid (not expired) is necessary for revocation check. Monitoring and refreshing CRL on a regular basis can help avoid CRL download timetaken when doing verification by ensure the CRL is valid. + +## Proposed Design + +![image](../img/CRL/CRL-workflow.png) + + +**Ratify Verification Request Path**: + +Step 1: Apply the CRs including certs and CRL config + +Step 2: Load CRLs from cert provided URLs // Implement CanCacheCRL() with GetCertificates() which includes all scenarios that introduce a new cert: KMP, KMP refresher and Verifer Config + +Step 3: Trigger Refresh Monitor and set up refresh schedule // Refresher is based on build-in ticker + +Step 4: Start verify task // Revocation list check is handled by notation verifier. + +Step 5: Load trust policy // Get `Opt.Fetcher` + +Step 6: Load CRL cache + +**CRL Handler**: + +Step 1: Load cert URLs from `[]*x509.Certificate` + +Step 2: Download CRL + +Step 3: Trigger Refresh Monitor, refresh monitor is [`time`](https://pkg.go.dev/time#example-NewTicker) pkg based. + +### Cache Content Design + +Key: +- `uri` in type `string` + +Value: +- `*Bundle` + - `NextUpdate` // nextUpdate can be get from bundle.BaseCRL.NextUpdate + +Reference: [revocation/crl/bundle.go](https://github.com/notaryproject/notation-core-go/blob/main/revocation/crl/bundle.go) + +Check CRL Cache Validity +``` +// directly checks CRL validity +now := time.Now() +if !crl.NextUpdate.IsZero() && now.After(crl.NextUpdate) { + // perform refresh +} +``` + +### Load CRL Cache + +Load cache is triggerred after cert loaded from the either configurations. + +#### Download CRL + + +Download is implemented by CRL `fetcher`, which can be done in parallel via start tasks in seperate go routines. + +CRL download location (URL) can be obtained from the certificate's CRL Distribution Point (CDP) extension. +`notation-core-go` will download all CDP URLs because each CDP URL may belong to a different scope, and we cannot distinguish them. + +For each CDP location, Notary Project verification workflow will try to download the CRL for the default threshold of 5 seconds. Ratify is able to configure this threshold. If the CRL cannot be downloaded within the timeout threshold the revocation result will be "revocation unavailable". + +#### Save CRL to Cache + +``` +// Set stores the CRL bundle in the file system +// Check closest expired date and set to `CRLCacheProvider`. +// Save to temp file and avoid concurrency issue with atomic write operation +// `rename()` is atomic on UNIX-like platforms + + +// notation-go FScache + +type fileCacheContent struct { + // BaseCRL is the ASN.1 encoded base CRL + BaseCRL []byte `json:"baseCRL"` + + // DeltaCRL is the ASN.1 encoded delta CRL + DeltaCRL []byte `json:"deltaCRL,omitempty"` +} + +// This cache builds on top of the UNIX file system to leverage the file system's +// atomic operations. The `rename` and `remove` operations will unlink the old +// file but keep the inode and file descriptor for existing processes to access +// the file. The old inode will be dereferenced when all processes close the old +// file descriptor. Additionally, the operations are proven to be atomic on +// UNIX-like platforms, so there is no need to handle file locking. +// +// NOTE: For Windows, the `open`, `rename` and `remove` operations need file +// locking to ensure atomicity. The current implementation does not handle +// file locking, so the concurrent write from multiple processes may be failed. +// Please do not use this cache in a multi-process environment on Windows. +``` + +### Provide CRL Cache + +#### Get Cache from Provider + +``` +// Get retrieves the CRL bundle from the file system +// If the CRL is expired, return ErrCacheMiss + + +// Policy that uses custom verification level to relax the strict verification. +// It logs expiry and skips the revocation check. +"name": "use-expired-blobs", + "signatureVerification": { + "level" : "strict", + "override" : { + "expiry" : "log", + "revocation" : "skip" + } + }, + +``` +Reference: https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#signatureverification-details:~:text=Notary%20Project%20defines,scope%20(*). + +#### Refresh Cache + +Cache Data Structure: Store data along with expiration timestamps. + +Monitor Scheduler: Use a scheduler (e.g., time.Ticker) to check the cache at regular intervals. + +Concurrency: Use synchronization mechanisms like mutexes for thread-safe access to shared data. + +Expiration Handling: When setting cache, check closest expired date that set to `CRLCacheProvider`. Compare the current time with the cache item's expiration. If expired, trigger the fetch process to update the data. + +Error Handling and Retries: Implement error handling with retry logic in case of failed refresh operations. + +Use synchronization primitives like mutexes to ensure thread safety during cache updates. + +# Dev Work Items + +- Implement CRL Fetcher based on notation-go library (~ 1-2 weeks) +- Implement file-based CRL Cache Provider (~ 2-3 weeks) +- Support loading CRLs from cert provided URLs, engage PM discussion (~ 1-2 weeks) +- Verifier performance test and Cache r/w performance test (~ 2-3 weeks) +- New e2e tests for different scenarios (~ 1 week) + + +# More details + +**Brief Aside about CRL and CRL Cache** + +X.509 defines one method of certificate revocation. This method involves each CA periodically issuing a signed data structure called a certificate revocation list (CRL). + +A CRL is a time stamped list identifying revoked certificates which is signed by a CA or CRL issuer and made freely available in a public repository. Each revoked certificate is identified in a CRL by its certificate serial number. + +When a certificate-using system uses a certificate (e.g., for verifying a remote user's digital signature), that system not only checks the certificate signature and validity but also acquires a suitably-recent CRL and checks that the certificate serial number is not on that CRL. +The meaning of "suitably-recent" may vary with local policy, but it usually means the most recently-issued CRL. + +A new CRL is issued on a regular periodic basis (e.g., hourly, daily, or weekly). +An entry is added to the CRL as part of the next update following notification of revocation. An entry MUST NOT be removed from the CRL until it appears on one regularly scheduled CRL issued beyond the revoked certificate's validity period. + +Implementations of the Notary Project verification specification support only HTTP CRL URLs. + +**Revocation Checking with CRL** + +To check the revocation status of a certificate against CRL, the following steps must be performed: + +1. Verify the CRL signature. +1. Verify that the CRL is valid (not expired). + A CRL is considered expired if the current date is after the `NextUpdate` field in the CRL. +1. Look up the certificate’s serial number in the CRL. + 1. If the certificate’s serial number is listed in the CRL, look for `InvalidityDate`. + If CRL has an invalidity date and artifact signature is timestamped then compare the invalidity date with the timestamping date. + 1. If the invalidity date is before the timestamping date, the certificate is considered revoked. + 1. If the invalidity date is not present in CRL, the certificate is considered revoked. + 1. If the CRL is expired and the certificate is listed in the CRL for any reason other than `certificate hold`, the certificate is considered revoked. + 1. If the certificate is not listed in the CRL or the revocation reason is `certificate hold`, a new CRL is retrieved if the current time is past the time in the `NextPublish` field in the current CRL. + The new CRL is then checked to determine if the certificate is revoked. + If the original reason was `certificate hold`, the CRL is checked to determine if the certificate is unrevoked by looking for the `RemoveFromCRL` revocation code. + + +**rfc3280** + +REF: [Internet X.509 Public Key Infrastructure](https://www.rfc-editor.org/rfc/rfc3280#section-1) + +Q: Does the Notary Project trust policy support overriding of revocation endpoints to support signature verification in disconnected environments? + +A: TODO: Update after verification extensibility spec is ready. Not natively supported but a user can configure revocationValidations to skip and then use extended validations to check for revocation. \ No newline at end of file diff --git a/docs/img/CRL/CRL-workflow.png b/docs/img/CRL/CRL-workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..b33faef398f97b79049cb0f17a76390ec9265b4f GIT binary patch literal 79472 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

h_YmvBs%}Xjd*L$;IVMw*w$f0j-NX za0uEcY&`YnW&UM;mUc~X^QgSv(3ZULoqQ%hBXbO7CiWhDgYNz{ghPn7jtNDEBCLTqygru7f88@iJw&#WobDd`^l%i->W}caz9gzc?f>oC7_xEWI4ILoV-g* zOO33p!F0Oxm08(0#0>7&pk6Wof!%o;pd{5^)4Br)cePw4o$qB9qz)o-0S)BqQdBxi z&LgIrb$ql9KzD)Qh}|D|E&&U_6j%au?Ay1?q-A9n*?~()*~)adQ>sDuJr%{p$o?1+ zSt7>S(8ar|n(|_krUr{={3y5PsoiOSZv^fJ9Oj?%{_P@9=%y?p_2LGllupX=EY?ed%uYu zr-;IaI;uT$_0U>qL$s3lY)VrpJ#uCaHu1t zd-xh!skylKH+_zPLN~nO(ifcH1&=+?{pTXv+f=8u+9=_X+UUFu+zXx7eyFOZb{Q

J&qk?I_5DRsx=8Yct|8_vx`TLX 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 literal 0 HcmV?d00001 From cc80ff81113bd860a64d3ef3c0bb9d820010aab7 Mon Sep 17 00:00:00 2001 From: Asaf Algawi <144888248+asafalgawi@users.noreply.github.com> Date: Tue, 22 Oct 2024 03:36:37 +0300 Subject: [PATCH 59/65] docs: Create proposal for verifying 'last-n' artifacts only. (#1797) Signed-off-by: Susan Shi --- docs/proposals/Verify-Latest-N-Artifacts.md | 103 ++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/proposals/Verify-Latest-N-Artifacts.md diff --git a/docs/proposals/Verify-Latest-N-Artifacts.md b/docs/proposals/Verify-Latest-N-Artifacts.md new file mode 100644 index 000000000..efe7240eb --- /dev/null +++ b/docs/proposals/Verify-Latest-N-Artifacts.md @@ -0,0 +1,103 @@ +# Verify only the latest N artifacts + +## Problem/Motivation + +When configuring a verifier in Ratify, we set the artifact type the verifier should work on. In such case, Ratify will verify all referrers of a given subject that have a matching artifact type using the verifier. +In some cases, this could lead to a wrong behavior. For instance, Vulnerability Artifacts are outdated once a new artifact is written to the repository, as such there is no use for verifying both the new one and the old one. + +The issue with verifying all the matching artifacts could also lead to performance issues, each "verification" process hides within a request to pull the artifcat manifest, and the blobs containing the actual data. +In previous studies made by the ratify team, it was observed that opverloading the registry with requests could lead to errors and throtteling. (see: https://ratify.dev/docs/reference/performance) + +Given the performance study listed above, in order to provide the best experience for Ratify's users ratify would reduce the load it generates on an the registry, thus reducing the chance for throtteling. + +# Proposed Solution + +Ratify uses the Referrer API (see: https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers) in order to obtain the list of attached artifacts of a given subject artifact. The response body for this request is a generated OCI image index, that looks like this: + +```json +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 1234, + "digest": "sha256:a1a1a1...", + "artifactType": "application/vnd.example.sbom.v1", + "annotations": { + "org.opencontainers.image.created": "2022-01-01T14:42:55Z", + "org.example.sbom.format": "json" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 1234, + "digest": "sha256:a2a2a2...", + "artifactType": "application/vnd.example.signature.v1", + "annotations": { + "org.opencontainers.image.created": "2022-01-01T07:21:33Z", + "org.example.signature.fingerprint": "abcd" + } + }, + { + "mediaType": "application/vnd.oci.image.index.v1+json", + "size": 1234, + "digest": "sha256:a3a3a3...", + "annotations": { + "org.opencontainers.image.created": "2023-01-01T07:21:33Z", + } + } + ] +} +``` + +The image index response is requried by API to include the image annotations, which gives Ratify to oprrotunity to perform some basic filtration before invoking the verifier on the listed artifacts. The exact mechanism for requiring the filtration should be specific to each verifier as the behavior and logic differs based on is actually being attested, as such, it will be part of the verifier configuration. + +## Latest N artifacts only verification + +Assuming artifacts are generated by ORAS, they all have a `org.opencontainers.image.created` annotation, that markes the creation date of the artifact. Based on it, Ratify could filter out stale artifacts and only evaluate the latest image. To achieve this, Ratify would have to read all the referrers, ordering them based on the artifact age, and only pass the latest one to the corresponding verifier. + +This kind of filtering strategy is best used on artifacts that are rapidly changing, for example Vulnerability Assessment artifacts that are immedietly oudated once a new artifact is pushed to the registry. + +* An artifact without the creation annotation is considered to be the oldest. +* The annotation value should be a date time string in RFC 3339 format, any other value will result is invalid, and should be treated as the oldest artifact. + +## User experiences + +This section describes the experience that users interact with Ratify using the proposed solution. In summary, the propsed solution suggest we should allow for filtering of artifact based on annotations, and as such the following section describes how the customer would configure the filtration. + +Seeing that filteration is unique to each verifier, it should be configured in the verifier itself, as such, in order to maintain backwards compatability, it is important to note that if no filteration is configured the default behavior would be to evaluate all artifacts. + +### Defining artifact age based filtering + +To support artifact age based filtering, we would add an additional field to the verifier configuration: + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier # NamespacedVerifier has the same spec. +metadata: + name: test-verifier +spec: + name: # REQUIRED: [string], the unique type of the verifier (notation, cosign) + artifactType: # REQUIRED: [string], comma seperated list, artifact type this verifier handles + verifyLastNArtifacts: # Optional: [int], denote the number of attached artfacts that should be verified. only the Last n will be verified. if not defined, all artifacts will be verified. + address: # OPTIONAL: [string], Plugin path, defaults to value of env "RATIFY_CONFIG" or "~/.ratify/plugins" + version: # OPTIONAL: [string], Version of the external plugin, defaults to 1.0.0. On ratify initialization, the specified version will be validated against the supported plugin version. + source: + artifact: # OPTIONAL: [string], Source location to download the plugin binary, learn more at docs/reference/dynamic-plugins.md e.g. wabbitnetworks.azurecr.io/test sample-verifier-plugin:v1 + parameters: # OPTIONAL: [object] Parameters specific to this verifier +``` + +### Implementation Considerations +To implmenet "Last N" verification only, Ratify has to be aware of all the attached artifacts of a givan kind before handing them to the verifier that wishes to attest only the latest artifact. In order to implement such behavior some modification has to be made to the executor of Ratify and the verifier implementation. + +Below are two proposals which are currently being considered for implemetation. +| Approach | Pros | Cons| Notes | +| -------- | ---- | ----| ----- | +| 1. Obtain and store all the referrer list
2. Sort it in descending order.
3. Use the CanVerify method of the referrer to make sure a verifier
that only wants the latest artifact is invoked once. | Naive implementation.

Does not make a huge change in the executor, other than fetching the list before hand.

Transparent change for verifiers that do not wish to use verify the latest image only. | The referrer list can be of any arbitrary size, therefore fetching the entire list may cause Ratify to hit a hard memory limit and crash.
To implement the feature with this kind of behavior, Ratify would have to limit the number of attached artifacts it supports to some constant number which will be determined during the implementation.

Additional latency for sorting the artifacts. | A test index list, with ~1000 artifacts within and two annotations (created timestamp, and another text field) weighs around ~400K, default ratify installation has 512MB of ram, so we're well within the limits of 'normal' use. +| 1. Split verifiers into two groups, those which require only latest artifact, and those which operate on all artifacts.
2. For verifiers that work on all artifacts, no change will be made.
3. For verifiers that require only the last N artifacts, the executor will manage a map between the verifier and an artifact descriptor list that is the "current candidates" for being the latest.
4. As we iterate all the referrer, the cadndiate list is constantly being updated, if a new artifact is discovered.
Once the executor had finished iterating over the referrer list, it would execute all the verifiers that required the latest N artifact against the "current candidate" list for each verifier, which are promised to be latest artifacts. | Does not modify Ratify's current scalability.| Requires the executor to be aware of verifier type, possibly by an interface change on the verifier API

Requires changes in multiple places in the executor, performing the verifier loop another time for the second list of verifiers that only require latest artifact. | The benefit of not pulling all the referrers, from the standpoint of keeping the same 'memory footprint' is not clear. + +# References + +* [Ratify Performance at Scale Study](https://ratify.dev/docs/reference/performance) +* [Referrer API in Distribution Spec](https://github.com/opencontainers/distribution-spec/blob/main/spec.md#listing-referrers) \ No newline at end of file From f578982bb0ca6295566abdd54caf8ffa4d8402be Mon Sep 17 00:00:00 2001 From: Josh Duffney Date: Mon, 21 Oct 2024 19:40:55 -0500 Subject: [PATCH 60/65] docs: nVersionCount support for KMP design doc (#1831) Signed-off-by: Joshua Duffney --- docs/design/kmp-nversions.md | 106 +++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 docs/design/kmp-nversions.md diff --git a/docs/design/kmp-nversions.md b/docs/design/kmp-nversions.md new file mode 100644 index 000000000..afc2421db --- /dev/null +++ b/docs/design/kmp-nversions.md @@ -0,0 +1,106 @@ +# nVersionCount support for Key Management Provider + +Author: Josh Duffney (@duffney) + +Tracked issues in scope: + +- https://github.com/ratify-project/ratify/issues/1751 + +Proposal ref: + +- https://github.com/ratify-project/ratify/blob/dev/docs/proposals/Automated-Certificate-and-Key-Updates.md + +## Problem Statement + +In version 1.3.0 and earlier, Ratify does not support the nVersionCount parameter for Key Management Provider (KMP) resources. This means that when a certificate or key is rotated, Ratify updates the cache with the new version and removes the previous one, which may not suit all use cases. + +For instance, if a user needs to retain the last three versions of a certificate or key in the cache, Ratify cannot meet this requirement without manually adjusting the KMP resource for each new version. + +By supporting nVersionCount, Ratify would allow users to specify how many versions of a certificate or key should be kept in the cache, eliminating the need for manual updates to the KMP resource. + +## Proposed Solution + +To address this challenge, this proposal suggests adding support for the `versionHistory` parameter to the KMP resource in Ratify. This parameter will allow users to specify the number of versions of a certificate or key that should be retained in the cache. + +When a new version of a certificate or key is created, Ratify will check the `versionHistory` parameter to determine how many versions should be retained in the cache. If the number of versions exceeds the specified count, Ratify will remove the oldest version from the cache. + +If a version is disabled, Ratify will remove it from the cache. This ensures that disabled versions are not retained in the cache, reducing the risk of using compromised keys or certificates being passed to the verifiers. + +Example: AKV KMP resource with `versionHistory` parameter + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + refreshInterval: 1m + parameters: + vaultURI: https://yourkeyvault.vault.azure.net/ + certificates: + - name: yourCertName + versionHistory: 2 + tenantID: + clientID: +``` + +Example: AKV KMP resource status with multiple versions retained in the cache + +```yaml +Status: + Issuccess: true + Lastfetchedtime: 2024-10-02T14:58:54Z + Properties: + Certificates: + Last Refreshed: 2024-10-02T14:58:54Z + Name: yourCertName + Version: a1b2c3d4e5f67890abcdef1234567890 + Enabled: true + Last Refreshed: 2024-10-02T14:58:54Z + Name: yourCertName + Version: 0ff373a9259c4578a247cfd7861a8805 + Enabled: false +``` + +## Implementation Details + +- Modify the KMP data structure to include the status of the version. + ```go + type KMPMapKey struct { + Name string + Version string + Enabled string // true or false + Created time.Time // Time the version was created used for determining the oldest version + } + ``` +- Add the `versionHistory` parameter to the KMP resource in Ratify. + - ensure the value cannot be less than 0 or a negative number + - default to 2 if not specified by passing an empty value + - maximum value should be (TBD) + - specify the value at the object level within the parameters of the KMP resource. +- Changes to `azurekeyvault` provider: + - support for the `versionHistory` parameter. + - allowing retrieval of multiple versions of certificates or keys. + - remove the oldest version from the cache when the number of versions exceeds the `versionHistory` parameter. + - update disabled certs status in the cache & remove the certData from the cache. +- Log when the status of a version changes. +- Log when a conflict between the `versionHistory` and the number of specified certificate versions occurs. + +## Dev Work Items + +## Open Questions + +- If a version is disabled, should it be removed from the cache or retained based on the nVersionCount and marked as inactive\disabled? + - [x] Keep the disabled version in the cache and mark it as disabled. +- If a version is disabled, does that count towards the nVersionCount? For example, if nVersionCount is set to 3 and one of the versions is disabled, should Ratify retain the last three active versions or the last three versions, regardless of their status? + - [x] Yes, disabled versions should count towards the nVersionCount. The reason for this is that disabled versions may be re-enabled in the future, and it is important to retain them in the cache. +- Should the existing KMP data structure be changed to group versions by key or certificate name? + - [x] No, a flat list of versions is sufficient. At this time, there is no need to group versions by key or certificate name because the verifiers do not need to know the history of the versions. +- Should the KMP status return a flat list of versions? + - [x] Yes, the status should return a flat list of versions. + +## Future Considerations + +- What should the maximum value for nVersionCount be? + - [ ] TBD From 44abd8bb844ae104182559fd25415e23563b01c2 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Tue, 22 Oct 2024 09:01:13 +0800 Subject: [PATCH 61/65] ci: retry trivy db update upon failure (#1881) Signed-off-by: Binbin Li --- Makefile | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 376a2d170..3d06df53b 100644 --- a/Makefile +++ b/Makefile @@ -160,7 +160,7 @@ test-e2e: generate-rotation-certs EXPIRING_CERT_DIR=.staging/rotation/expiring-certs CERT_DIR=.staging/rotation GATEKEEPER_VERSION=${GATEKEEPER_VERSION} bats -t ${BATS_PLUGIN_TESTS_FILE} .PHONY: test-e2e-cli -test-e2e-cli: e2e-dependencies e2e-create-local-registry e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-setup e2e-licensechecker-setup e2e-sbom-setup e2e-schemavalidator-setup e2e-vulnerabilityreport-setup +test-e2e-cli: e2e-dependencies e2e-create-local-registry e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-setup e2e-licensechecker-setup e2e-sbom-setup e2e-trivy-setup e2e-schemavalidator-setup e2e-vulnerabilityreport-setup rm ${GOCOVERDIR} -rf mkdir ${GOCOVERDIR} -p RATIFY_DIR=${INSTALL_DIR} TEST_REGISTRY=${TEST_REGISTRY} ${GITHUB_WORKSPACE}/bin/bats -t ${BATS_CLI_TESTS_FILE} @@ -459,14 +459,37 @@ e2e-sbom-setup: NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/sbom@`${GITHUB_WORKSPACE}/bin/oras discover --distribution-spec v1.1-referrers-api -o json --artifact-type application/spdx+json ${TEST_REGISTRY}/sbom:v0 | jq -r ".manifests[0].digest"` NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/all@`${GITHUB_WORKSPACE}/bin/oras discover --distribution-spec v1.1-referrers-api -o json --artifact-type application/spdx+json ${TEST_REGISTRY}/all:v0 | jq -r ".manifests[0].digest"` +e2e-trivy-setup: + rm -rf .staging/trivy + mkdir -p .staging/trivy + + # Install Trivy + curl -L https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz --output .staging/trivy/trivy.tar.gz + tar -zxf .staging/trivy/trivy.tar.gz -C .staging/trivy + + # Download vulnerability database in retry mode + max_retries=3; \ + attempt=1; \ + wait_time=2; \ + while [ $$attempt -le $$max_retries ]; do \ + echo "Attempt $$attempt of $$max_retries..."; \ + if .staging/trivy/trivy image --download-db-only; then \ + break; \ + fi; \ + if [ $$attempt -eq $$max_retries ]; then \ + echo "Failed after $$max_retries attempts."; \ + exit 1; \ + fi; \ + echo "Failed. Retrying in $$wait_time seconds..."; \ + sleep $$wait_time; \ + wait_time=$$(( wait_time * 2 )); \ + attempt=$$(( attempt + 1 )); \ + done + e2e-schemavalidator-setup: rm -rf .staging/schemavalidator mkdir -p .staging/schemavalidator - # Install Trivy - curl -L https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz --output .staging/schemavalidator/trivy.tar.gz - tar -zxf .staging/schemavalidator/trivy.tar.gz -C .staging/schemavalidator - # Build/Push Images printf 'FROM ${ALPINE_IMAGE}\nCMD ["echo", "schemavalidator image"]' > .staging/schemavalidator/Dockerfile docker buildx create --use @@ -475,7 +498,7 @@ e2e-schemavalidator-setup: rm .staging/schemavalidator/schemavalidator.tar # Create/Attach Scan Results - .staging/schemavalidator/trivy image --format sarif --output .staging/schemavalidator/trivy-scan.sarif ${TEST_REGISTRY}/schemavalidator:v0 + .staging/trivy/trivy image --skip-db-update --format sarif --output .staging/schemavalidator/trivy-scan.sarif ${TEST_REGISTRY}/schemavalidator:v0 ${GITHUB_WORKSPACE}/bin/oras attach \ --artifact-type application/vnd.aquasecurity.trivy.report.sarif.v1 \ --distribution-spec v1.1-referrers-api \ @@ -491,10 +514,6 @@ e2e-vulnerabilityreport-setup: rm -rf .staging/vulnerabilityreport mkdir -p .staging/vulnerabilityreport - # Install Trivy - curl -L https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz --output .staging/vulnerabilityreport/trivy.tar.gz - tar -zxf .staging/vulnerabilityreport/trivy.tar.gz -C .staging/vulnerabilityreport - # Build/Push Image printf 'FROM ${ALPINE_IMAGE_VULNERABLE}\nCMD ["echo", "vulnerabilityreport image"]' > .staging/vulnerabilityreport/Dockerfile docker buildx create --use @@ -503,7 +522,7 @@ e2e-vulnerabilityreport-setup: rm .staging/vulnerabilityreport/vulnerabilityreport.tar # Create/Attach Scan Result - .staging/vulnerabilityreport/trivy image --format sarif --output .staging/vulnerabilityreport/trivy-sarif.json ${TEST_REGISTRY}/vulnerabilityreport:v0 + .staging/trivy/trivy image --skip-db-update --format sarif --output .staging/vulnerabilityreport/trivy-sarif.json ${TEST_REGISTRY}/vulnerabilityreport:v0 ${GITHUB_WORKSPACE}/bin/oras attach \ --artifact-type application/sarif+json \ --distribution-spec v1.1-referrers-api \ @@ -524,7 +543,7 @@ e2e-inlinecert-setup: .staging/notation/notation cert generate-test "alternate-cert" NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} --key "alternate-cert" ${TEST_REGISTRY}/notation@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/notation:signed-alternate --descriptor | jq .digest | xargs` -e2e-azure-setup: e2e-create-all-image e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-akv-setup e2e-licensechecker-setup e2e-sbom-setup e2e-schemavalidator-setup +e2e-azure-setup: e2e-create-all-image e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-akv-setup e2e-licensechecker-setup e2e-sbom-setup e2e-trivy-setup e2e-schemavalidator-setup e2e-deploy-gatekeeper: e2e-helm-install ./.staging/helm/linux-amd64/helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts @@ -560,7 +579,7 @@ e2e-deploy-base-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosi rm mount_config.json -e2e-deploy-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-setup e2e-cosign-setup e2e-licensechecker-setup e2e-sbom-setup e2e-schemavalidator-setup e2e-vulnerabilityreport-setup e2e-inlinecert-setup e2e-build-crd-image load-build-crd-image e2e-build-local-ratify-image load-local-ratify-image e2e-helm-deploy-ratify +e2e-deploy-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-setup e2e-cosign-setup e2e-licensechecker-setup e2e-sbom-setup e2e-trivy-setup e2e-schemavalidator-setup e2e-vulnerabilityreport-setup e2e-inlinecert-setup e2e-build-crd-image load-build-crd-image e2e-build-local-ratify-image load-local-ratify-image e2e-helm-deploy-ratify e2e-build-local-ratify-base-image: docker build --progress=plain --no-cache \ From 75ed5bad201c923b67117810801a9e07bf988958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:23:04 +0800 Subject: [PATCH 62/65] chore: Bump anchore/sbom-action from 0.17.4 to 0.17.5 (#1882) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d95884f5..b550b2372 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - name: Install Syft - uses: anchore/sbom-action/download-syft@8d0a6505bf28ced3e85154d13dc6af83299e13f1 # v0.17.4 + uses: anchore/sbom-action/download-syft@1ca97d9028b51809cf6d3c934c3e160716e1b605 # v0.17.5 - name: Set up Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 From df7fdffd5281e2ec6c9d2ad82841ceea53473d5b Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 23 Oct 2024 09:05:59 +0800 Subject: [PATCH 63/65] ci: fix tagging in publish-ghcr workflow (#1884) Signed-off-by: Binbin Li --- .github/workflows/publish-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 6a1499ecb..21bcf2815 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -64,7 +64,7 @@ jobs: --attest type=sbom \ --attest type=provenance,mode=max \ --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg LDFLAGS="-X github.com/ratify-project/ratify/internal/version.Version=$(TAG)" \ + --build-arg LDFLAGS="-X github.com/ratify-project/ratify/internal/version.Version=$TAG" \ --label org.opencontainers.image.revision=${{ github.sha }} \ -t ${{ steps.prepare.outputs.baseref }} \ --push . @@ -79,7 +79,7 @@ jobs: --build-arg build_licensechecker=true \ --build-arg build_schemavalidator=true \ --build-arg build_vulnerabilityreport=true \ - --build-arg LDFLAGS="-X github.com/ratify-project/ratify/internal/version.Version=$(TAG)" \ + --build-arg LDFLAGS="-X github.com/ratify-project/ratify/internal/version.Version=$TAG" \ --label org.opencontainers.image.revision=${{ github.sha }} \ -t ${{ steps.prepare.outputs.ref }} \ --push . From c4adde7881d5b3d6c6e2ea1d3f8550a3ec41639c Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 23 Oct 2024 10:16:23 +0800 Subject: [PATCH 64/65] ci: retry trivy download-db on failure (#1883) Signed-off-by: Binbin Li --- .github/workflows/scan-vulns.yaml | 76 ++++++++++++------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index a4182aa7e..23208d9b1 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 15 env: - TRIVY_VERSION: v0.49.1 + TRIVY_VERSION: 0.49.1 steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 @@ -48,58 +48,38 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 - - name: Manual Trivy Setup - uses: aquasecurity/setup-trivy@eadb05c36f891dc855bba00f67174a1e61528cd4 # v0.2.1 + - name: Download trivy + run: | + pushd $(mktemp -d) + wget https://github.com/aquasecurity/trivy/releases/download/v${{ env.TRIVY_VERSION }}/trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz + tar zxvf trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz + echo "$(pwd)" >> $GITHUB_PATH + + - name: Download vulnerability database + uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0 with: - cache: true - version: ${{ env.TRIVY_VERSION }} + max_attempts: 3 + retry_on: error + timeout_seconds: 30 + retry_wait_seconds: 5 + command: | + trivy image --download-db-only - name: Run trivy on git repository - uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 - with: - scan-type: 'fs' - scan-ref: '.' - ignore-unfixed: true - scanners: 'vuln' - version: ${{ env.TRIVY_VERSION }} + run: | + trivy fs --skip-db-update --format table --ignore-unfixed --scanners vuln . - name: Build docker images run: | make e2e-build-local-ratify-image make e2e-build-crd-image - - - name: Run Trivy vulnerability scanner on localbuild:test - uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 - with: - scan-type: 'image' - image-ref: 'localbuild:test' - ignore-unfixed: true - version: ${{ env.TRIVY_VERSION }} - - - name: Run Trivy vulnerability scanner on localbuildcrd:test - uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 - with: - scan-type: 'image' - image-ref: 'localbuildcrd:test' - ignore-unfixed: true - version: ${{ env.TRIVY_VERSION }} - - - name: Run Trivy vulnerability scanner on localbuild:test and exit on HIGH severity - uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 - with: - scan-type: 'image' - image-ref: 'localbuild:test' - ignore-unfixed: true - severity: 'HIGH,CRITICAL' - exit-code: '1' - version: ${{ env.TRIVY_VERSION }} - - - name: Run Trivy vulnerability scanner on localbuildcrd:test and exit on HIGH severity - uses: aquasecurity/trivy-action@915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2 # 0.28.0 - with: - scan-type: 'image' - image-ref: 'localbuildcrd:test' - ignore-unfixed: true - severity: 'HIGH,CRITICAL' - exit-code: '1' - version: ${{ env.TRIVY_VERSION }} \ No newline at end of file + - name: Run trivy on images for all severity + run: | + for img in "localbuild:test" "localbuildcrd:test"; do + trivy image --skip-db-update --ignore-unfixed --vuln-type="os,library" "${img}" + done + - name: Run trivy on images and exit on HIGH/CRITICAL severity + run: | + for img in "localbuild:test" "localbuildcrd:test"; do + trivy image --skip-db-update --ignore-unfixed --exit-code 1 --severity HIGH,CRITICAL --vuln-type="os,library" "${img}" + done \ No newline at end of file From a70ebefbfc5eec0df97234b5efd3e96930dcdc87 Mon Sep 17 00:00:00 2001 From: Shahram Kalantari Date: Wed, 23 Oct 2024 13:45:57 +1000 Subject: [PATCH 65/65] chore: remove commented code Signed-off-by: Shahram Kalantari --- pkg/common/oras/authprovider/azure/azureidentity.go | 1 - pkg/common/oras/authprovider/azure/azureworkloadidentity.go | 1 - 2 files changed, 2 deletions(-) diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 97a687ee0..d0369c4dc 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -172,7 +172,6 @@ func (d *MIAuthProvider) Provide(ctx context.Context, artifact string) (provider var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { - // return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry by azure managed identity token", re.HideStackTrace) return provider.AuthConfig{}, re.ErrorCodeAuthDenied.WithError(err).WithDetail("failed to create authentication client for container registry by azure managed identity token") } diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 1ef2c490d..31f45127d 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -174,7 +174,6 @@ func (d *WIAuthProvider) Provide(ctx context.Context, artifact string) (provider var options *azcontainerregistry.AuthenticationClientOptions client, err := d.authClientFactory.CreateAuthClient(serverURL, options) if err != nil { - // return provider.AuthConfig{}, re.ErrorCodeAuthDenied.NewError(re.AuthProvider, "", re.AzureWorkloadIdentityLink, err, "failed to create authentication client for container registry", re.HideStackTrace) return provider.AuthConfig{}, re.ErrorCodeAuthDenied.WithError(err).WithDetail("failed to create authentication client for container registry by azure managed identity token") }