diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4a565b617..6734c86e8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -13,9 +13,8 @@ # 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.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster -ARG VARIANT="1.21-bullseye" -FROM mcr.microsoft.com/vscode/devcontainers/go:${VARIANT} +# [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 # [Choice] Node.js version: none, lts/*, 18, 16, 14 ARG NODE_VERSION="none" @@ -31,7 +30,7 @@ RUN curl -Lo bats.tar.gz https://github.com/bats-core/bats-core/archive/v${BATS_ && bash ./bats-core-${BATS_VERSION}/install.sh /usr/local \ && rm -rf bats.tar.gz ./bats-core-${BATS_VERSION} -ARG NOTATION_VERSION="1.0.0-rc.1" +ARG NOTATION_VERSION="1.2.0" RUN curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v${NOTATION_VERSION}/notation_${NOTATION_VERSION}_linux_amd64.tar.gz \ && tar -zxf notation.tar.gz \ && mv ./notation /usr/local/bin/notation \ @@ -54,8 +53,8 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ # [Optional] Uncomment the next lines to use go get to install anything else you need USER vscode -RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 \ - && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 \ +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1 \ + && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2.0 \ && chmod a+w -R /go/pkg # [Optional] Uncomment this line to install global node packages. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8c6506b9b..54a00f2a8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,10 +5,10 @@ "build": { "dockerfile": "Dockerfile", "args": { - // Update the VARIANT arg to pick a version of Go: 1.21, 1.20, 1.19, 1.18 + // Update the VARIANT arg to pick a version of Go: 1.22, 1.21, 1.20, 1.19, 1.18 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local arm64/Apple Silicon. - "VARIANT": "1.21-bullseye", + "VARIANT": "1.22-bullseye", // Options "NODE_VERSION": "none", // Ratify-specific devcontainer options diff --git a/.github/codecov.yml b/.github/codecov.yml index 193437e4d..aaac20a90 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,2 +1,8 @@ ignore: - - "./api" # ignore folders and all its contents \ No newline at end of file + - "./api" # ignore folders and all its contents + - "./experimental/proto/v1" +coverage: + status: + patch: + default: + target: 80% diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d3ac40afa..150fffaee 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,6 +17,32 @@ updates: ignore: - dependency-name: "*" update-types: - - "version-update:semver-major" - - "version-update:semver-minor" - \ No newline at end of file + - "version-update:semver-major" + - "version-update:semver-minor" + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "chore" + + - package-ecosystem: "docker" + directory: "/httpserver" + schedule: + interval: "weekly" + ignore: + - dependency-name: "golang" + versions: '> 1.22' + commit-message: + prefix: "chore" + + - package-ecosystem: "docker" + directory: "/.devcontainer" + schedule: + interval: "weekly" + ignore: + - dependency-name: "vscode/devcontainers/go" + versions: '> 1.22' + commit-message: + prefix: "chore" diff --git a/.github/licenserc.yml b/.github/licenserc.yml index 0a208e9f0..c7ae27c4a 100644 --- a/.github/licenserc.yml +++ b/.github/licenserc.yml @@ -29,7 +29,7 @@ header: limitations under the License. paths-ignore: - - "**/*.{md,svg,yaml,crt,json,pub,yml,pb.go,proto}" + - "**/*.{md,svg,yaml,crt,cer,json,pub,yml,pb.go,proto}" - "CODEOWNERS" - "PROJECT" - "NOTICE" @@ -49,7 +49,7 @@ dependency: - go.mod licenses: - name: github.com/spdx/tools-golang - version: v0.5.3 + version: v0.5.5 license: Apache-2.0 - name: github.com/alibabacloud-go/cr-20160607 # TODO: remove this when library is upgraded to v2.0.0 version: v1.0.1 diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 245e7de74..804ea6a88 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -5,7 +5,7 @@ on: types: [labeled] pull_request: branches: - - staging + - dev workflow_dispatch: permissions: read-all @@ -13,17 +13,20 @@ permissions: read-all jobs: call_test_cli: uses: ./.github/workflows/e2e-cli.yml - + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + call_test_e2e_basic: name: "run e2e on basic matrix" + if: ${{ ! (contains(github.event.pull_request.labels.*.name, 'safe to test') || github.event_name == 'workflow_dispatch') }} permissions: contents: read strategy: fail-fast: false matrix: - KUBERNETES_VERSION: ["1.27.7"] - GATEKEEPER_VERSION: ["3.15.0"] - uses: ./.github/workflows/e2e-k8s.yml + KUBERNETES_VERSION: ["1.29.2"] + GATEKEEPER_VERSION: ["3.17.0"] + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} @@ -34,12 +37,12 @@ jobs: strategy: fail-fast: false matrix: - KUBERNETES_VERSION: ["1.26.10", "1.27.7"] - GATEKEEPER_VERSION: ["3.13.0", "3.14.0", "3.15.0"] - uses: ./.github/workflows/e2e-k8s.yml + KUBERNETES_VERSION: ["1.28.12", "1.29.2"] + GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} - gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} + gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} build_test_aks_e2e_conditional: name: "Build and run e2e Test on AKS with conditions" @@ -50,37 +53,41 @@ jobs: strategy: fail-fast: false matrix: - KUBERNETES_VERSION: ["1.26.10", "1.27.7"] - GATEKEEPER_VERSION: ["3.13.0", "3.14.0", "3.15.0"] + KUBERNETES_VERSION: ["1.28.12", "1.29.2"] + GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] uses: ./.github/workflows/e2e-aks.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} secrets: inherit - + aks-test-cleanup: - env: - AZURE_SUBSCRIPTION_ID: daae1e1a-63dc-454f-825d-b39289070f79 - AZURE_CLIENT_ID: 814e6e97-120c-4534-b8a9-f1645bc99500 - AZURE_TENANT_ID: 72f988bf-86f1-41af-91ab-2d7cd011db47 - needs: ['build_test_aks_e2e_conditional'] + needs: ["build_test_aks_e2e_conditional"] runs-on: ubuntu-latest permissions: id-token: write contents: read + environment: azure-test steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Set up Go 1.21 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up Go 1.22 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.21' + go-version: "1.22" - name: Az CLI login - uses: azure/login@8c334a195cbb38e46038007b304988d888bf676a # v2.0.0 + uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 with: - creds: '{"clientId":"${{ env.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ env.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ env.AZURE_TENANT_ID }}"}' + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: clean up run: | - make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ env.AZURE_SUBSCRIPTION_ID }} \ No newline at end of file + make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index c38811a41..46042f7f1 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -4,14 +4,22 @@ on: types: - closed +permissions: + contents: read + jobs: cleanup: runs-on: ubuntu-latest - steps: + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Cleanup run: | gh extension install actions/gh-actions-cache - + echo "Fetching list of cache key" cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) @@ -26,4 +34,4 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} - BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge \ No newline at end of file + BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge diff --git a/.github/workflows/clean-dev-package.yml b/.github/workflows/clean-dev-package.yml new file mode 100644 index 000000000..0a53bd8d0 --- /dev/null +++ b/.github/workflows/clean-dev-package.yml @@ -0,0 +1,33 @@ +name: clean-dev-package + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + cleanup-packages: + runs-on: ubuntu-latest + permissions: + packages: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - name: Clean up ratify-crds-dev + uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 + with: + package-name: "ratify-crds-dev" + package-type: "container" + min-versions-to-keep: 7 + delete-only-pre-release-versions: "true" + - name: Clean up ratify-dev + uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 + with: + package-name: "ratify-dev" + package-type: "container" + min-versions-to-keep: 7 + delete-only-pre-release-versions: "true" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c792a05b9..e8b47b0fd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,17 +1,18 @@ - name: "CodeQL Scan" on: push: - branches: + branches: - main + - dev - 1.0.0* pull_request: - branches: + branches: - main + - dev - 1.0.0* schedule: - - cron: '30 1 * * 0' + - cron: "30 1 * * 0" workflow_dispatch: permissions: read-all @@ -24,14 +25,19 @@ jobs: security-events: write steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag=3.0.2 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 - name: setup go environment - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: "1.21" + go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@cdcdbb579706841c47f7063dda365e292e5cad7a # tag=v2.13.4 + uses: github/codeql-action/init@8214744c546c1e5c8f03dde8fab3a7353211988d # tag=v3.26.7 with: languages: go - name: Run tidy @@ -39,4 +45,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@cdcdbb579706841c47f7063dda365e292e5cad7a # tag=v2.13.4 + uses: github/codeql-action/analyze@8214744c546c1e5c8f03dde8fab3a7353211988d # tag=v3.26.7 diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index 6f3fd1756..f6a2c1f9d 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -1,40 +1,56 @@ name: e2e-aks +permissions: + contents: read + on: workflow_call: inputs: k8s_version: - description: 'Kubernetes version' + description: "Kubernetes version" required: true - default: '1.27.7' + default: "1.29.2" type: string gatekeeper_version: - description: 'Gatekeeper version' + description: "Gatekeeper version" required: true - default: '3.15.0' + default: "3.17.0" type: string jobs: build_test_aks_e2e: name: "Build and run e2e Test on AKS" - env: - AZURE_CLIENT_ID: 814e6e97-120c-4534-b8a9-f1645bc99500 - AZURE_TENANT_ID: 72f988bf-86f1-41af-91ab-2d7cd011db47 - AZURE_SUBSCRIPTION_ID: daae1e1a-63dc-454f-825d-b39289070f79 runs-on: ubuntu-latest timeout-minutes: 30 + environment: azure-test + permissions: + id-token: write + contents: read steps: - - name: Check out code into the Go module directory - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Set up Go 1.21 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: - go-version: '1.21' + egress-policy: audit + - name: Check out code into the Go module directory + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up Go 1.22 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: "1.22" - name: Az CLI login - uses: azure/login@8c334a195cbb38e46038007b304988d888bf676a # v2.0.0 + uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 with: - creds: '{"clientId":"${{ env.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ env.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ env.AZURE_TENANT_ID }}"}' + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Cache AAD tokens + run: | + az version + # Key Vault: + az account get-access-token --scope https://vault.azure.net/.default --output none + # Container Registry: + az account get-access-token --scope https://containerregistry.azure.net/.default --output none - name: Dependencies e2e run: | @@ -45,12 +61,12 @@ jobs: - name: Run e2e on Azure run: | - make e2e-aks KUBERNETES_VERSION=${{ inputs.k8s_version }} GATEKEEPER_VERSION=${{ inputs.gatekeeper_version }} TENANT_ID=${{ env.AZURE_TENANT_ID }} + 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ always() }} with: name: e2e-logs-aks-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} path: | - logs-*.json \ No newline at end of file + logs-*.json diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml index 30ecfc5f5..67038445c 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -2,13 +2,24 @@ name: e2e-cli on: workflow_call: + secrets: + CODECOV_TOKEN: + required: true + +permissions: + contents: read jobs: check-license: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Check license header uses: apache/skywalking-eyes/header@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91 with: @@ -18,17 +29,21 @@ jobs: uses: apache/skywalking-eyes/dependency@cd7b195c51fd3d6ad52afceb760719ddc6b3ee91 with: config: .github/licenserc.yml - flags: - --weak-compatible=true + flags: --weak-compatible=true build: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: setup go environment - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: "1.21" + go-version: "1.22" - name: Run tidy run: go mod tidy - name: Build CLI @@ -36,9 +51,9 @@ jobs: - name: Check build run: bin/ratify version - name: Upload coverage to codecov.io - uses: codecov/codecov-action@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} - name: Run helm lint run: helm lint charts/ratify build_test_cli: @@ -47,12 +62,17 @@ jobs: permissions: contents: read steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: setup go environment - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: "1.21" + go-version: "1.22" - name: Run tidy run: go mod tidy - name: Build CLI @@ -64,20 +84,25 @@ 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@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} markdown-link-check: - runs-on: ubuntu-latest - steps: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Run link check uses: gaurav-nelson/github-action-markdown-link-check@d53a906aa6b22b8979d33bc86170567e619495ec #3.10.3 with: - use-quiet-mode: 'no' - use-verbose-mode: 'yes' - config-file: '.github/workflows/markdown.links.config.json' - folder-path: 'docs/' \ No newline at end of file + use-quiet-mode: "no" + use-verbose-mode: "yes" + config-file: ".github/workflows/markdown.links.config.json" + folder-path: "docs/" diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index 228d8ae6d..717ff937c 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -1,17 +1,20 @@ name: e2e-k8s +permissions: + contents: read + on: workflow_call: inputs: k8s_version: - description: 'Kubernetes version' + description: "Kubernetes version" required: true - default: '1.27.7' + default: "1.29.2" type: string gatekeeper_version: - description: 'Gatekeeper version' + description: "Gatekeeper version" required: true - default: '3.15.0' + default: "3.17.0" type: string jobs: @@ -22,12 +25,17 @@ jobs: permissions: contents: read steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Set up Go 1.21 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up Go 1.22 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.21' + go-version: "1.22" - name: Bootstrap e2e run: | @@ -57,9 +65,9 @@ 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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ always() }} with: name: e2e-logs-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }} path: | - logs-*.json \ No newline at end of file + logs-*.json diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 382e27524..cb827af40 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -3,6 +3,7 @@ on: push: branches: - main + - dev - 1.0.0* pull_request: workflow_dispatch: @@ -13,11 +14,17 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: - go-version: '1.21' - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + egress-policy: audit + + - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: "1.22" + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: golangci-lint - uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4.0.0 + uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 with: - version: v1.55.2 + version: v1.59.1 + args: --timeout=10m diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index 06acdcee3..631f9bb6f 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -6,11 +6,13 @@ on: pull_request: branches: - main + - dev - 1.0.0* push: branches: - 1.0.0* - main + - dev workflow_dispatch: permissions: read-all @@ -25,14 +27,19 @@ jobs: contents: read strategy: matrix: - DAPR_VERSION: ["1.11.1"] + DAPR_VERSION: ["1.13.2"] steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Set up Go 1.21 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up Go 1.22 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.21' + go-version: "1.22" - name: Bootstrap e2e run: | @@ -53,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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ always() }} with: name: e2e-logs-${{ matrix.DAPR_VERSION }} diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml index bbcb4ca8e..5fcefe211 100644 --- a/.github/workflows/pr-to-main.yml +++ b/.github/workflows/pr-to-main.yml @@ -1,27 +1,31 @@ name: pr_to_main on: - push: - branches: - - 'staging' + schedule: + - cron: "30 8 * * 0" # early morning (08:30 UTC) every Sunday + workflow_dispatch: permissions: pull-requests: write jobs: - main: - name: Create PR Release to Main + pull-request: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: git checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - # https://github.com/marketplace/actions/github-pull-request-action + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" - name: create pull request with reposync action id: open-pr uses: repo-sync/pull-request@7e79a9f5dc3ad0ce53138f01df2fad14a04831c5 #v2.12.1 with: - github_token: ${{ secrets.PR_TOKEN }} destination_branch: main - pr_title: ${{ github.event.commits[0].message }} - pr_body: "Automated Pull Request" + pr_title: "chore: automated PR to main ${{ steps.date.outputs.date }}" + pr_body: "Automated Pull Request to main branch" diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index 20fdf69ac..850838750 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -12,8 +12,13 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Publish Helm charts uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0 with: - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-cosign-sample.yml b/.github/workflows/publish-cosign-sample.yml new file mode 100644 index 000000000..566c27885 --- /dev/null +++ b/.github/workflows/publish-cosign-sample.yml @@ -0,0 +1,60 @@ +name: publish-cosign-sample + +on: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +permissions: + contents: read + +jobs: + build-publish: + name: "Build and publish cosign signed sample image" + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: write + packages: write + id-token: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - name: Install cosign + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + + - name: Get repo + run: | + echo "REPOSITORY=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_ENV + + - name: Write signing key to disk + run: 'echo "$KEY" > cosign.key' + shell: bash + env: + KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + + - name: Log in to GHCR + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build sample images + run: | + docker build -t ${REPOSITORY}/cosign-image:signed-key https://github.com/wabbit-networks/net-monitor.git + docker build --no-cache -t ${REPOSITORY}/cosign-image:signed-keyless https://github.com/wabbit-networks/net-monitor.git + docker build --no-cache -t ${REPOSITORY}/cosign-image:unsigned https://github.com/wabbit-networks/net-monitor.git + - name: Push images + run: | + docker push ${REPOSITORY}/cosign-image:signed-key + docker push ${REPOSITORY}/cosign-image:signed-keyless + docker push ${REPOSITORY}/cosign-image:unsigned + - name: Sign image with cosign + run: | + cosign sign --yes --key cosign.key ${REPOSITORY}/cosign-image:signed-key + cosign sign --yes ${REPOSITORY}/cosign-image:signed-keyless diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index e24bbbd0e..c64a243e8 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -2,7 +2,7 @@ name: publish-dev-assets on: schedule: - - cron: '30 8 * * 0' # early morning (08:30 UTC) every Sunday + - cron: "30 8 * * 0" # early morning (08:30 UTC) every Sunday workflow_dispatch: permissions: read-all @@ -13,9 +13,30 @@ jobs: permissions: packages: write contents: read + id-token: write + environment: azure-publish steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - name: Install Notation + uses: notaryproject/notation-action/setup@104aa999103172f827373af8ac14dde7aa6d28f1 # v1.1.0 + - name: Install cosign + uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 + - name: Az CLI login + uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Cache AAD tokens + run: | + az version + # Key Vault: + az account get-access-token --scope https://vault.azure.net/.default --output none - name: prepare id: prepare run: | @@ -37,7 +58,7 @@ jobs: echo ::set-output name=baseref::${REPOSITORYBASE} echo ::set-output name=crdref::${REPOSITORYCRD} - name: docker login - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -45,13 +66,24 @@ jobs: - name: docker build ratify-crds run: | docker buildx create --use - docker buildx build --build-arg KUBE_VERSION="1.27.7" -f crd.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 --label org.opencontainers.image.revision=${{ github.sha }} -t ${{ steps.prepare.outputs.crdref }}:${{ steps.prepare.outputs.version }} -t ${{ steps.prepare.outputs.crdref }} --push ./charts/ratify/crds + docker buildx build \ + --attest type=sbom \ + --attest type=provenance,mode=max \ + --build-arg KUBE_VERSION="1.29.2" \ + -f crd.Dockerfile \ + --platform linux/amd64,linux/arm64,linux/arm/v7 \ + --label org.opencontainers.image.revision=${{ github.sha }} \ + -t ${{ steps.prepare.outputs.crdref }}:${{ steps.prepare.outputs.version }} \ + -t ${{ steps.prepare.outputs.crdref }} \ + --push ./charts/ratify/crds - name: docker build ratify base run: | docker buildx create --use docker buildx build -f ./httpserver/Dockerfile \ + --attest type=sbom \ + --attest type=provenance,mode=max \ --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg LDFLAGS="-X github.com/deislabs/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 }}:${{ steps.prepare.outputs.version }} \ -t ${{ steps.prepare.outputs.baseref }} \ @@ -60,29 +92,52 @@ jobs: run: | docker buildx create --use docker buildx build -f ./httpserver/Dockerfile \ + --attest type=sbom \ + --attest type=provenance,mode=max \ --platform linux/amd64,linux/arm64,linux/arm/v7 \ --build-arg build_sbom=true \ --build-arg build_licensechecker=true \ --build-arg build_schemavalidator=true \ --build-arg build_vulnerabilityreport=true \ - --build-arg LDFLAGS="-X github.com/deislabs/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 }}:${{ steps.prepare.outputs.version }} \ -t ${{ steps.prepare.outputs.ref }} \ --push . - name: replace version run: | - sed -i '/^ repository:/c\ repository: ghcr.io/deislabs/ratify-dev' charts/ratify/values.yaml - sed -i '/^ crdRepository:/c\ crdRepository: ghcr.io/deislabs/ratify-crds-dev' charts/ratify/values.yaml - sed -i '/^ tag:/c\ tag: ${{ steps.prepare.outputs.version }}' charts/ratify/values.yaml + sed -i '/^ repository:/c\ repository: ghcr.io/ratify-project/ratify-dev' charts/ratify/values.yaml + sed -i '/^ crdRepository:/c\ crdRepository: ghcr.io/ratify-project/ratify-crds-dev' charts/ratify/values.yaml + sed -i '/^ tag:/c\ tag: ${{ steps.prepare.outputs.version }}' charts/ratify/values.yaml - name: helm package run: | - helm package ./charts/ratify --version ${{ steps.prepare.outputs.semversion }} - helm package ./charts/ratify --version ${{ steps.prepare.outputs.semversionrolling }} + helm package ./charts/ratify --version ${{ steps.prepare.outputs.semversion }} + helm package ./charts/ratify --version ${{ steps.prepare.outputs.semversionrolling }} - name: helm push run: | 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 + with: + plugin_name: azure-kv + plugin_url: ${{ vars.AZURE_KV_PLUGIN_URL }} + plugin_checksum: ${{ vars.AZURE_KV_CHECKSUM }} + key_id: ${{ secrets.AZURE_KV_KEY_ID }} + target_artifact_reference: |- + ${{ steps.prepare.outputs.crdref }}:${{ steps.prepare.outputs.version }} + ${{ steps.prepare.outputs.baseref }}:${{ steps.prepare.outputs.version }} + ${{ steps.prepare.outputs.ref }}:${{ steps.prepare.outputs.version }} + ${{ steps.prepare.outputs.chartrepo }}/ratify:${{ steps.prepare.outputs.semversionrolling }} + ${{ steps.prepare.outputs.chartrepo }}/ratify:${{ steps.prepare.outputs.semversion }} + signature_format: cose + - name: Sign with Cosign + run: | + cosign sign --yes ${{ steps.prepare.outputs.crdref }}:${{ steps.prepare.outputs.version }} + cosign sign --yes ${{ steps.prepare.outputs.baseref }}:${{ steps.prepare.outputs.version }} + cosign sign --yes ${{ steps.prepare.outputs.ref }}:${{ steps.prepare.outputs.version }} + cosign sign --yes ${{ steps.prepare.outputs.chartrepo }}/ratify:${{ steps.prepare.outputs.semversionrolling }} + cosign sign --yes ${{ steps.prepare.outputs.chartrepo }}/ratify:${{ steps.prepare.outputs.semversion }} - name: clear if: always() run: | diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 77103e1ff..e4d81f984 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -15,8 +15,12 @@ jobs: packages: write contents: read steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: prepare id: prepare run: | @@ -36,7 +40,7 @@ jobs: run: | echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: docker login - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -44,13 +48,23 @@ jobs: - name: docker build ratify-crds run: | docker buildx create --use - docker buildx build --build-arg KUBE_VERSION="1.27.7" -f crd.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 --label org.opencontainers.image.revision=${{ github.sha }} -t ${{ steps.prepare.outputs.crdref }} --push ./charts/ratify/crds + docker buildx build \ + --attest type=sbom \ + --attest type=provenance,mode=max \ + --build-arg KUBE_VERSION="1.29.2" \ + -f crd.Dockerfile \ + --platform linux/amd64,linux/arm64,linux/arm/v7 \ + --label org.opencontainers.image.revision=${{ github.sha }} \ + -t ${{ steps.prepare.outputs.crdref }} \ + --push ./charts/ratify/crds - name: docker build ratify base run: | docker buildx create --use docker buildx build -f ./httpserver/Dockerfile \ + --attest type=sbom \ + --attest type=provenance,mode=max \ --platform linux/amd64,linux/arm64,linux/arm/v7 \ - --build-arg LDFLAGS="-X github.com/deislabs/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 . @@ -58,12 +72,14 @@ jobs: run: | docker buildx create --use docker buildx build -f ./httpserver/Dockerfile \ + --attest type=sbom \ + --attest type=provenance,mode=max \ --platform linux/amd64,linux/arm64,linux/arm/v7 \ --build-arg build_sbom=true \ --build-arg build_licensechecker=true \ --build-arg build_schemavalidator=true \ --build-arg build_vulnerabilityreport=true \ - --build-arg LDFLAGS="-X github.com/deislabs/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 . diff --git a/.github/workflows/publish-sample.yml b/.github/workflows/publish-sample.yml index 6eaf7db2c..52981797d 100644 --- a/.github/workflows/publish-sample.yml +++ b/.github/workflows/publish-sample.yml @@ -1,11 +1,11 @@ name: publish-sample -on: - workflow_dispatch: +on: + workflow_dispatch: env: REGISTRY: ghcr.io - + permissions: contents: read @@ -17,22 +17,25 @@ jobs: permissions: contents: write packages: write - steps: + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Get repo - run: | + run: | echo "REPOSITORY=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_ENV - name: Log in to the GHCR - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Copy signed sample test image - run: - oras cp -r wabbitnetworks.azurecr.io/ratify/notary-image:signed ${REPOSITORY}/notary-image:signed + run: oras cp -r wabbitnetworks.azurecr.io/ratify/notary-image:signed ${REPOSITORY}/notary-image:signed - - name: Copy unsigned sample test image - run: - oras cp wabbitnetworks.azurecr.io/ratify/notary-image:unsigned ${REPOSITORY}/notary-image:unsigned + - name: Copy unsigned sample test image + run: oras cp wabbitnetworks.azurecr.io/ratify/notary-image:unsigned ${REPOSITORY}/notary-image:unsigned diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index 29d7dd424..f6739f243 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -1,15 +1,20 @@ name: quick-start +permissions: + contents: read + on: pull_request_target: types: [labeled] pull_request: branches: - main + - dev - 1.0.0* push: branches: - 1.0.0* + - dev - main workflow_dispatch: @@ -22,14 +27,19 @@ jobs: contents: read strategy: matrix: - KUBERNETES_VERSION: ["1.27.7"] + KUBERNETES_VERSION: ["1.29.2"] steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: setup go environment - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: "1.21" + go-version: "1.22" - name: Run tidy run: go mod tidy - name: Bootstrap e2e @@ -49,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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: ${{ always() }} with: name: e2e-logs-${{ matrix.KUBERNETES_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a7f2931d..e261c1bd6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release Ratify on: push: tags: - - 'v*' + - "v*" workflow_dispatch: permissions: read-all @@ -15,32 +15,41 @@ jobs: permissions: contents: write steps: - - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag=3.0.2 - with: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 + with: fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version: '1.21' - - - name: Goreleaser - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 - with: - version: '1.18.0' - args: release --rm-dist - env: + + - name: Install Syft + uses: anchore/sbom-action/download-syft@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2 + + - name: Set up Go + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: "1.22" + + - name: Goreleaser + id: goreleaser + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + with: + version: "2.0.1" + args: release --clean + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Generate SBOM - run: | - curl -Lo $RUNNER_TEMP/sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64 - chmod +x $RUNNER_TEMP/sbom-tool - $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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # tag=v4.3.1 - with: - name: SBOM SPDX files - path: _manifest/spdx_2.2/** + - name: Generate SBOM + run: | + curl -Lo $RUNNER_TEMP/sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64 + chmod +x $RUNNER_TEMP/sbom-tool + $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 + with: + name: SBOM SPDX files + path: _manifest/spdx_2.2/** diff --git a/.github/workflows/run-full-validation.yml b/.github/workflows/run-full-validation.yml index bce9ca720..3f2464cbc 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -1,15 +1,13 @@ name: run-full-validation on: - pull_request_target: - types: [labeled] pull_request: branches: - main - - 1.0.0* + - release* push: branches: - - 1.0.0* + - release* - main workflow_dispatch: @@ -18,6 +16,8 @@ permissions: read-all jobs: call-e2e-cli: uses: ./.github/workflows/e2e-cli.yml + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} call_test_e2e_full: name: "Build and run e2e on full test matrix" @@ -26,9 +26,9 @@ jobs: strategy: fail-fast: false matrix: - KUBERNETES_VERSION: ["1.26.10", "1.27.7"] - GATEKEEPER_VERSION: ["3.13.0", "3.14.0", "3.15.0"] - uses: ./.github/workflows/e2e-k8s.yml + KUBERNETES_VERSION: ["1.28.12", "1.29.2"] + GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} @@ -41,8 +41,8 @@ jobs: strategy: fail-fast: false matrix: - KUBERNETES_VERSION: ["1.26.10", "1.27.7"] - GATEKEEPER_VERSION: ["3.13.0", "3.14.0", "3.15.0"] + KUBERNETES_VERSION: ["1.28.12", "1.29.2"] + GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] uses: ./.github/workflows/e2e-aks.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} @@ -50,28 +50,32 @@ jobs: secrets: inherit aks-test-cleanup: - env: - AZURE_SUBSCRIPTION_ID: daae1e1a-63dc-454f-825d-b39289070f79 - AZURE_CLIENT_ID: 814e6e97-120c-4534-b8a9-f1645bc99500 - AZURE_TENANT_ID: 72f988bf-86f1-41af-91ab-2d7cd011db47 - needs: ['build_test_aks_e2e'] + needs: ["build_test_aks_e2e"] runs-on: ubuntu-latest permissions: id-token: write contents: read + environment: azure-test steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: Set up Go 1.21 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - name: Set up Go 1.22 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.21' + go-version: "1.22" - name: Az CLI login - uses: azure/login@8c334a195cbb38e46038007b304988d888bf676a # v2.0.0 + uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 with: - creds: '{"clientId":"${{ env.AZURE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_CLIENT_SECRET }}","subscriptionId":"${{ env.AZURE_SUBSCRIPTION_ID }}","tenantId":"${{ env.AZURE_TENANT_ID }}"}' + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - name: clean up run: | - make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ env.AZURE_SUBSCRIPTION_ID }} \ No newline at end of file + make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml new file mode 100644 index 000000000..aed9bba20 --- /dev/null +++ b/.github/workflows/scan-vulns.yaml @@ -0,0 +1,75 @@ +name: scan_vulns +on: + push: + paths-ignore: + - "docs/**" + - "library/**" + - "**.md" + pull_request: + paths-ignore: + - "docs/**" + - "library/**" + - "**.md" + schedule: + - cron: "30 8 * * 0" # early morning (08:30 UTC) every Sunday + workflow_dispatch: + +permissions: read-all + +jobs: + govulncheck: + name: "Run govulncheck" + runs-on: ubuntu-22.04 + timeout-minutes: 15 + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: "1.22" + check-latest: true + - uses: golang/govulncheck-action@dd0578b371c987f96d1185abb54344b44352bd58 # v1.0.3 + + scan_vulnerabilities: + name: "[Trivy] Scan for vulnerabilities" + runs-on: ubuntu-22.04 + timeout-minutes: 15 + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - name: Check out code into the Go module directory + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + + - 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: Run trivy on git repository + run: | + trivy fs --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 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 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2a47a0218..ea8382ee8 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -3,9 +3,17 @@ on: branch_protection_rule: schedule: # Weekly on Saturdays. - - cron: '30 1 * * 6' + - cron: "30 1 * * 6" push: - branches: [ main ] + branches: + - main + - dev + - release-* + pull_request: + branches: + - dev + - main + - release-* workflow_dispatch: permissions: read-all @@ -19,15 +27,20 @@ jobs: id-token: write actions: read contents: read - + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + - name: "Checkout code" - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag=3.0.2 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # tag=3.0.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # tag=v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # tag=v2.4.0 with: results_file: results.sarif results_format: sarif @@ -35,13 +48,13 @@ jobs: publish_results: true - name: "Upload artifact" - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # tag=v4.3.1 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # tag=v4.4.0 with: name: SARIF file path: results.sarif retention-days: 5 - + - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@cdcdbb579706841c47f7063dda365e292e5cad7a # tag=v2.13.4 + uses: github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d # tag=v3.26.7 with: sarif_file: results.sarif diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml index 708ea54e0..8c584c7e7 100644 --- a/.github/workflows/sync-gh-pages.yml +++ b/.github/workflows/sync-gh-pages.yml @@ -2,9 +2,9 @@ name: Sync GH Pages on: push: branches: - - main + - main paths: - - library/** + - library/** permissions: read-all @@ -16,10 +16,15 @@ jobs: pull-requests: write repository-projects: write steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: everlytic/branch-merge@c4a244dc23143f824ae6c022a10732566cb8e973 with: github_token: ${{ github.token }} source_ref: ${{ github.ref }} - target_branch: 'gh-pages' - commit_message_template: '[Automated] Merged {source_ref} into target {target_branch}' \ No newline at end of file + target_branch: "gh-pages" + commit_message_template: "[Automated] Merged {source_ref} into target {target_branch}" diff --git a/.golangci.yml b/.golangci.yml index 2adfff8f6..45a5d329b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,3 @@ -run: - deadline: 5m - linters: disable-all: true enable: diff --git a/.goreleaser.yml b/.goreleaser.yml index 994d94350..73aead7f3 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,4 +1,5 @@ # Check the documentation at https://goreleaser.com for more options +version: 2 before: hooks: - go mod tidy @@ -19,7 +20,7 @@ builds: - goos: windows goarch: arm64 ldflags: - - -w -X github.com/deislabs/ratify/internal/version.GitTag={{.Version}} -X github.com/deislabs/ratify/internal/version.GitCommitHash={{.FullCommit}} + - -w -X github.com/ratify-project/ratify/internal/version.GitTag={{.Version}} -X github.com/ratify-project/ratify/internal/version.GitCommitHash={{.FullCommit}} - id: sbom dir: plugins/verifier/sbom @@ -57,15 +58,15 @@ release: prerelease: auto draft: true archives: - - replacements: - darwin: Darwin - linux: Linux - windows: Windows - format_overrides: + - format_overrides: - goos: windows format: zip checksum: name_template: 'checksums.txt' +sboms: + - artifacts: archive + - id: source + artifacts: source snapshot: name_template: '{{ incpatch .Version }}-next' changelog: diff --git a/.vscode/launch.json b/.vscode/launch.json index 3f39685ca..d96839b00 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,15 +12,15 @@ "program": "${workspaceFolder}/cmd/ratify", "env": { "RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS": "1", - "RATIFY_LOG_LEVEL": "debug", + "RATIFY_LOG_LEVEL": "debug" }, "args": [ "verify", "-s", "${input:subject}", "-c", - "${input:configPath}", - ], + "${input:configPath}" + ] }, { "name": "Serve", @@ -35,10 +35,9 @@ "serve", "--http", ":6001" - ], + ] }, { - // This requires your kubeconfig to be pointed at a cluster with Ratify CRDs installed "name": "Serve w/ CRD manager", "type": "go", "request": "launch", @@ -47,18 +46,19 @@ "env": { "RATIFY_LOG_LEVEL": "debug", "RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS": "1", - "RATIFY_NAMESPACE": "gatekeeper-system", + "RATIFY_NAMESPACE": "gatekeeper-system" }, "args": [ "serve", "--enable-crd-manager", "--http", ":6001" - ], + ] }, { // This requires your kubeconfig to be pointed at a cluster with Ratify CRDs installed - // This requires you to have generated tls.crt and tls.key and placed them in a single directory + // This requires you to have generated server TLS certs: tls.crt, tls.key, ca.crt, ca.key and placed them in a single directory + // This requires you to have a client CA cert (Gatekeeper CA cert) to verify the client cert "name": "Serve w/ CRD manager and TLS enabled", "type": "go", "request": "launch", @@ -73,8 +73,10 @@ "--enable-crd-manager", "--http", ":6001", - "--cert-dir=${input:tlsDir}" - ], + "--cert-dir=${input:tlsDir}", + "--ca-cert-file=${input:clientCACert}", + "--config=${input:configPath}" + ] }, { "name": "Debug SBOM Plugin", @@ -87,7 +89,7 @@ "RATIFY_LOG_LEVEL": "debug", "RATIFY_VERIFIER_COMMAND": "VERIFY", "RATIFY_VERIFIER_SUBJECT": "wabbitnetworks.azurecr.io/test/image:sbom", - "RATIFY_VERIFIER_VERSION": "1.0.0", + "RATIFY_VERIFIER_VERSION": "1.0.0" }, "console": "integratedTerminal" } @@ -111,5 +113,11 @@ "description": "Absolute path to tls cert and key directory", "default": "${workspaceFolder}/tls/certs" }, + { + "id": "clientCACert", + "type": "promptString", + "description": "Absolute path to client CA cert (Gatekeeper CA cert)", + "default": "${workspaceFolder}/client-ca-cert/ca.crt" + } ] } diff --git a/.well-known/pki-validation/ratify-verification.crt b/.well-known/pki-validation/ratify-verification.crt new file mode 100644 index 000000000..065c274bf --- /dev/null +++ b/.well-known/pki-validation/ratify-verification.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQbZCNsoQpQC+/hsEf/FdjDjANBgkqhkiG9w0BAQsFADBa +MQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxFzAV +BgNVBAoTDnJhdGlmeS1wcm9qZWN0MRMwEQYDVQQDEwpyYXRpZnkuZGV2MB4XDTI0 +MDYxMDIyNDQ1OVoXDTI1MDYxMDIyNTQ1OVowWjELMAkGA1UEBhMCVVMxCzAJBgNV +BAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRcwFQYDVQQKEw5yYXRpZnktcHJvamVj +dDETMBEGA1UEAxMKcmF0aWZ5LmRldjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAM6wAPQhVoIA7qbckyk8qYYiTnKjlUXaThXn8RYe6+dYQjBypxciAyyJ +NLbM675gXTc26rZDm29ldsych6G6VSP0wkziqZHIYKTSZ/kSzw0mm/KLRgHQPKsj +PdhVYnLbg4keyKvSRBDmKl6I04VMNXERezJWpcK0F+6wtt6BKZOJRSPCbTPhHIVY +U5iwqwMt9lOzNgWKsFUfTW+eZ2sPOJzpu78h/0JaOGgsms80Btm5UBjeJCWAOQDS +VzurUk8Q5EX5X4faNbUiKLREY4210ELVZq8xU1yixkzO2reh9Sw6maG2YLW3W0rc +bn1PTxz5ddvTF1r/J+A9JSQUhVCM7sEfh9oXW6az4OeZVCD1beG1mX5mtE/kzZqN +9KfzvBSwdFx4D4t0KBG4/f2/k2pwZuTy3qqA+R9cd434jJpdY4/dQi0JTAG7XmuG +HMpEM/1TQmXV3Nccwbyy5W2D07/y4fDVSxh2czumK8C7hWs0EX3CQ0C4mi0o3GjD +1meir2GYKDqQUyVQx0gcDgOVDQRsyA2zQ3gUy7/7RGDbtaGN0XVT0kBMU66aS6Mj +z69YEu+Yj3QNqVQL/Ms4A2G+mPXoqVTikQxbDI5Bi3d5U0Xu6zQ7/Wx+li1pO2TV +3tNSyy5Tj/IT5Mou5e5oDt81yIfrXtb6Mye5zJ6uiqq5RwK9+b2BAgMBAAGjcjBw +MA4GA1UdDwEB/wQEAwIHgDAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMD +MB8GA1UdIwQYMBaAFO4kXbjSuXzopPxrB1tKZ5g4oZQ/MB0GA1UdDgQWBBTuJF24 +0rl86KT8awdbSmeYOKGUPzANBgkqhkiG9w0BAQsFAAOCAgEASs8iFwuuh1lB1eM7 +cDM2rZfExb0RFXkcHWdF6+dEwuchn9rjKKXFLXU4k5sARKj+rTzxKGKNbymCLUto +DzaJwwWygZu7HoG5DHGcj5StlBXNELMCoRwFW/MjRTv4Sfkiakw03PUBeiZeg8Xb +SB3WuZreD0c/DPiONuXGSdx5cPgf0NkMTY8hiOoENP9eBCqcMhCUGMYRf1sIXr4m +YWuS5KTB93DsWLjdw+X9FDi0irFa1uP01IM+iSsPtoGoT1tV8fnYl5anvBCLsh8U +Y9vAXwJvZM6XlfA5XX4fzq82KuLASMBktyhXrYWeGBUOYOpQa4M3c/KiJyYjtm08 +pFRBdbzUwK1GF2mDfO/8oyyqPMhdkD3dO6/iToOklSLcNS4AVoTBE+S62QHHluBD +zlmeVabCB/TGLRm5wlKKm7YRI7DJXF0HVQ5KS8vXjbT1MzN7WB+Iey2lelH3U+CR +V50lbGJhvs8WiSojzcl1xJAvhRaWXZ/h8TCBI8srQzBlZ+/8o2zhs4nlEFrgurzq +Vd+m0s1lM1iGHrE//AxlU7NVh2yCjrhPumQ53Sv53LG3Kl5RD86Pvhd01ORvKKNR +zS27F63BGy6basxXtHR8ibCPNs1gbT24YLfaPiXmhy0j8dwPhEFGwQEIL8peSG56 +6OcrwcZ4J28hTXOl4EIJ2Prp9mQ= +-----END CERTIFICATE----- diff --git a/BREAKING_CHANGE_AND_DEPRECATION.md b/BREAKING_CHANGE_AND_DEPRECATION.md new file mode 100644 index 000000000..a766d8172 --- /dev/null +++ b/BREAKING_CHANGE_AND_DEPRECATION.md @@ -0,0 +1,45 @@ +# Breaking changes and deprecations + +Breaking changes are defined as a change to any of the following that causes installation errors or +unexpected runtime behavior after upgrading to the next stable minor version of Ratify: +- Verification API +- Verification result and schema +- Default configuration value +- User facing plugin interfaces + +## Stability levels +- Generally available features should not be removed from that version or have its behavior significantly changed to avoid breaking existing users. +- Beta or pre-release versions may introduce breaking changes without deprecation notice. +- Alpha or experimental API versions may change in runtime behaviour without prior change and deprecation notice. + +The following features are currently in experimental: +- [Dynamic plugin](https://ratify.dev/docs/reference/dynamic-plugins) +- [High Availability](https://ratify.dev/docs/quickstarts/ratify-high-availability) + +## Process for applying breaking changes +- A deprecation notice must be posted as part of a release. +- The PR containing the breaking changes should contain a "!" to indicate this is breaking change. E.g. feat! , fix! +- Breaking changes should be listed before new features in the release notes +- Create a issue to help customer to mitigate the change + +## Deprecations +Deprecations can apply to: + - CRDs + - Experimental Features + - Plugins + - User facing Interfaces + - Configuration Values + - Cli Usage and Configuration + - Verification Schema + +## Process for deprecation + + - A deprecation notice must be posted as part of the release notes. + - Documentation should mark the feature as deprecated and redirect user to the alternative implementation. + +## Attribution + +The specification release process was created using content and verbiage from the following specifications: +- [Kubernetes Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/) +- [Dapr reference](https://docs.dapr.io/operations/support/breaking-changes-and-deprecations/) + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 343d0da35..5acefcd1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,21 +10,39 @@ Welcome! We are very happy to accept community contributions to Ratify, whether * Checkout the repo locally with `git clone git@github.com:{your_username}/ratify.git`. * Build the Ratify CLI with `go build -o ./bin/ratify ./cmd/ratify` or if on Mac/Linux/WSL `make build-cli`. +## 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: +- Proposed changes +- Proposed feature flag +- Impacted code paths +- Required test coverage +- Backward compatibility +- Performance impact +- Security consideration +- Open questions + +This approach ensures that the changes are well-documented and reviewed before implementation. + ## Pull Requests -If you'd like to start contributing to Ratify, you can search for issues tagged as "good first issue" [here](https://github.com/deislabs/ratify/labels/good%20first%20issue). +If you'd like to start contributing to Ratify, you can search for issues tagged as "good first issue" [here](https://github.com/ratify-project/ratify/labels/good%20first%20issue). -We use the `staging` branch as the our default branch. All ratify release are cut from the main branch. A sample PR process is outlined below: -1. Fork this repo and create your dev branch from default `staging` branch. -2. Create a PR against default branch -3. Maintainer approval and e2e test validation is required for completing the PR. -4. On PR complete, the `push` event will trigger an automated PR targeting the `main` branch where we run a full suite validation including cloud specific tests. +We use the `dev` branch as the our default branch. PRs passing the basic set of validation can be merged to the `dev` branch, we then run the full suite of validation including cloud specific tests on `dev` before changes can be merged into `main`. All ratify release are cut from the `main` branch. A sample PR process is outlined below: +1. Fork this repo and create your dev branch from default `dev` branch. +2. Create a PR against default branch. +3. Add new unit test and [e2e test](https://github.com/ratify-project/ratify/tree/dev/test/bats) where approriate. +4. Maintainer approval and e2e test validation is required for completing the PR. +5. On PR complete, the `push` event will trigger an automated PR targeting the `main` branch where we run a full suite validation including cloud specific tests. 6. Manual merge is required to complete the PR. (**Please keep individual commits to maintain commit history**) If the PR contains a regression that could not pass the full validation, please revert the change to unblock others: -1. Create a new dev branch based off staging. -2. Open a revert PR against staging. -3. Follow the same process to get this PR gets merged into staging. +1. Create a new dev branch based off `dev`. +2. Open a revert PR against `dev`. +3. Follow the same process to get this PR gets merged into `dev`. 4. Work on the fix and follow the above PR process. ## Developing @@ -54,7 +72,9 @@ The Ratify project is composed of the following main components: 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. -Sample json for cli: +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`, +and then modify the config file by setting the `verificationCerts` attribute in the notation verifier to the downloaded cert file path. ```json { @@ -65,7 +85,11 @@ Sample json for cli: "request": "launch", "mode": "debug", "program": "${workspaceFolder}/cmd/ratify", - "args": ["verify", "-s", "ratify.azurecr.io/testimage@sha256:9515b691095051d68b4409a30c4819c98bd6f4355d5993a7487687cdc6d47cc3"] + "args": [ + "verify", + "-s", "ghcr.io/deislabs/ratify/notary-image:signed", + "-c", "${workspaceFolder}/test/bats/tests/config/config_cli.json" + ] }] } ``` @@ -148,7 +172,7 @@ Sample JSON stdin Press `Ctrl+D` to send EOF character to terminate the stdin input. (Note: you may have to press `Ctrl+D` twice) -View more plugin debugging information [here](https://github.com/deislabs/ratify-verifier-plugin#debugging-in-vs-code) +View more plugin debugging information [here](https://github.com/ratify-project/ratify-verifier-plugin#debugging-in-vs-code) ### Test local changes in the k8s cluster scenario @@ -161,14 +185,14 @@ Follow the steps below to build and deploy a Ratify image with your private chan export REGISTRY=yourregistry docker buildx create --use -docker buildx build -f httpserver/Dockerfile --platform linux/amd64 --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -t ${REGISTRY}/deislabs/ratify:yourtag . -docker build --progress=plain --build-arg KUBE_VERSION="1.27.7" --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t ${REGISTRY}/localbuildcrd:yourtag ./charts/ratify/crds +docker buildx build -f httpserver/Dockerfile --platform linux/amd64 --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -t ${REGISTRY}/ratify-project/ratify:yourtag . +docker build --progress=plain --build-arg KUBE_VERSION="1.29.2" --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t ${REGISTRY}/localbuildcrd:yourtag ./charts/ratify/crds ``` #### [Authenticate](https://docs.docker.com/engine/reference/commandline/login/#usage) with your registry, and push the newly built image ```bash -docker push ${REGISTRY}/deislabs/ratify:yourtag +docker push ${REGISTRY}/ratify-project/ratify:yourtag docker push ${REGISTRY}/localbuildcrd:yourtag ``` @@ -196,16 +220,16 @@ Development charts + images are published weekly and latest versions are tagged Deploy to cluster: ```bash -helmfile sync -f git::https://github.com/deislabs/ratify.git@dev.helmfile.yaml +helmfile sync -f git::https://github.com/ratify-project/ratify.git@dev.helmfile.yaml ``` ### Deploy from local helm chart -#### Update [values.yaml](https://github.com/deislabs/ratify/blob/main/charts/ratify/values.yaml) to pull from your registry, when reusing image tag, setting pull policy to "Always" ensures we are pull the new changes +#### Update [values.yaml](https://github.com/ratify-project/ratify/blob/main/charts/ratify/values.yaml) to pull from your registry, when reusing image tag, setting pull policy to "Always" ensures we are pull the new changes ```json image: - repository: yourregistry/deislabs/ratify + repository: yourregistry/ratify-project/ratify tag: yourtag pullPolicy: Always ``` @@ -269,19 +293,25 @@ Gatekeeper requires TLS for external data provider interactions. As such ratify helm install ratify \ ./charts/ratify --atomic \ --namespace gatekeeper-system \ - --set-file notationCert=./test/testdata/notation.crt \ + --set logger.level=debug \ + --set-file notationCerts[0]=./test/testdata/notation.crt \ --set-file provider.tls.crt=./tls/certs/tls.crt \ --set-file provider.tls.key=./tls/certs/tls.key \ - --set-file provider.tls.cabundle=./tls/certs/ca.crt + --set provider.tls.cabundle="$(cat ./tls/certs/ca.crt | base64 | tr -d '\n\r')" \ + --set-file provider.tls.caCert=./tls/certs/ca.crt \ + --set-file provider.tls.caKey=./tls/certs/ca.key ``` +Update the `KubernetesLocalProcessConfig.yaml` with updated directory/file paths: +- In the file, set the `` to an absolute directory accessible on local environment. This is the directory where Bridge to K8s will download the Azure Workload Identity JWT token. +- In the file, set the `` to an absolute directory accessible on local environment. This is the directory where Bridge to K8s will download the `client-ca-cert` volume (Gatekeeper's `ca.crt`). Configure Bridge to Kubernetes (Comprehensive guide [here](https://learn.microsoft.com/en-us/visualstudio/bridge/bridge-to-kubernetes-vs-code)) 1. Open the `Command Palette` in VSCode `CTRL-SHIFT-P` -1. Select `Bridge to Kubernetes: Configure` -1. Select `Ratify` from the list as the service to redirect to -1. Set port to be 6001 -1. Select `Serve w/ CRD manager and TLS enabled` as the launch config -1. Select 'No' for request isolation +2. Select `Bridge to Kubernetes: Configure` +3. Select `Ratify` from the list as the service to redirect to +4. Set port to be 6001 +5. Select `Serve w/ CRD manager and TLS enabled` as the launch config +6. Select 'No' for request isolation This should automatically append a new Bridge to Kubernetes configuration to the launch.json file and add a new tasks.json file. @@ -308,25 +338,11 @@ If you'd like to contribute to the collection of plugins: ## Feature Suggestions -* Please first search [Open Ratify Issues](https://github.com/deislabs/ratify/issues) before opening an issue to check whether your feature has already been suggested. If it has, feel free to add your own comments to the existing issue. +* Please first search [Open Ratify Issues](https://github.com/ratify-project/ratify/issues) before opening an issue to check whether your feature has already been suggested. If it has, feel free to add your own comments to the existing issue. * Ensure you have included a "What?" - what your feature entails, being as specific as possible, and giving mocked-up syntax examples where possible. * Ensure you have included a "Why?" - what the benefit of including this feature will be. ## Bug Reports -* Please first search [Open Ratify Issues](https://github.com/deislabs/ratify/issues) before opening an issue, to see if it has already been reported. +* Please first search [Open Ratify Issues](https://github.com/ratify-project/ratify/issues) before opening an issue, to see if it has already been reported. * Try to be as specific as possible, including the version of the Ratify CLI used to reproduce the issue, and any example arguments needed to reproduce it. - -## CLA - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit . - -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/KubernetesLocalProcessConfig.yaml b/KubernetesLocalProcessConfig.yaml new file mode 100644 index 000000000..98890ec3f --- /dev/null +++ b/KubernetesLocalProcessConfig.yaml @@ -0,0 +1,15 @@ +version: 0.1 +env: + - name: azure-identity-token # REMOVE if not using Azure Workload Identity + value: $(volumeMounts:azure-identity-token) # REMOVE if not using Azure Workload Identity + - name: client-ca-cert + value: $(volumeMounts:client-ca-cert) + - name: AZURE_FEDERATED_TOKEN_FILE # REMOVE if not using Azure Workload Identity + value: /azure-identity-token # REMOVE if not using Azure Workload Identity + - name: RATIFY_NAMESPACE + value: gatekeeper-system +volumeMounts: + - name: client-ca-cert + localPath: + - name: azure-identity-token # REMOVE if not using Azure Workload Identity + localPath: # REMOVE if not using Azure Workload Identity \ No newline at end of file diff --git a/Makefile b/Makefile index 923edb697..376a2d170 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ BINARY_NAME = ratify INSTALL_DIR = ~/.ratify CERT_DIR = ${GITHUB_WORKSPACE}/tls/certs -GO_PKG = github.com/deislabs/ratify +GO_PKG = github.com/ratify-project/ratify GIT_COMMIT_HASH = $(shell git rev-parse HEAD) GIT_TREE_STATE = $(shell test -n "`git status --porcelain`" && echo "modified" || echo "unmodified") GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) @@ -26,12 +26,12 @@ LDFLAGS += -X $(GO_PKG)/internal/version.GitTreeState=$(GIT_TREE_STATE) LDFLAGS += -X $(GO_PKG)/internal/version.GitTag=$(GIT_TAG) KIND_VERSION ?= 0.22.0 -KUBERNETES_VERSION ?= 1.27.7 -KIND_KUBERNETES_VERSION ?= 1.27.3 -GATEKEEPER_VERSION ?= 3.15.0 +KUBERNETES_VERSION ?= 1.29.2 +KIND_KUBERNETES_VERSION ?= 1.29.2 +GATEKEEPER_VERSION ?= 3.17.0 DAPR_VERSION ?= 1.12.5 COSIGN_VERSION ?= 2.2.3 -NOTATION_VERSION ?= 1.1.0 +NOTATION_VERSION ?= 1.2.0 ORAS_VERSION ?= 1.1.0 HELM_VERSION ?= 3.14.2 @@ -56,12 +56,19 @@ TRIVY_VERSION ?= 0.49.1 GATEKEEPER_NAMESPACE = gatekeeper-system RATIFY_NAME = ratify +TIMESTAMP_URL = http://timestamp.digicert.com + # Local Registry Setup LOCAL_REGISTRY_IMAGE ?= ghcr.io/project-zot/zot-linux-amd64:v2.0.2 TEST_REGISTRY = localhost:5000 TEST_REGISTRY_USERNAME = test_user TEST_REGISTRY_PASSWORD = test_pw +# Azure Key Vault Setup +KEYVAULT_NAME ?= ratify-akv +KEYVAULT_KEY_NAME ?= test-key +AZURE_SP_OBJECT_ID ?= 00000000-0000-0000-0000-000000000000 + all: build test .PHONY: build @@ -70,17 +77,17 @@ build: build-cli build-plugins .PHONY: build-cli build-cli: fmt vet go build --ldflags="$(LDFLAGS)" -cover \ - -coverpkg=github.com/deislabs/ratify/pkg/...,github.com/deislabs/ratify/config/...,github.com/deislabs/ratify/cmd/... \ + -coverpkg=github.com/ratify-project/ratify/pkg/...,github.com/ratify-project/ratify/config/...,github.com/ratify-project/ratify/cmd/... \ -o ./bin/${BINARY_NAME} ./cmd/${BINARY_NAME} .PHONY: build-plugins build-plugins: - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/licensechecker/... -o ./bin/plugins/ ./plugins/verifier/licensechecker - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/sample/... -o ./bin/plugins/ ./plugins/verifier/sample - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/referrerstore/sample/... -o ./bin/plugins/referrerstore/ ./plugins/referrerstore/sample - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/sbom/... -o ./bin/plugins/ ./plugins/verifier/sbom - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/schemavalidator/... -o ./bin/plugins/ ./plugins/verifier/schemavalidator - go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/vulnerabilityreport/... -o ./bin/plugins/ ./plugins/verifier/vulnerabilityreport + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/verifier/licensechecker/... -o ./bin/plugins/ ./plugins/verifier/licensechecker + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/verifier/sample/... -o ./bin/plugins/ ./plugins/verifier/sample + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/referrerstore/sample/... -o ./bin/plugins/referrerstore/ ./plugins/referrerstore/sample + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/verifier/sbom/... -o ./bin/plugins/ ./plugins/verifier/sbom + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/verifier/schemavalidator/... -o ./bin/plugins/ ./plugins/verifier/schemavalidator + go build -cover -coverpkg=github.com/ratify-project/ratify/plugins/verifier/vulnerabilityreport/... -o ./bin/plugins/ ./plugins/verifier/vulnerabilityreport .PHONY: install install: @@ -93,6 +100,7 @@ install: ratify-config: cp ./test/bats/tests/config/* ${INSTALL_DIR} cp ./test/bats/tests/certificates/wabbit-networks.io.crt ${INSTALL_DIR}/ratify-certs/notation/wabbit-networks.io.crt + cp ./test/bats/tests/certificates/tsarootca.cer ${INSTALL_DIR}/ratify-certs/notation/tsarootca.cer cp ./test/bats/tests/certificates/cosign.pub ${INSTALL_DIR}/ratify-certs/cosign/cosign.pub cp -r ./test/bats/tests/schemas/ ${INSTALL_DIR} @@ -121,17 +129,17 @@ delete-ratify: .PHONY: deploy-demo-constraints deploy-demo-constraints: - kubectl apply -f ./library/default/template.yaml - kubectl apply -f ./library/default/samples/constraint.yaml + kubectl apply -f ./library/multi-tenancy-validation/template.yaml + kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml .PHONY: delete-demo-constraints delete-demo-constraints: - kubectl delete -f ./library/default/template.yaml - kubectl delete -f ./library/default/samples/constraint.yaml + kubectl delete -f ./library/multi-tenancy-validation/template.yaml + kubectl delete -f ./library/multi-tenancy-validation/samples/constraint.yaml .PHONY: deploy-rego-policy deploy-rego-policy: - kubectl apply -f ./config/samples/policy/config_v1beta1_policy_rego.yaml + kubectl replace -f ./config/samples/clustered/policy/config_v1beta1_policy_rego.yaml .PHONY: deploy-gatekeeper deploy-gatekeeper: @@ -290,10 +298,16 @@ e2e-notation-setup: ${GITHUB_WORKSPACE}/bin/oras cp --from-oci-layout .staging/notation/notation.tar:v0 ${TEST_REGISTRY}/notation:unsigned rm .staging/notation/notation.tar + printf 'FROM ${ALPINE_IMAGE}\nCMD ["echo", "notation tsa signed image"]' > .staging/notation/Dockerfile + docker buildx create --use + docker buildx build --output type=oci,dest=.staging/notation/notation.tar -t notation:v0 .staging/notation + ${GITHUB_WORKSPACE}/bin/oras cp --from-oci-layout .staging/notation/notation.tar:v0 ${TEST_REGISTRY}/notation:tsa + rm .staging/notation/notation.tar + rm -rf ~/.config/notation .staging/notation/notation cert generate-test --default "ratify-bats-test" - NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign --allow-referrers-api -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/notation@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/notation:signed --descriptor | jq .digest | xargs` + NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign --timestamp-url ${TIMESTAMP_URL} --timestamp-root-cert ./test/bats/tests/certificates/tsarootca.cer --allow-referrers-api -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/notation@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/notation:tsa --descriptor | jq .digest | xargs` NOTATION_EXPERIMENTAL=1 .staging/notation/notation sign --allow-referrers-api -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} ${TEST_REGISTRY}/all@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/all:v0 --descriptor | jq .digest | xargs` e2e-notation-leaf-cert-setup: @@ -340,6 +354,32 @@ e2e-cosign-setup: ./cosign-linux-amd64 sign --allow-insecure-registry --allow-http-registry --tlog-upload=false --key cosign.key ${TEST_REGISTRY}/cosign@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/cosign:signed-key --descriptor | jq .digest | xargs` && \ ./cosign-linux-amd64 sign --allow-insecure-registry --allow-http-registry --tlog-upload=false --key cosign.key ${TEST_REGISTRY}/all@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/all:v0 --descriptor | jq .digest | xargs` +e2e-cosign-akv-setup: + rm -rf .staging/cosign + mkdir -p .staging/cosign + curl -sSLO https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64 + mv cosign-linux-amd64 .staging/cosign + chmod +x .staging/cosign/cosign-linux-amd64 + + # image signed with a key from azure key vault + printf 'FROM ${ALPINE_IMAGE}\nCMD ["echo", "cosign signed akv image"]' > .staging/cosign/Dockerfile + docker buildx create --use + docker buildx build --output type=oci,dest=.staging/cosign/cosign.tar -t cosign:v0 .staging/cosign + ${GITHUB_WORKSPACE}/bin/oras cp --from-oci-layout .staging/cosign/cosign.tar:v0 ${TEST_REGISTRY}/cosign:signed-key + rm .staging/cosign/cosign.tar + + printf 'FROM ${ALPINE_IMAGE}\nCMD ["echo", "cosign unsigned image"]' > .staging/cosign/Dockerfile + docker buildx create --use + docker buildx build --output type=oci,dest=.staging/cosign/cosign.tar -t cosign:v0 .staging/cosign + ${GITHUB_WORKSPACE}/bin/oras cp --from-oci-layout .staging/cosign/cosign.tar:v0 ${TEST_REGISTRY}/cosign:unsigned + rm .staging/cosign/cosign.tar + + export COSIGN_PASSWORD="test" && \ + cd .staging/cosign && \ + ./cosign-linux-amd64 login ${TEST_REGISTRY} -u ${TEST_REGISTRY_USERNAME} -p ${TEST_REGISTRY_PASSWORD} && \ + ./cosign-linux-amd64 sign --allow-insecure-registry --allow-http-registry --tlog-upload=false --key azurekms://${KEYVAULT_NAME}.vault.azure.net/${KEYVAULT_KEY_NAME} ${TEST_REGISTRY}/cosign@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/cosign:signed-key --descriptor | jq .digest | xargs` && \ + ./cosign-linux-amd64 sign --allow-insecure-registry --allow-http-registry --tlog-upload=false --key azurekms://${KEYVAULT_NAME}.vault.azure.net/${KEYVAULT_KEY_NAME} ${TEST_REGISTRY}/all@`${GITHUB_WORKSPACE}/bin/oras manifest fetch ${TEST_REGISTRY}/all:v0 --descriptor | jq .digest | xargs` + e2e-licensechecker-setup: rm -rf .staging/licensechecker mkdir -p .staging/licensechecker @@ -484,48 +524,49 @@ 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-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-schemavalidator-setup e2e-deploy-gatekeeper: e2e-helm-install ./.staging/helm/linux-amd64/helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts - if [ ${GATEKEEPER_VERSION} = "3.13.0" ]; then ./.staging/helm/linux-amd64/helm install gatekeeper/gatekeeper --version ${GATEKEEPER_VERSION} --name-template=gatekeeper --namespace ${GATEKEEPER_NAMESPACE} --create-namespace --set enableExternalData=true --set validatingWebhookTimeoutSeconds=5 --set mutatingWebhookTimeoutSeconds=2 --set auditInterval=0; fi - if [ ${GATEKEEPER_VERSION} = "3.13.0" ]; then kubectl -n ${GATEKEEPER_NAMESPACE} patch deployment gatekeeper-controller-manager --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--external-data-provider-response-cache-ttl=1s"}]' && sleep 60; fi - # Gatekeeper versions >= 3.14.0 need a special helm value to override the default external data response cache ttl to 10s - if [ ${GATEKEEPER_VERSION} != "3.13.0" ]; then ./.staging/helm/linux-amd64/helm install gatekeeper/gatekeeper --version ${GATEKEEPER_VERSION} --name-template=gatekeeper --namespace ${GATEKEEPER_NAMESPACE} --create-namespace --set enableExternalData=true --set validatingWebhookTimeoutSeconds=5 --set mutatingWebhookTimeoutSeconds=2 --set auditInterval=0 --set externaldataProviderResponseCacheTTL=1s; fi + ./.staging/helm/linux-amd64/helm install gatekeeper/gatekeeper --version ${GATEKEEPER_VERSION} --name-template=gatekeeper --namespace ${GATEKEEPER_NAMESPACE} --create-namespace --set enableExternalData=true --set validatingWebhookTimeoutSeconds=5 --set mutatingWebhookTimeoutSeconds=2 --set auditInterval=0 --set externaldataProviderResponseCacheTTL=1s e2e-build-crd-image: - docker build --progress=plain --no-cache --build-arg KUBE_VERSION=${KUBERNETES_VERSION} --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t localbuildcrd:test ./charts/ratify/crds - kind load docker-image --name kind localbuildcrd:test + docker build --progress=plain --no-cache --build-arg KUBE_VERSION=${KUBERNETES_VERSION} --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t localbuildcrd:test ./charts/ratify/crds -e2e-deploy-base-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-inlinecert-setup e2e-build-crd-image - docker build --progress=plain --no-cache \ - -f ./httpserver/Dockerfile \ - -t baselocalbuild:test . - kind load docker-image --name kind baselocalbuild:test +load-build-crd-image: + kind load docker-image --name kind localbuildcrd:test +e2e-deploy-base-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-setup e2e-inlinecert-setup e2e-build-crd-image load-build-crd-image e2e-build-local-ratify-base-image printf "{\n\t\"auths\": {\n\t\t\"registry:5000\": {\n\t\t\t\"auth\": \"`echo "${TEST_REGISTRY_USERNAME}:${TEST_REGISTRY_PASSWORD}" | tr -d '\n' | base64 -i -w 0`\"\n\t\t}\n\t}\n}" > mount_config.json ./.staging/helm/linux-amd64/helm install ${RATIFY_NAME} \ - ./charts/ratify --atomic --namespace ${GATEKEEPER_NAMESPACE} --create-namespace \ - --set image.repository=baselocalbuild \ - --set image.crdRepository=localbuildcrd \ - --set image.tag=test \ - --set gatekeeper.version=${GATEKEEPER_VERSION} \ - --set featureFlags.RATIFY_CERT_ROTATION=${CERT_ROTATION_ENABLED} \ - --set-file provider.tls.crt=${CERT_DIR}/server.crt \ - --set-file provider.tls.key=${CERT_DIR}/server.key \ - --set-file provider.tls.caCert=${CERT_DIR}/ca.crt \ - --set-file provider.tls.caKey=${CERT_DIR}/ca.key \ - --set provider.tls.cabundle="$(shell cat ${CERT_DIR}/ca.crt | base64 | tr -d '\n')" \ - --set notationCerts[0]="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \ - --set oras.useHttp=true \ - --set cosign.enabled=false \ - --set-file dockerConfig="mount_config.json" \ - --set logger.level=debug + ./charts/ratify --atomic --namespace ${GATEKEEPER_NAMESPACE} --create-namespace \ + --set image.repository=baselocalbuild \ + --set image.crdRepository=localbuildcrd \ + --set image.tag=test \ + --set gatekeeper.version=${GATEKEEPER_VERSION} \ + --set featureFlags.RATIFY_CERT_ROTATION=${CERT_ROTATION_ENABLED} \ + --set-file provider.tls.crt=${CERT_DIR}/server.crt \ + --set-file provider.tls.key=${CERT_DIR}/server.key \ + --set-file provider.tls.caCert=${CERT_DIR}/ca.crt \ + --set-file provider.tls.caKey=${CERT_DIR}/ca.key \ + --set provider.tls.cabundle="$(shell cat ${CERT_DIR}/ca.crt | base64 | tr -d '\n')" \ + --set notationCerts[0]="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \ + --set cosignKeys[0]="$$(cat .staging/cosign/cosign.pub)" \ + --set cosign.key="$$(cat .staging/cosign/cosign.pub)" \ + --set oras.useHttp=true \ + --set-file dockerConfig="mount_config.json" \ + --set logger.level=debug 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 e2e-build-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-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 \ + -f ./httpserver/Dockerfile \ + -t baselocalbuild:test . + kind load docker-image --name kind baselocalbuild:test e2e-build-local-ratify-image: docker build --progress=plain --no-cache \ @@ -535,10 +576,12 @@ e2e-build-local-ratify-image: --build-arg build_vulnerabilityreport=true \ -f ./httpserver/Dockerfile \ -t localbuild:test . + +load-local-ratify-image: kind load docker-image --name kind localbuild:test e2e-helmfile-deploy-released-ratify: - ./.staging/helmfilebin/helmfile sync -f git::https://github.com/deislabs/ratify.git@helmfile.yaml + ./.staging/helmfilebin/helmfile sync -f git::https://github.com/ratify-project/ratify.git@helmfile.yaml e2e-helm-deploy-ratify: printf "{\n\t\"auths\": {\n\t\t\"registry:5000\": {\n\t\t\t\"auth\": \"`echo "${TEST_REGISTRY_USERNAME}:${TEST_REGISTRY_PASSWORD}" | tr -d '\n' | base64 -i -w 0`\"\n\t\t}\n\t}\n}" > mount_config.json @@ -556,7 +599,9 @@ e2e-helm-deploy-ratify: --set-file provider.tls.caKey=${CERT_DIR}/ca.key \ --set provider.tls.cabundle="$(shell cat ${CERT_DIR}/ca.crt | base64 | tr -d '\n')" \ --set notationCerts[0]="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \ + --set cosignKeys[0]="$$(cat .staging/cosign/cosign.pub)" \ --set cosign.key="$$(cat .staging/cosign/cosign.pub)" \ + --set cosign.tLogVerify=false \ --set oras.useHttp=true \ --set-file dockerConfig="mount_config.json" \ --set logger.level=debug @@ -573,8 +618,10 @@ e2e-helm-deploy-ratify-without-tls-certs: --set image.tag=test \ --set gatekeeper.version=${GATEKEEPER_VERSION} \ --set featureFlags.RATIFY_CERT_ROTATION=${CERT_ROTATION_ENABLED} \ - --set notaryCert="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \ + --set notationCerts[0]="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \ --set cosign.key="$$(cat .staging/cosign/cosign.pub)" \ + --set cosignKeys[0]="$$(cat .staging/cosign/cosign.pub)" \ + --set cosign.tLogVerify=false \ --set oras.useHttp=true \ --set-file dockerConfig="mount_config.json" \ --set logger.level=debug @@ -598,7 +645,7 @@ e2e-helm-deploy-redis: e2e-helm-deploy-dapr kubectl apply -f test/testdata/dapr/dapr-redis-secret.yaml -n ${GATEKEEPER_NAMESPACE} kubectl apply -f test/testdata/dapr/dapr-redis.yaml -n ${GATEKEEPER_NAMESPACE} -e2e-helm-deploy-ratify-replica: e2e-helm-deploy-redis e2e-notation-setup e2e-build-crd-image e2e-build-local-ratify-image +e2e-helm-deploy-ratify-replica: e2e-helm-deploy-redis e2e-notation-setup e2e-build-crd-image load-build-crd-image e2e-build-local-ratify-image load-local-ratify-image printf "{\n\t\"auths\": {\n\t\t\"registry:5000\": {\n\t\t\t\"auth\": \"`echo "${TEST_REGISTRY_USERNAME}:${TEST_REGISTRY_PASSWORD}" | tr -d '\n' | base64 -i -w 0`\"\n\t\t}\n\t}\n}" > mount_config.json ./.staging/helm/linux-amd64/helm install ${RATIFY_NAME} \ @@ -628,7 +675,7 @@ e2e-helm-deploy-ratify-replica: e2e-helm-deploy-redis e2e-notation-setup e2e-bui rm mount_config.json e2e-aks: - ./scripts/azure-ci-test.sh ${KUBERNETES_VERSION} ${GATEKEEPER_VERSION} ${TENANT_ID} ${GATEKEEPER_NAMESPACE} ${CERT_DIR} + ./scripts/azure-ci-test.sh ${KUBERNETES_VERSION} ${GATEKEEPER_VERSION} ${TENANT_ID} ${GATEKEEPER_NAMESPACE} ${CERT_DIR} ${AZURE_SP_OBJECT_ID} e2e-cleanup: ./scripts/azure-ci-test-cleanup.sh ${AZURE_SUBSCRIPTION_ID} @@ -642,9 +689,10 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust generate: controller-gen conversion-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. Also generate conversions between structs of different API versions. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." $(CONVERSION_GEN) \ - --input-dirs "./api/v1beta1,./api/v1alpha1" \ --go-header-file "./hack/boilerplate.go.txt" \ - --output-file-base "zz_generated.conversion" + --output-file "zz_generated.conversion.go" \ + ./api/v1beta1 ./api/v1alpha1 + .PHONY: fmt fmt: ## Run go fmt against code. @@ -692,8 +740,8 @@ CONVERSION_GEN ?= $(LOCALBIN)/conversion-gen ## Tool Versions KUSTOMIZE_VERSION ?= v3.8.7 -CONTROLLER_TOOLS_VERSION ?= v0.9.2 -CONVERSION_TOOLS_VERSION ?= v0.26.1 +CONTROLLER_TOOLS_VERSION ?= v0.15.0 +CONVERSION_TOOLS_VERSION ?= v0.30.2 KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize diff --git a/PROJECT b/PROJECT index 75078fb96..5841a8f99 100644 --- a/PROJECT +++ b/PROJECT @@ -80,4 +80,36 @@ resources: kind: KeyManagementProvider path: github.com/deislabs/ratify/api/v1beta1 version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedPolicy + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedStore + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedKeyManagementProvider + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + domain: ratify.deislabs.io + group: config + kind: NamespacedVerifier + path: github.com/deislabs/ratify/api/v1beta1 + version: v1beta1 version: "3" diff --git a/README.md b/README.md index fbb1a1978..c58271ebe 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,12 @@ Is a verification engine as a binary executable and on Kubernetes which enables verification of artifact security metadata and admits for deployment only those that comply with policies you create. -[![Go Report Card](https://goreportcard.com/badge/github.com/deislabs/ratify)](https://goreportcard.com/report/github.com/deislabs/ratify) -[![build-pr](https://github.com/deislabs/ratify/actions/workflows/build-pr.yml/badge.svg)](https://github.com/deislabs/ratify/actions/workflows/build-pr.yml) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/deislabs/ratify/badge)](https://api.securityscorecards.dev/projects/github.com/deislabs/ratify) -[![Go Reference](https://pkg.go.dev/badge/github.com/deislabs/ratify.svg)](https://pkg.go.dev/github.com/deislabs/ratify) +[![Go Report Card](https://goreportcard.com/badge/github.com/ratify-project/ratify)](https://goreportcard.com/report/github.com/ratify-project/ratify) +[![build-pr](https://github.com/ratify-project/ratify/actions/workflows/build-pr.yml/badge.svg)](https://github.com/ratify-project/ratify/actions/workflows/build-pr.yml) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ratify-project/ratify/badge)](https://api.securityscorecards.dev/projects/github.com/ratify-project/ratify) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9334/badge)](https://www.bestpractices.dev/projects/9334) +[![Go Reference](https://pkg.go.dev/badge/github.com/ratify-project/ratify.svg)](https://pkg.go.dev/github.com/ratify-project/ratify) +[![codecov](https://codecov.io/gh/ratify-project/ratify/graph/badge.svg?token=3X0BIPI4VD)](https://codecov.io/gh/ratify-project/ratify) ## Table of Contents @@ -20,6 +22,7 @@ Is a verification engine as a binary executable and on Kubernetes which enables - [Pull Request Review Series](#pull-request-review-series) - [Documents](#documents) - [Code of Conduct](#code-of-conduct) + - [Project Governance](#project-governance) - [Release Management](#release-management) - [Licensing](#licensing) - [Trademark](#trademark) @@ -43,19 +46,19 @@ Get Ratify Community Meeting Calendar [here](https://calendar.google.com/calenda ## Documents -Please see the [Ratify website](https://ratify.dev/docs/what-is-ratify) for more in-depth information. +Please see the [Ratify website](https://ratify.dev/docs/what-is-ratify) for more in-depth information. -Meeting notes for weekly project syncs can be found [here](https://hackmd.io/ABueHjizRz2iFQpWnQrnNA?both) +Meeting notes for weekly project syncs can be found [here](https://hackmd.io/ABueHjizRz2iFQpWnQrnNA?both). + +The Ratify community documents can be found in the repository [`.github`](https://github.com/ratify-project/.github). ## Code of Conduct -This project has adopted the [Microsoft Open Source Code of -Conduct](https://opensource.microsoft.com/codeofconduct/). +Ratify follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). + +## Project Governance -For more information see the [Code of Conduct -FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact -[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional -questions or comments. +The Ratify project governance can be found [here](https://github.com/ratify-project/.github/blob/main/GOVERNANCE.md). ## Release Management diff --git a/RELEASES.md b/RELEASES.md index 6487a911c..bbf03b1ac 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ The specification release process was created using content and verbiage from th * [ORAS Artifact Specification Releases](https://github.com/oras-project/artifacts-spec/blob/main/RELEASES.md) * [ORAS Developer Guide](https://github.com/oras-project/oras-www/blob/main/docs/CLI/5_developer_guide.md) * [Mystikos Release Management](https://github.com/deislabs/mystikos/blob/main/doc/releasing.md) +* [Gatekeeper Release Management](https://github.com/open-policy-agent/gatekeeper/blob/8f5201f0f48d50cc14153d100172689f03aa5f39/docs/Release_Management.md) ## Versioning @@ -26,9 +27,9 @@ Example pre-release versions include `v0.1.0-alpha1`, `v0.1.0-beta2`, `v0.1.0-rc 2. If the format of the data returned for [external data calls](docs/reference/verification-result-version.md) has changed, validate change is also reflected in [`httpserver/types.go`](httpserver/types.go). -3. Delete all dev images generated since the previous release under the `ratify-dev` and `ratify-crds-dev` [packages](https://github.com/orgs/deislabs/packages?repo_name=ratify). Each dev image tag is prefixed with `dev` followed by the date of creation and then the abbreviated 7 character commit SHA (e.g a build generated on March 8, 2023 from main branch with commit SHA `4cf98388ef33c587ef86b82e05cb0f7de2da2ea8` would be tagged `dev.20230308.4cf9838`). The most recent images are also tagged with a rolling tag `latest`. +3. Delete all dev images generated since the previous release under the `ratify-dev` and `ratify-crds-dev` [packages](https://github.com/orgs/ratify-project/packages?repo_name=ratify). Each dev image tag is prefixed with `dev` followed by the date of creation and then the abbreviated 7 character commit SHA (e.g a build generated on March 8, 2023 from main branch with commit SHA `4cf98388ef33c587ef86b82e05cb0f7de2da2ea8` would be tagged `dev.20230308.4cf9838`). The most recent images are also tagged with a rolling tag `latest`. -4. Delete all dev helm charts since the previous release under the `ratify-chart-dev/ratify` [packages](https://github.com/orgs/deislabs/packages?repo_name=ratify). Each helm chart is published with a semantic version compatible tag `0-dev` followed by the date of creation and then the abbreviated 7 character commit SHA (e.g a chart generated on March 8, 2023 from main branch with commit SHA `4cf98388ef33c587ef86b82e05cb0f7de2da2ea8` would be tagged `0-dev.20230308.4cf9838`). The most recent dev chart is also tagged with the rolling tag `0-dev`. +4. Delete all dev helm charts since the previous release under the `ratify-chart-dev/ratify` [packages](https://github.com/orgs/ratify-project/packages?repo_name=ratify). Each helm chart is published with a semantic version compatible tag `0-dev` followed by the date of creation and then the abbreviated 7 character commit SHA (e.g a chart generated on March 8, 2023 from main branch with commit SHA `4cf98388ef33c587ef86b82e05cb0f7de2da2ea8` would be tagged `0-dev.20230308.4cf9838`). The most recent dev chart is also tagged with the rolling tag `0-dev`. 5. Copy contents from [`dev.helmfile.yaml`](dev.helmfile.yaml) to [`helmfile.yaml`](helmfile.yaml) & [`dev.high-availability.helmfile.yaml`](dev.high-availability.helmfile.yaml) to [`high-availability.helmfile.yaml`](high-availability.helmfile.yaml). You MUST update/remove values marked by comments in the files. The `dev` prefixed helmfiles are treated as staging files that are up to date with new changes on main branch. The primary `helmfile.yaml` and `high-availability.helmfile.yaml` MUST stay pinned to the current release since they are used by the quickstarts. Update `dev.helmfile.yaml` & `dev.high-availability.helmfile.yaml` ratify chart version to new release version. @@ -36,21 +37,26 @@ Example pre-release versions include `v0.1.0-alpha1`, `v0.1.0-beta2`, `v0.1.0-rc This section deals with the practical considerations of versioning in Git, this repo's version control system. See the semantic versioning specification for the scope of changes allowed for each release type. +All releases will be of the form _vX.Y.Z_ where X is the major version, Y is the minor version and Z is the patch version. + ### Patch releases -When a patch release is required, the patch commits should be merged with the `main` branch when ready. Then a new branch should be created with the patch version incremented and optional pre-release specifiers. For example if the previous release was `v0.1.0`, the branch should be named `v0.1.1` and can optionally be suffixed with a pre-release (e.g. `v0.1.1-rc1`). The limited nature of fixes in a patch release should mean pre-releases can often be omitted. +Applicable fixes, including security fixes, may be backported to supported releases, depending on severity and feasibility. Patch release are cut from branch `release-X.Y`. Commits can be cherry-picked from `main`, changes should be merged into latest supported minor release-X.Y branches once required PR requirements are met. ### Minor releases -When a minor release is required, the release commits should be merged with the `main` branch when ready. Then a new branch should be created with the minor version incremented and optional pre-release specifiers. For example if the previous release was `v0.1.1`, the branch should be named `v0.2.0` and can optionally be suffixed with a pre-release (e.g. `v0.2.0-beta1`). Pre-releases will be more common will be more common with minor releases. +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 ### Major releases -When a major release is required, the release commits should be merged with the `main` branch when ready. Then a new branch should be created with the major version incremented and optional pre-release specifiers. For example if the previous release was `v1.1.1`, the branch should be named `v2.0.0` and can optionally be suffixed with a pre-release (e.g. `v2.0.0-alpha1`). Major versions will usually require multiple pre-release versions. +When a major release is required, the release commits should be merged with the `main` branch when ready. Major versions will usually require multiple pre-release versions. Similar to minor releases, the new branch should be created for the RC and stable release. ### Tag and Release -Prepare the release with a [PR](https://github.com/deislabs/ratify/pull/1031/files) to update the chart value. When the release branch is ready, a tag should be pushed with a name matching the branch name, e.g. `git tag v0.1.0-alpha1` 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. 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/): * 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. @@ -58,6 +64,18 @@ Prepare the release with a [PR](https://github.com/deislabs/ratify/pull/1031/fil * The pre-built binaries are built from commit at the head of the release branch. * The files are named `ratify_--__` with `.zip` files for Windows and `.tar.gz` for all others. +## Supported Releases + +Applicable fixes, including security fixes, may be cherry-picked into the release branch, depending on severity and feasibility. Patch releases are cut from that branch as needed. + +We expect to "support" n (current). "Support" means we expect users to be running that version in production. For example, when v1.2 comes out, v1.1 will no longer be supported for patches, and we encourage users to upgrade to a supported version as soon as possible. + +## Supported Kubernetes and Gatekeeper Versions + +Ratify is assumed to be compatible with [GateKeeper Supported Versions](https://github.com/open-policy-agent/gatekeeper/blob/master/docs/Release_Management.md#supported-releases) and the [current Kubernetes Supported Versions](https://kubernetes.io/releases/patch-releases/#detailed-release-history-for-active-branches) per [Kubernetes Supported Versions policy](https://kubernetes.io/releases/version-skew-policy/). + +For example, if Gatekeeper _supported_ versions are v3.13 and v3.14, and Kubernetes _supported_ versions are v1.28, v1.29, then current version of Ratify (v1.2) are assumed to be compatible with all supported Kubernetes versions (v1.28, v1.29) and Gatekeeper version(v3.13, v3.14). + ## 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. @@ -65,29 +83,34 @@ After a successful release, please manually trigger [quick start action](.github ### Weekly Dev Release #### Publishing Guidelines -- Ratify is configured to generate and publish dev build images based on the schedule [here](https://github.com/deislabs/ratify/blob/main/.github/workflows/publish-package.yml#L8). -- Contributors MUST select the `Helm Chart Change` option under the `Type of Change` section if there is ANY update to the helm chart that is required for proposed changes in PR. -- Maintainers MUST manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change` - - Go to the `Actions` tab for the Ratify repository - - Select `publish-ghcr` option from list of workflows on left pane - - Select the `Run workflow` drop down on the right side above the list of action runs - - Choose `Branch: main` - - Select `Run workflow` -- Process to Request an off-schedule dev build be published - - Submit a new feature request issue prefixed with `[Dev Build Request]` - - In the the `What this PR does / why we need it` section, briefly explain why an off schedule build is needed - - Once issue is created, post in the `#ratify` slack channel and tag the maintainers - - Maintainers should acknowledge request by approving/denying request as a follow up comment + +* Ratify is configured to generate and publish dev build images based on the schedule [here](https://github.com/ratify-project/ratify/blob/main/.github/workflows/publish-package.yml#L8). +* Contributors MUST select the `Helm Chart Change` option under the `Type of Change` section if there is ANY update to the helm chart that is required for proposed changes in PR. +* Maintainers MUST manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change` + * Go to the `Actions` tab for the Ratify repository + * Select `publish-ghcr` option from list of workflows on left pane + * Select the `Run workflow` drop down on the right side above the list of action runs + * Choose `Branch: main` + * Select `Run workflow` +* Process to Request an off-schedule dev build be published + * Submit a new feature request issue prefixed with `[Dev Build Request]` + * In the the `What this PR does / why we need it` section, briefly explain why an off schedule build is needed + * Once issue is created, post in the `#ratify` slack channel and tag the maintainers + * Maintainers should acknowledge request by approving/denying request as a follow up comment + #### How to use a dev build -1. The `ratify` image and `ratify-crds` image for dev builds exist as separate packages on Github [here](https://github.com/deislabs/ratify/pkgs/container/ratify-dev) and [here](https://github.com/deislabs/ratify/pkgs/container/ratify-crds-dev). + +1. The `ratify` image and `ratify-crds` image for dev builds exist as separate packages on Github [here](https://github.com/ratify-project/ratify/pkgs/container/ratify-dev) and [here](https://github.com/ratify-project/ratify/pkgs/container/ratify-crds-dev). 2. the `repository` `crdRepository` and `tag` fields must be updated in the helm chart to point to dev build instead of last released build. Please set the tag to be latest tag found at the corresponding `-dev` suffixed package. An example install command scaffold: -``` + +```bash helm install ratify \ ./charts/ratify --atomic \ --namespace gatekeeper-system \ - --set image.repository=ghcr.io/deislabs/ratify-dev - --set image.crdRepository=ghcr.io/deislabs/ratify-crds-dev + --set image.repository=ghcr.io/ratify-project/ratify-dev + --set image.crdRepository=ghcr.io/ratify-project/ratify-crds-dev --set image.tag=dev.. - --set-file notationCert=./test/testdata/notation.crt + --set-file notationCerts[0]=./test/testdata/notation.crt ``` -NOTE: the tag field is the only value that will change when updating to newer dev build images \ No newline at end of file + +NOTE: the tag field is the only value that will change when updating to newer dev build images diff --git a/ROADMAP.md b/ROADMAP.md index 5ae2a0368..b98c8f72f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -10,7 +10,7 @@ This document presents the roadmap of Ratify that translates our strategy into p ## Milestones -The Ratify roadmap is divided into milestones, each with a set of features (high level) and timeline. The milestones marked as `Tentative` are subject to change based on the project’s priorities and the community’s feedback. We will prioritize releases for security or urgent fixes, so the roadmap may be adjusted and new features may be postponed to the next milestone. Any dates and features listed below in a given milestone are subject to change. See the [GitHub milestones](https://github.com/deislabs/ratify/milestones?state=open) for the most up-to-date issues and their status. We are targeting to release a new Ratify version every 3 or 4 months. +The Ratify roadmap is divided into milestones, each with a set of features (high level) and timeline. The milestones marked as `Tentative` are subject to change based on the project’s priorities and the community’s feedback. We will prioritize releases for security or urgent fixes, so the roadmap may be adjusted and new features may be postponed to the next milestone. Any dates and features listed below in a given milestone are subject to change. See the [GitHub milestones](https://github.com/ratify-project/ratify/milestones?state=open) for the most up-to-date issues and their status. We are targeting to release a new Ratify version every 3 or 4 months. ### v1.0 @@ -18,7 +18,7 @@ The Ratify roadmap is divided into milestones, each with a set of features (high **Released date**: Sep 27, 2023 -**Release link**: [v1.0.0 Release Notes](https://github.com/deislabs/ratify/releases/tag/v1.0.0) +**Release link**: [v1.0.0 Release Notes](https://github.com/ratify-project/ratify/releases/tag/v1.0.0) **Major features** @@ -34,7 +34,7 @@ The Ratify roadmap is divided into milestones, each with a set of features (high **Release date**: Dec 12, 2023 -**Release link**: [v1.1.0 Release Notes](https://github.com/deislabs/ratify/releases/tag/v1.1.0) +**Release link**: [v1.1.0 Release Notes](https://github.com/ratify-project/ratify/releases/tag/v1.1.0) **Major features** @@ -44,43 +44,46 @@ The Ratify roadmap is divided into milestones, each with a set of features (high ### v1.2 -**Status**: In progress +**Status**: Completed + +**Target date**: May 31, 2024 -**Target date**: Apr 30, 2024 +**Release link**: [v1.2.0 Release Notes](https://github.com/ratify-project/ratify/releases/tag/v1.2.0) **major features** - Kubernetes multi-tenancy support (Namespace-specific policies) - OCI v1.1 compliance - Cosign signatures verification using keys in AKV -- Error logs improvements -See details in [GitHub milestone v1.2.0](https://github.com/deislabs/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.2.0). +See details in [GitHub milestone v1.2.0](https://github.com/ratify-project/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.2.0). ### v1.3 -**Status**: Not started +**Status**: In progress -**Target date**: Jun 30, 2024 +**Target date**: Aug 30, 2024 **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 -See details in [GitHub milestone v1.3.0](https://github.com/deislabs/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.3.0). +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 -**Target date**: Sep 30, 2024 +**Target date**: Nov 30, 2024 **Major features** - Attestations support +- Ratify supports Azure Trusted Signing as a new KeyManagementProvider - Use Ratify at container runtime (Preview) ### v2.0 diff --git a/SECURITY.md b/SECURITY.md index 42825fdd0..0d83f96a7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,41 +1,12 @@ - +# Ratify Project Security Process and Policy +This document provide details on the Ratify Project security policy and details the process on how to report a security vulnerability within the Ratify Project organization. -# Security +## Reporting a Vulnerability -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). +We're extremely grateful for security researchers and users who report vulnerabilities to the Ratify Project community. All reports are thouroughly investigated by a set of Project maintainers. -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. +To make a report plese use the GitHub Security Vulnerability Disclosure process for each one of the Ratify Project repositories. +- [Ratify Vulnerability Report](https://github.com/ratify-project/ratify/security/advisories/new) -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/msrc/pgp-key-msrc). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - -* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) -* Full paths of source file(s) related to the manifestation of the issue -* The location of the affected source code (tag/branch/commit or direct URL) -* Any special configuration required to reproduce the issue -* Step-by-step instructions to reproduce the issue -* Proof-of-concept or exploit code (if possible) -* Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/msrc/cvd). - - \ No newline at end of file +## Credits +We would like to give credit to the [Helm Community](https://github.com/helm/community) for using their security process and policy as an example. diff --git a/api/unversioned/keymanagementprovider_types.go b/api/unversioned/keymanagementprovider_types.go index 9b3a77db9..b347b9692 100644 --- a/api/unversioned/keymanagementprovider_types.go +++ b/api/unversioned/keymanagementprovider_types.go @@ -32,6 +32,10 @@ type KeyManagementProviderSpec struct { // Name of the key management provider Type string `json:"type,omitempty"` + // Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + // Parameters of the key management provider Parameters runtime.RawExtension `json:"parameters,omitempty"` } diff --git a/api/unversioned/namespacedkeymanagementprovider_types.go b/api/unversioned/namespacedkeymanagementprovider_types.go new file mode 100644 index 000000000..cdccfe7f1 --- /dev/null +++ b/api/unversioned/namespacedkeymanagementprovider_types.go @@ -0,0 +1,80 @@ +/* +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. +*/ + +// +kubebuilder:skip +package unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedKeyManagementProviderSpec defines the desired state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the key management provider + Type string `json:"type,omitempty"` + + // Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the key management provider + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedKeyManagementProviderStatus defines the observed state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in loading certificate/key files + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` + // The time stamp of last successful certificate/key fetch operation. If operation failed, last fetched time shows the time of error + // +optional + LastFetchedTime *metav1.Time `json:"lastfetchedtime,omitempty"` + // provider specific properties of the each individual certificate/key + // +optional + Properties runtime.RawExtension `json:"properties,omitempty"` +} + +// NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders API +type NamespacedKeyManagementProvider struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedKeyManagementProviderSpec `json:"spec,omitempty"` + Status NamespacedKeyManagementProviderStatus `json:"status,omitempty"` +} + +// NamespacedKeyManagementProviderList contains a list of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedKeyManagementProvider `json:"items"` +} diff --git a/api/unversioned/namespacedpolicy_types.go b/api/unversioned/namespacedpolicy_types.go new file mode 100644 index 000000000..606fc6b59 --- /dev/null +++ b/api/unversioned/namespacedpolicy_types.go @@ -0,0 +1,63 @@ +/* +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 unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// NamespacedPolicySpec defines the desired state of Policy +type NamespacedPolicySpec struct { + // Important: Run "make" to regenerate code after modifying this file + + // Type of the policy + Type string `json:"type,omitempty"` + // Parameters for this policy + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedPolicyStatus defines the observed state of Policy +type NamespacedPolicyStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful while applying the policy. + IsSuccess bool `json:"issuccess"` + // Error message if NamespacedPolicy is not successfully applied. + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// NamespacedPolicy is the Schema for the policies API +type NamespacedPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedPolicySpec `json:"spec,omitempty"` + Status NamespacedPolicyStatus `json:"status,omitempty"` +} + +// NamespacedPolicyList contains a list of Policy +type NamespacedPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedPolicy `json:"items"` +} diff --git a/api/unversioned/namespacedstore_types.go b/api/unversioned/namespacedstore_types.go new file mode 100644 index 000000000..c918da3c8 --- /dev/null +++ b/api/unversioned/namespacedstore_types.go @@ -0,0 +1,69 @@ +/* +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 unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// NamespacedStoreSpec defines the desired state of NamespacedStore +type NamespacedStoreSpec struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Name of the store + Name string `json:"name"` + // Version of the store plugin. Optional + Version string `json:"version,omitempty"` + // Plugin path, optional + Address string `json:"address,omitempty"` + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // Parameters of the store + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedStoreStatus defines the observed state of NamespacedStore +type NamespacedStoreStatus struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// NamespacedStore is the Schema for the namespacedstores API +type NamespacedStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedStoreSpec `json:"spec,omitempty"` + Status NamespacedStoreStatus `json:"status,omitempty"` +} + +// NamespacedStoreList contains a list of NamespacedStore +type NamespacedStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedStore `json:"items"` +} diff --git a/api/unversioned/namespacedverifier_types.go b/api/unversioned/namespacedverifier_types.go new file mode 100644 index 000000000..994e9d1f8 --- /dev/null +++ b/api/unversioned/namespacedverifier_types.go @@ -0,0 +1,81 @@ +/* +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. +*/ + +// +kubebuilder:skip +package unversioned + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// NamespacedVerifierSpec defines the desired state of NamespacedVerifier +type NamespacedVerifierSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the verifier. Deprecated + Name string `json:"name"` + + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + + // Version of the verifier plugin. Optional + Version string `json:"version,omitempty"` + + // The type of artifact this verifier handles + ArtifactTypes string `json:"artifactTypes"` + + // URL/file path. Optional + Address string `json:"address,omitempty"` + + // OCI Artifact source to download the plugin from. Optional + Source *PluginSource `json:"source,omitempty"` + + // Parameters for this verifier + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedVerifierStatus defines the observed state of NamespacedVerifier +type NamespacedVerifierStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// NamespacedVerifier is the Schema for the namespacedverifiers API +type NamespacedVerifier struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedVerifierSpec `json:"spec,omitempty"` + Status NamespacedVerifierStatus `json:"status,omitempty"` +} + +// NamespacedVerifierList contains a list of NamespacedVerifier +type NamespacedVerifierList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedVerifier `json:"items"` +} diff --git a/api/unversioned/verifier_types.go b/api/unversioned/verifier_types.go index 74b8bdf73..fbdd69b43 100644 --- a/api/unversioned/verifier_types.go +++ b/api/unversioned/verifier_types.go @@ -26,19 +26,22 @@ import ( type VerifierSpec struct { // Important: Run "make" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name,omitempty"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes,omitempty"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // Parameters for this verifier diff --git a/api/unversioned/zz_generated.deepcopy.go b/api/unversioned/zz_generated.deepcopy.go index 433353d89..6b619b4e9 100644 --- a/api/unversioned/zz_generated.deepcopy.go +++ b/api/unversioned/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright The Ratify Authors. @@ -181,6 +180,317 @@ func (in *KeyManagementProviderStatus) DeepCopy() *KeyManagementProviderStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProvider) DeepCopyInto(out *NamespacedKeyManagementProvider) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProvider. +func (in *NamespacedKeyManagementProvider) DeepCopy() *NamespacedKeyManagementProvider { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderList) DeepCopyInto(out *NamespacedKeyManagementProviderList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedKeyManagementProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderList. +func (in *NamespacedKeyManagementProviderList) DeepCopy() *NamespacedKeyManagementProviderList { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderSpec) DeepCopyInto(out *NamespacedKeyManagementProviderSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderSpec. +func (in *NamespacedKeyManagementProviderSpec) DeepCopy() *NamespacedKeyManagementProviderSpec { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderStatus) DeepCopyInto(out *NamespacedKeyManagementProviderStatus) { + *out = *in + if in.LastFetchedTime != nil { + in, out := &in.LastFetchedTime, &out.LastFetchedTime + *out = (*in).DeepCopy() + } + in.Properties.DeepCopyInto(&out.Properties) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderStatus. +func (in *NamespacedKeyManagementProviderStatus) DeepCopy() *NamespacedKeyManagementProviderStatus { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicy) DeepCopyInto(out *NamespacedPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicy. +func (in *NamespacedPolicy) DeepCopy() *NamespacedPolicy { + if in == nil { + return nil + } + out := new(NamespacedPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyList) DeepCopyInto(out *NamespacedPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyList. +func (in *NamespacedPolicyList) DeepCopy() *NamespacedPolicyList { + if in == nil { + return nil + } + out := new(NamespacedPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicySpec) DeepCopyInto(out *NamespacedPolicySpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicySpec. +func (in *NamespacedPolicySpec) DeepCopy() *NamespacedPolicySpec { + if in == nil { + return nil + } + out := new(NamespacedPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyStatus) DeepCopyInto(out *NamespacedPolicyStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyStatus. +func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { + if in == nil { + return nil + } + out := new(NamespacedPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStore) DeepCopyInto(out *NamespacedStore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStore. +func (in *NamespacedStore) DeepCopy() *NamespacedStore { + if in == nil { + return nil + } + out := new(NamespacedStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreList) DeepCopyInto(out *NamespacedStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreList. +func (in *NamespacedStoreList) DeepCopy() *NamespacedStoreList { + if in == nil { + return nil + } + out := new(NamespacedStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreSpec) DeepCopyInto(out *NamespacedStoreSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreSpec. +func (in *NamespacedStoreSpec) DeepCopy() *NamespacedStoreSpec { + if in == nil { + return nil + } + out := new(NamespacedStoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreStatus) DeepCopyInto(out *NamespacedStoreStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreStatus. +func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { + if in == nil { + return nil + } + out := new(NamespacedStoreStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifier) DeepCopyInto(out *NamespacedVerifier) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifier. +func (in *NamespacedVerifier) DeepCopy() *NamespacedVerifier { + if in == nil { + return nil + } + out := new(NamespacedVerifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierList) DeepCopyInto(out *NamespacedVerifierList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedVerifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierList. +func (in *NamespacedVerifierList) DeepCopy() *NamespacedVerifierList { + if in == nil { + return nil + } + out := new(NamespacedVerifierList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierSpec) DeepCopyInto(out *NamespacedVerifierSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierSpec. +func (in *NamespacedVerifierSpec) DeepCopy() *NamespacedVerifierSpec { + if in == nil { + return nil + } + out := new(NamespacedVerifierSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierStatus) DeepCopyInto(out *NamespacedVerifierStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierStatus. +func (in *NamespacedVerifierStatus) DeepCopy() *NamespacedVerifierStatus { + if in == nil { + return nil + } + out := new(NamespacedVerifierStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginSource) DeepCopyInto(out *PluginSource) { *out = *in diff --git a/api/v1alpha1/certificatestore_conversion.go b/api/v1alpha1/certificatestore_conversion.go index 8fc433a24..76e9cc876 100644 --- a/api/v1alpha1/certificatestore_conversion.go +++ b/api/v1alpha1/certificatestore_conversion.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 import ( - unversioned "github.com/deislabs/ratify/api/unversioned" + unversioned "github.com/ratify-project/ratify/api/unversioned" conversion "k8s.io/apimachinery/pkg/conversion" ) diff --git a/api/v1alpha1/doc.go b/api/v1alpha1/doc.go index 5f12afe87..dd50563c2 100644 --- a/api/v1alpha1/doc.go +++ b/api/v1alpha1/doc.go @@ -14,5 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/deislabs/ratify/api/unversioned +// +k8s:conversion-gen=github.com/ratify-project/ratify/api/unversioned package v1alpha1 diff --git a/api/v1alpha1/policy_conversion.go b/api/v1alpha1/policy_conversion.go index b67653eb4..4f406de60 100644 --- a/api/v1alpha1/policy_conversion.go +++ b/api/v1alpha1/policy_conversion.go @@ -17,8 +17,8 @@ limitations under the License. package v1alpha1 import ( - unversioned "github.com/deislabs/ratify/api/unversioned" - "github.com/deislabs/ratify/internal/constants" + unversioned "github.com/ratify-project/ratify/api/unversioned" + "github.com/ratify-project/ratify/internal/constants" conversion "k8s.io/apimachinery/pkg/conversion" ) diff --git a/api/v1alpha1/policy_conversion_test.go b/api/v1alpha1/policy_conversion_test.go index 862e16c63..8a6dba65c 100644 --- a/api/v1alpha1/policy_conversion_test.go +++ b/api/v1alpha1/policy_conversion_test.go @@ -20,8 +20,8 @@ import ( "reflect" "testing" - unversioned "github.com/deislabs/ratify/api/unversioned" - "github.com/deislabs/ratify/internal/constants" + unversioned "github.com/ratify-project/ratify/api/unversioned" + "github.com/ratify-project/ratify/internal/constants" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha1/store_conversion.go b/api/v1alpha1/store_conversion.go index ca8ec8b7e..ddf9ba309 100644 --- a/api/v1alpha1/store_conversion.go +++ b/api/v1alpha1/store_conversion.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 import ( - unversioned "github.com/deislabs/ratify/api/unversioned" + unversioned "github.com/ratify-project/ratify/api/unversioned" conversion "k8s.io/apimachinery/pkg/conversion" ) diff --git a/api/v1alpha1/verifier_conversion.go b/api/v1alpha1/verifier_conversion.go index 77d185f06..257709118 100644 --- a/api/v1alpha1/verifier_conversion.go +++ b/api/v1alpha1/verifier_conversion.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 import ( - unversioned "github.com/deislabs/ratify/api/unversioned" + unversioned "github.com/ratify-project/ratify/api/unversioned" conversion "k8s.io/apimachinery/pkg/conversion" ) diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index d0311a1fb..9be9f8190 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -23,7 +23,7 @@ package v1alpha1 import ( unsafe "unsafe" - unversioned "github.com/deislabs/ratify/api/unversioned" + unversioned "github.com/ratify-project/ratify/api/unversioned" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -642,6 +642,7 @@ func Convert_v1alpha1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, func autoConvert_unversioned_VerifierSpec_To_v1alpha1_VerifierSpec(in *unversioned.VerifierSpec, out *VerifierSpec, s conversion.Scope) error { out.Name = in.Name + // WARNING: in.Type requires manual conversion: does not exist in peer-type // WARNING: in.Version requires manual conversion: does not exist in peer-type out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c3396c0e7..dd638dc22 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright The Ratify Authors. diff --git a/api/v1beta1/doc.go b/api/v1beta1/doc.go index 7d2226e2b..c5e4175d4 100644 --- a/api/v1beta1/doc.go +++ b/api/v1beta1/doc.go @@ -14,5 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/deislabs/ratify/api/unversioned +// +k8s:conversion-gen=github.com/ratify-project/ratify/api/unversioned package v1beta1 diff --git a/api/v1beta1/keymanagementproviders_types.go b/api/v1beta1/keymanagementproviders_types.go index d8f10d53c..2797ccac2 100644 --- a/api/v1beta1/keymanagementproviders_types.go +++ b/api/v1beta1/keymanagementproviders_types.go @@ -31,6 +31,10 @@ type KeyManagementProviderSpec struct { // Name of the key management provider Type string `json:"type,omitempty"` + // Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields // Parameters of the key management provider Parameters runtime.RawExtension `json:"parameters,omitempty"` @@ -58,6 +62,7 @@ type KeyManagementProviderStatus struct { } // +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Cluster" // +kubebuilder:subresource:status // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` diff --git a/api/v1beta1/namespacedkeymanagementprovider_types.go b/api/v1beta1/namespacedkeymanagementprovider_types.go new file mode 100644 index 000000000..d124e88f1 --- /dev/null +++ b/api/v1beta1/namespacedkeymanagementprovider_types.go @@ -0,0 +1,93 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedKeyManagementProviderSpec defines the desired state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the key management provider + Type string `json:"type,omitempty"` + + // Refresh interval for the key management provider. Only used if the key management provider is refreshable. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the key management provider + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedKeyManagementProviderStatus defines the observed state of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in loading certificate/key files + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` + // The time stamp of last successful certificate/key fetch operation. If operation failed, last fetched time shows the time of error + // +optional + LastFetchedTime *metav1.Time `json:"lastfetchedtime,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields + // provider specific properties of the each individual certificate/key + // +optional + Properties runtime.RawExtension `json:"properties,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` +// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.brieferror` +// +kubebuilder:printcolumn:name="LastFetchedTime",type=date,JSONPath=`.status.lastfetchedtime` +// NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders API +type NamespacedKeyManagementProvider struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedKeyManagementProviderSpec `json:"spec,omitempty"` + Status NamespacedKeyManagementProviderStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedKeyManagementProviderList contains a list of NamespacedKeyManagementProvider +type NamespacedKeyManagementProviderList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedKeyManagementProvider `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedKeyManagementProvider{}, &NamespacedKeyManagementProviderList{}) +} diff --git a/api/v1beta1/namespacedpolicy_types.go b/api/v1beta1/namespacedpolicy_types.go new file mode 100644 index 000000000..26747d5f6 --- /dev/null +++ b/api/v1beta1/namespacedpolicy_types.go @@ -0,0 +1,80 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedPolicySpec defines the desired state of NamespacedPolicy +type NamespacedPolicySpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Type of the policy + Type string `json:"type,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters for this policy + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedPolicyStatus defines the observed state of NamespacedPolicy +type NamespacedPolicyStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful while applying the policy. + IsSuccess bool `json:"issuccess"` + // Error message if policy is not successfully applied. + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` +// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.brieferror` +// NamespacedPolicy is the Schema for the namespacedpolicies API +type NamespacedPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedPolicySpec `json:"spec,omitempty"` + Status NamespacedPolicyStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedPolicyList contains a list of NamespacedPolicy +type NamespacedPolicyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedPolicy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedPolicy{}, &NamespacedPolicyList{}) +} diff --git a/api/v1beta1/namespacedstore_types.go b/api/v1beta1/namespacedstore_types.go new file mode 100644 index 000000000..45a829087 --- /dev/null +++ b/api/v1beta1/namespacedstore_types.go @@ -0,0 +1,85 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedStoreSpec defines the desired state of NamespacedStore +type NamespacedStoreSpec struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Name of the store + Name string `json:"name"` + // Version of the store plugin. Optional + Version string `json:"version,omitempty"` + // Plugin path, optional + Address string `json:"address,omitempty"` + // OCI Artifact source to download the plugin from, optional + Source *PluginSource `json:"source,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters of the store + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedStoreStatus defines the observed state of NamespacedStore +type NamespacedStoreStatus struct { + // Important: Run "make install-crds" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` +// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.brieferror` +// NamespacedStore is the Schema for the namespacedstores API +type NamespacedStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedStoreSpec `json:"spec,omitempty"` + Status NamespacedStoreStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedStoreList contains a list of NamespacedStore +type NamespacedStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedStore `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedStore{}, &NamespacedStoreList{}) +} diff --git a/api/v1beta1/namespacedverifier_types.go b/api/v1beta1/namespacedverifier_types.go new file mode 100644 index 000000000..17809a456 --- /dev/null +++ b/api/v1beta1/namespacedverifier_types.go @@ -0,0 +1,96 @@ +/* +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 v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// NamespacedVerifierSpec defines the desired state of NamespacedVerifier +type NamespacedVerifierSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Name of the verifier. Deprecated + Name string `json:"name"` + + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + + // Version of the verifier plugin. Optional + Version string `json:"version,omitempty"` + + // The type of artifact this verifier handles + ArtifactTypes string `json:"artifactTypes"` + + // URL/file path. Optional + Address string `json:"address,omitempty"` + + // OCI Artifact source to download the plugin from. Optional + Source *PluginSource `json:"source,omitempty"` + + // +kubebuilder:pruning:PreserveUnknownFields + // Parameters for this verifier + Parameters runtime.RawExtension `json:"parameters,omitempty"` +} + +// NamespacedVerifierStatus defines the observed state of NamespacedVerifier +type NamespacedVerifierStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Is successful in finding the plugin + IsSuccess bool `json:"issuccess"` + // Error message if operation was unsuccessful + // +optional + Error string `json:"error,omitempty"` + // Truncated error message if the message is too long + // +optional + BriefError string `json:"brieferror,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope="Namespaced" +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="IsSuccess",type=boolean,JSONPath=`.status.issuccess` +// +kubebuilder:printcolumn:name="Error",type=string,JSONPath=`.status.brieferror` +// NamespacedVerifier is the Schema for the namespacedverifiers API +type NamespacedVerifier struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NamespacedVerifierSpec `json:"spec,omitempty"` + Status NamespacedVerifierStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// NamespacedVerifierList contains a list of NamespacedVerifier +type NamespacedVerifierList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NamespacedVerifier `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NamespacedVerifier{}, &NamespacedVerifierList{}) +} diff --git a/api/v1beta1/verifier_types.go b/api/v1beta1/verifier_types.go index 5d4bf0974..b273a4898 100644 --- a/api/v1beta1/verifier_types.go +++ b/api/v1beta1/verifier_types.go @@ -25,19 +25,22 @@ import ( type VerifierSpec struct { // Important: Run "make install-crds" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 2e097dc80..0b69afaf9 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -23,7 +23,7 @@ package v1beta1 import ( unsafe "unsafe" - unversioned "github.com/deislabs/ratify/api/unversioned" + unversioned "github.com/ratify-project/ratify/api/unversioned" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" @@ -76,6 +76,206 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*KeyManagementProvider)(nil), (*unversioned.KeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(a.(*KeyManagementProvider), b.(*unversioned.KeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProvider)(nil), (*KeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(a.(*unversioned.KeyManagementProvider), b.(*KeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderList)(nil), (*unversioned.KeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(a.(*KeyManagementProviderList), b.(*unversioned.KeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderList)(nil), (*KeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(a.(*unversioned.KeyManagementProviderList), b.(*KeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderSpec)(nil), (*unversioned.KeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(a.(*KeyManagementProviderSpec), b.(*unversioned.KeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderSpec)(nil), (*KeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(a.(*unversioned.KeyManagementProviderSpec), b.(*KeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*KeyManagementProviderStatus)(nil), (*unversioned.KeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(a.(*KeyManagementProviderStatus), b.(*unversioned.KeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.KeyManagementProviderStatus)(nil), (*KeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(a.(*unversioned.KeyManagementProviderStatus), b.(*KeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProvider)(nil), (*unversioned.NamespacedKeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(a.(*NamespacedKeyManagementProvider), b.(*unversioned.NamespacedKeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProvider)(nil), (*NamespacedKeyManagementProvider)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(a.(*unversioned.NamespacedKeyManagementProvider), b.(*NamespacedKeyManagementProvider), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderList)(nil), (*unversioned.NamespacedKeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(a.(*NamespacedKeyManagementProviderList), b.(*unversioned.NamespacedKeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderList)(nil), (*NamespacedKeyManagementProviderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(a.(*unversioned.NamespacedKeyManagementProviderList), b.(*NamespacedKeyManagementProviderList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderSpec)(nil), (*unversioned.NamespacedKeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(a.(*NamespacedKeyManagementProviderSpec), b.(*unversioned.NamespacedKeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderSpec)(nil), (*NamespacedKeyManagementProviderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(a.(*unversioned.NamespacedKeyManagementProviderSpec), b.(*NamespacedKeyManagementProviderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedKeyManagementProviderStatus)(nil), (*unversioned.NamespacedKeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(a.(*NamespacedKeyManagementProviderStatus), b.(*unversioned.NamespacedKeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedKeyManagementProviderStatus)(nil), (*NamespacedKeyManagementProviderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(a.(*unversioned.NamespacedKeyManagementProviderStatus), b.(*NamespacedKeyManagementProviderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicy)(nil), (*unversioned.NamespacedPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(a.(*NamespacedPolicy), b.(*unversioned.NamespacedPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicy)(nil), (*NamespacedPolicy)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(a.(*unversioned.NamespacedPolicy), b.(*NamespacedPolicy), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicyList)(nil), (*unversioned.NamespacedPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(a.(*NamespacedPolicyList), b.(*unversioned.NamespacedPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicyList)(nil), (*NamespacedPolicyList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(a.(*unversioned.NamespacedPolicyList), b.(*NamespacedPolicyList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicySpec)(nil), (*unversioned.NamespacedPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(a.(*NamespacedPolicySpec), b.(*unversioned.NamespacedPolicySpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicySpec)(nil), (*NamespacedPolicySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(a.(*unversioned.NamespacedPolicySpec), b.(*NamespacedPolicySpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedPolicyStatus)(nil), (*unversioned.NamespacedPolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(a.(*NamespacedPolicyStatus), b.(*unversioned.NamespacedPolicyStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedPolicyStatus)(nil), (*NamespacedPolicyStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(a.(*unversioned.NamespacedPolicyStatus), b.(*NamespacedPolicyStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStore)(nil), (*unversioned.NamespacedStore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(a.(*NamespacedStore), b.(*unversioned.NamespacedStore), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStore)(nil), (*NamespacedStore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(a.(*unversioned.NamespacedStore), b.(*NamespacedStore), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreList)(nil), (*unversioned.NamespacedStoreList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(a.(*NamespacedStoreList), b.(*unversioned.NamespacedStoreList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreList)(nil), (*NamespacedStoreList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(a.(*unversioned.NamespacedStoreList), b.(*NamespacedStoreList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreSpec)(nil), (*unversioned.NamespacedStoreSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(a.(*NamespacedStoreSpec), b.(*unversioned.NamespacedStoreSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreSpec)(nil), (*NamespacedStoreSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(a.(*unversioned.NamespacedStoreSpec), b.(*NamespacedStoreSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedStoreStatus)(nil), (*unversioned.NamespacedStoreStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(a.(*NamespacedStoreStatus), b.(*unversioned.NamespacedStoreStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedStoreStatus)(nil), (*NamespacedStoreStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(a.(*unversioned.NamespacedStoreStatus), b.(*NamespacedStoreStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifier)(nil), (*unversioned.NamespacedVerifier)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(a.(*NamespacedVerifier), b.(*unversioned.NamespacedVerifier), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifier)(nil), (*NamespacedVerifier)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(a.(*unversioned.NamespacedVerifier), b.(*NamespacedVerifier), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierList)(nil), (*unversioned.NamespacedVerifierList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(a.(*NamespacedVerifierList), b.(*unversioned.NamespacedVerifierList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierList)(nil), (*NamespacedVerifierList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(a.(*unversioned.NamespacedVerifierList), b.(*NamespacedVerifierList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierSpec)(nil), (*unversioned.NamespacedVerifierSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(a.(*NamespacedVerifierSpec), b.(*unversioned.NamespacedVerifierSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierSpec)(nil), (*NamespacedVerifierSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(a.(*unversioned.NamespacedVerifierSpec), b.(*NamespacedVerifierSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*NamespacedVerifierStatus)(nil), (*unversioned.NamespacedVerifierStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(a.(*NamespacedVerifierStatus), b.(*unversioned.NamespacedVerifierStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*unversioned.NamespacedVerifierStatus)(nil), (*NamespacedVerifierStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(a.(*unversioned.NamespacedVerifierStatus), b.(*NamespacedVerifierStatus), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*PluginSource)(nil), (*unversioned.PluginSource)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_PluginSource_To_unversioned_PluginSource(a.(*PluginSource), b.(*unversioned.PluginSource), scope) }); err != nil { @@ -313,6 +513,534 @@ func Convert_unversioned_CertificateStoreStatus_To_v1beta1_CertificateStoreStatu return autoConvert_unversioned_CertificateStoreStatus_To_v1beta1_CertificateStoreStatus(in, out, s) } +func autoConvert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in *KeyManagementProvider, out *unversioned.KeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in *KeyManagementProvider, out *unversioned.KeyManagementProvider, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProvider_To_unversioned_KeyManagementProvider(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in *unversioned.KeyManagementProvider, out *KeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in *unversioned.KeyManagementProvider, out *KeyManagementProvider, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProvider_To_v1beta1_KeyManagementProvider(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in *KeyManagementProviderList, out *unversioned.KeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.KeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in *KeyManagementProviderList, out *unversioned.KeyManagementProviderList, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderList_To_unversioned_KeyManagementProviderList(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in *unversioned.KeyManagementProviderList, out *KeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]KeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in *unversioned.KeyManagementProviderList, out *KeyManagementProviderList, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProviderList(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in *KeyManagementProviderSpec, out *unversioned.KeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.RefreshInterval = in.RefreshInterval + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in *KeyManagementProviderSpec, out *unversioned.KeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in *unversioned.KeyManagementProviderSpec, out *KeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.RefreshInterval = in.RefreshInterval + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in *unversioned.KeyManagementProviderSpec, out *KeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in, out, s) +} + +func autoConvert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in *KeyManagementProviderStatus, out *unversioned.KeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus is an autogenerated conversion function. +func Convert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in *KeyManagementProviderStatus, out *unversioned.KeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_v1beta1_KeyManagementProviderStatus_To_unversioned_KeyManagementProviderStatus(in, out, s) +} + +func autoConvert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in *unversioned.KeyManagementProviderStatus, out *KeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus is an autogenerated conversion function. +func Convert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in *unversioned.KeyManagementProviderStatus, out *KeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_unversioned_KeyManagementProviderStatus_To_v1beta1_KeyManagementProviderStatus(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in *NamespacedKeyManagementProvider, out *unversioned.NamespacedKeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in *NamespacedKeyManagementProvider, out *unversioned.NamespacedKeyManagementProvider, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProvider_To_unversioned_NamespacedKeyManagementProvider(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in *unversioned.NamespacedKeyManagementProvider, out *NamespacedKeyManagementProvider, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in *unversioned.NamespacedKeyManagementProvider, out *NamespacedKeyManagementProvider, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProvider_To_v1beta1_NamespacedKeyManagementProvider(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in *NamespacedKeyManagementProviderList, out *unversioned.NamespacedKeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedKeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in *NamespacedKeyManagementProviderList, out *unversioned.NamespacedKeyManagementProviderList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderList_To_unversioned_NamespacedKeyManagementProviderList(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in *unversioned.NamespacedKeyManagementProviderList, out *NamespacedKeyManagementProviderList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedKeyManagementProvider)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in *unversioned.NamespacedKeyManagementProviderList, out *NamespacedKeyManagementProviderList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_NamespacedKeyManagementProviderList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in *NamespacedKeyManagementProviderSpec, out *unversioned.NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.RefreshInterval = in.RefreshInterval + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in *NamespacedKeyManagementProviderSpec, out *unversioned.NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in *unversioned.NamespacedKeyManagementProviderSpec, out *NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + out.Type = in.Type + out.RefreshInterval = in.RefreshInterval + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in *unversioned.NamespacedKeyManagementProviderSpec, out *NamespacedKeyManagementProviderSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in *NamespacedKeyManagementProviderStatus, out *unversioned.NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in *NamespacedKeyManagementProviderStatus, out *unversioned.NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedKeyManagementProviderStatus_To_unversioned_NamespacedKeyManagementProviderStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(in *unversioned.NamespacedKeyManagementProviderStatus, out *NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + out.LastFetchedTime = (*v1.Time)(unsafe.Pointer(in.LastFetchedTime)) + out.Properties = in.Properties + return nil +} + +// Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(in *unversioned.NamespacedKeyManagementProviderStatus, out *NamespacedKeyManagementProviderStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedKeyManagementProviderStatus_To_v1beta1_NamespacedKeyManagementProviderStatus(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in *NamespacedPolicy, out *unversioned.NamespacedPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in *NamespacedPolicy, out *unversioned.NamespacedPolicy, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicy_To_unversioned_NamespacedPolicy(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in *unversioned.NamespacedPolicy, out *NamespacedPolicy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in *unversioned.NamespacedPolicy, out *NamespacedPolicy, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicy_To_v1beta1_NamespacedPolicy(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in *NamespacedPolicyList, out *unversioned.NamespacedPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedPolicy)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in *NamespacedPolicyList, out *unversioned.NamespacedPolicyList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicyList_To_unversioned_NamespacedPolicyList(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in *unversioned.NamespacedPolicyList, out *NamespacedPolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedPolicy)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in *unversioned.NamespacedPolicyList, out *NamespacedPolicyList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicyList_To_v1beta1_NamespacedPolicyList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in *NamespacedPolicySpec, out *unversioned.NamespacedPolicySpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in *NamespacedPolicySpec, out *unversioned.NamespacedPolicySpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicySpec_To_unversioned_NamespacedPolicySpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in *unversioned.NamespacedPolicySpec, out *NamespacedPolicySpec, s conversion.Scope) error { + out.Type = in.Type + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in *unversioned.NamespacedPolicySpec, out *NamespacedPolicySpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicySpec_To_v1beta1_NamespacedPolicySpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in *NamespacedPolicyStatus, out *unversioned.NamespacedPolicyStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in *NamespacedPolicyStatus, out *unversioned.NamespacedPolicyStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedPolicyStatus_To_unversioned_NamespacedPolicyStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in *unversioned.NamespacedPolicyStatus, out *NamespacedPolicyStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in *unversioned.NamespacedPolicyStatus, out *NamespacedPolicyStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedPolicyStatus_To_v1beta1_NamespacedPolicyStatus(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in *NamespacedStore, out *unversioned.NamespacedStore, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in *NamespacedStore, out *unversioned.NamespacedStore, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStore_To_unversioned_NamespacedStore(in, out, s) +} + +func autoConvert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in *unversioned.NamespacedStore, out *NamespacedStore, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore is an autogenerated conversion function. +func Convert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in *unversioned.NamespacedStore, out *NamespacedStore, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStore_To_v1beta1_NamespacedStore(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in *NamespacedStoreList, out *unversioned.NamespacedStoreList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedStore)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in *NamespacedStoreList, out *unversioned.NamespacedStoreList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreList_To_unversioned_NamespacedStoreList(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in *unversioned.NamespacedStoreList, out *NamespacedStoreList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedStore)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in *unversioned.NamespacedStoreList, out *NamespacedStoreList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreList_To_v1beta1_NamespacedStoreList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in *NamespacedStoreSpec, out *unversioned.NamespacedStoreSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.Address = in.Address + out.Source = (*unversioned.PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in *NamespacedStoreSpec, out *unversioned.NamespacedStoreSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreSpec_To_unversioned_NamespacedStoreSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in *unversioned.NamespacedStoreSpec, out *NamespacedStoreSpec, s conversion.Scope) error { + out.Name = in.Name + out.Version = in.Version + out.Address = in.Address + out.Source = (*PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in *unversioned.NamespacedStoreSpec, out *NamespacedStoreSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreSpec_To_v1beta1_NamespacedStoreSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in *NamespacedStoreStatus, out *unversioned.NamespacedStoreStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in *NamespacedStoreStatus, out *unversioned.NamespacedStoreStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedStoreStatus_To_unversioned_NamespacedStoreStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in *unversioned.NamespacedStoreStatus, out *NamespacedStoreStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in *unversioned.NamespacedStoreStatus, out *NamespacedStoreStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedStoreStatus_To_v1beta1_NamespacedStoreStatus(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in *NamespacedVerifier, out *unversioned.NamespacedVerifier, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in *NamespacedVerifier, out *unversioned.NamespacedVerifier, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifier_To_unversioned_NamespacedVerifier(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in *unversioned.NamespacedVerifier, out *NamespacedVerifier, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in *unversioned.NamespacedVerifier, out *NamespacedVerifier, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifier_To_v1beta1_NamespacedVerifier(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in *NamespacedVerifierList, out *unversioned.NamespacedVerifierList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]unversioned.NamespacedVerifier)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in *NamespacedVerifierList, out *unversioned.NamespacedVerifierList, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierList_To_unversioned_NamespacedVerifierList(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in *unversioned.NamespacedVerifierList, out *NamespacedVerifierList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]NamespacedVerifier)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in *unversioned.NamespacedVerifierList, out *NamespacedVerifierList, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierList(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in *NamespacedVerifierSpec, out *unversioned.NamespacedVerifierSpec, s conversion.Scope) error { + out.Name = in.Name + out.Type = in.Type + out.Version = in.Version + out.ArtifactTypes = in.ArtifactTypes + out.Address = in.Address + out.Source = (*unversioned.PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in *NamespacedVerifierSpec, out *unversioned.NamespacedVerifierSpec, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in *unversioned.NamespacedVerifierSpec, out *NamespacedVerifierSpec, s conversion.Scope) error { + out.Name = in.Name + out.Type = in.Type + out.Version = in.Version + out.ArtifactTypes = in.ArtifactTypes + out.Address = in.Address + out.Source = (*PluginSource)(unsafe.Pointer(in.Source)) + out.Parameters = in.Parameters + return nil +} + +// Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in *unversioned.NamespacedVerifierSpec, out *NamespacedVerifierSpec, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in, out, s) +} + +func autoConvert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in *NamespacedVerifierStatus, out *unversioned.NamespacedVerifierStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus is an autogenerated conversion function. +func Convert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in *NamespacedVerifierStatus, out *unversioned.NamespacedVerifierStatus, s conversion.Scope) error { + return autoConvert_v1beta1_NamespacedVerifierStatus_To_unversioned_NamespacedVerifierStatus(in, out, s) +} + +func autoConvert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in *unversioned.NamespacedVerifierStatus, out *NamespacedVerifierStatus, s conversion.Scope) error { + out.IsSuccess = in.IsSuccess + out.Error = in.Error + out.BriefError = in.BriefError + return nil +} + +// Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus is an autogenerated conversion function. +func Convert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in *unversioned.NamespacedVerifierStatus, out *NamespacedVerifierStatus, s conversion.Scope) error { + return autoConvert_unversioned_NamespacedVerifierStatus_To_v1beta1_NamespacedVerifierStatus(in, out, s) +} + func autoConvert_v1beta1_PluginSource_To_unversioned_PluginSource(in *PluginSource, out *unversioned.PluginSource, s conversion.Scope) error { out.Artifact = in.Artifact out.AuthProvider = in.AuthProvider @@ -597,6 +1325,7 @@ func Convert_unversioned_VerifierList_To_v1beta1_VerifierList(in *unversioned.Ve func autoConvert_v1beta1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, out *unversioned.VerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address @@ -612,6 +1341,7 @@ func Convert_v1beta1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, func autoConvert_unversioned_VerifierSpec_To_v1beta1_VerifierSpec(in *unversioned.VerifierSpec, out *VerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 8e390e3c8..e45588f30 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* Copyright The Ratify Authors. @@ -215,6 +214,381 @@ func (in *KeyManagementProviderStatus) DeepCopy() *KeyManagementProviderStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProvider) DeepCopyInto(out *NamespacedKeyManagementProvider) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProvider. +func (in *NamespacedKeyManagementProvider) DeepCopy() *NamespacedKeyManagementProvider { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProvider) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedKeyManagementProvider) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderList) DeepCopyInto(out *NamespacedKeyManagementProviderList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedKeyManagementProvider, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderList. +func (in *NamespacedKeyManagementProviderList) DeepCopy() *NamespacedKeyManagementProviderList { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedKeyManagementProviderList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderSpec) DeepCopyInto(out *NamespacedKeyManagementProviderSpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderSpec. +func (in *NamespacedKeyManagementProviderSpec) DeepCopy() *NamespacedKeyManagementProviderSpec { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedKeyManagementProviderStatus) DeepCopyInto(out *NamespacedKeyManagementProviderStatus) { + *out = *in + if in.LastFetchedTime != nil { + in, out := &in.LastFetchedTime, &out.LastFetchedTime + *out = (*in).DeepCopy() + } + in.Properties.DeepCopyInto(&out.Properties) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedKeyManagementProviderStatus. +func (in *NamespacedKeyManagementProviderStatus) DeepCopy() *NamespacedKeyManagementProviderStatus { + if in == nil { + return nil + } + out := new(NamespacedKeyManagementProviderStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicy) DeepCopyInto(out *NamespacedPolicy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicy. +func (in *NamespacedPolicy) DeepCopy() *NamespacedPolicy { + if in == nil { + return nil + } + out := new(NamespacedPolicy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedPolicy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyList) DeepCopyInto(out *NamespacedPolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedPolicy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyList. +func (in *NamespacedPolicyList) DeepCopy() *NamespacedPolicyList { + if in == nil { + return nil + } + out := new(NamespacedPolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedPolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicySpec) DeepCopyInto(out *NamespacedPolicySpec) { + *out = *in + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicySpec. +func (in *NamespacedPolicySpec) DeepCopy() *NamespacedPolicySpec { + if in == nil { + return nil + } + out := new(NamespacedPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedPolicyStatus) DeepCopyInto(out *NamespacedPolicyStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedPolicyStatus. +func (in *NamespacedPolicyStatus) DeepCopy() *NamespacedPolicyStatus { + if in == nil { + return nil + } + out := new(NamespacedPolicyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStore) DeepCopyInto(out *NamespacedStore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStore. +func (in *NamespacedStore) DeepCopy() *NamespacedStore { + if in == nil { + return nil + } + out := new(NamespacedStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedStore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreList) DeepCopyInto(out *NamespacedStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreList. +func (in *NamespacedStoreList) DeepCopy() *NamespacedStoreList { + if in == nil { + return nil + } + out := new(NamespacedStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedStoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreSpec) DeepCopyInto(out *NamespacedStoreSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreSpec. +func (in *NamespacedStoreSpec) DeepCopy() *NamespacedStoreSpec { + if in == nil { + return nil + } + out := new(NamespacedStoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedStoreStatus) DeepCopyInto(out *NamespacedStoreStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedStoreStatus. +func (in *NamespacedStoreStatus) DeepCopy() *NamespacedStoreStatus { + if in == nil { + return nil + } + out := new(NamespacedStoreStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifier) DeepCopyInto(out *NamespacedVerifier) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifier. +func (in *NamespacedVerifier) DeepCopy() *NamespacedVerifier { + if in == nil { + return nil + } + out := new(NamespacedVerifier) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedVerifier) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierList) DeepCopyInto(out *NamespacedVerifierList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NamespacedVerifier, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierList. +func (in *NamespacedVerifierList) DeepCopy() *NamespacedVerifierList { + if in == nil { + return nil + } + out := new(NamespacedVerifierList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NamespacedVerifierList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierSpec) DeepCopyInto(out *NamespacedVerifierSpec) { + *out = *in + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(PluginSource) + (*in).DeepCopyInto(*out) + } + in.Parameters.DeepCopyInto(&out.Parameters) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierSpec. +func (in *NamespacedVerifierSpec) DeepCopy() *NamespacedVerifierSpec { + if in == nil { + return nil + } + out := new(NamespacedVerifierSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespacedVerifierStatus) DeepCopyInto(out *NamespacedVerifierStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespacedVerifierStatus. +func (in *NamespacedVerifierStatus) DeepCopy() *NamespacedVerifierStatus { + if in == nil { + return nil + } + out := new(NamespacedVerifierStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PluginSource) DeepCopyInto(out *PluginSource) { *out = *in diff --git a/archive/meeting-notes/ratify-weekly-notes-2023-Jan-2023-Jun.md b/archive/meeting-notes/ratify-weekly-notes-2023-Jan-2023-Jun.md index b328afce6..c70e59ca7 100644 --- a/archive/meeting-notes/ratify-weekly-notes-2023-Jan-2023-Jun.md +++ b/archive/meeting-notes/ratify-weekly-notes-2023-Jan-2023-Jun.md @@ -785,7 +785,7 @@ Recording: https://youtu.be/vn_GOUXZGhw ### Presentation/Discussion Agenda Items: - [Akash]How do we handle breaking changes that require a change to the README? (Akash) From last week - [Susan] Maybe link to github page something like https://deislabs.github.io/ratify/getting-started.html? how does csi driver maintain its docs ? https://secrets-store-csi-driver.sigs.k8s.io/getting-started/getting-started.html + [Susan] Maybe link to github page something like https://ratify-project.github.io/ratify/getting-started.html? how does csi driver maintain its docs ? https://secrets-store-csi-driver.sigs.k8s.io/getting-started/getting-started.html [Sajay] Not sure if external doc will have maintainance overhead. We can add a link to the quickstart that is pinned to a released version for now. - [Akash] Cosign auth support: https://hackmd.io/@akashsinghal/rks7vlOps diff --git a/archive/meeting-notes/ratify-weekly-notes-2023-Jun-2024-Jun.md b/archive/meeting-notes/ratify-weekly-notes-2023-Jun-2024-Jun.md new file mode 100644 index 000000000..06509f412 --- /dev/null +++ b/archive/meeting-notes/ratify-weekly-notes-2023-Jun-2024-Jun.md @@ -0,0 +1,1722 @@ +## Jun 26 2024 + +Moderator: Susan Shi +Notes: Binbin Li +### Announcement: + +### Attendees: +- Susan Shi +- Josh Duffney +- Juncheng Zhu +- Akash Singhal +- Luis Dieguez +- Shiwei Zhang +- Yi Zha +- Binbin Li + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- init design doc for OCI Store Cache Worker, https://github.com/ratify-project/ratify/pull/1578 +- Brainstorming: How to break down the issue https://github.com/ratify-project/ratify/issues/1321 (Yi) +### Notes + +https://youtu.be/0vW5thB94lk + +--- +## Jun 19 2024 + +Moderator: Binbin Li +Notes: Akash Singhal + +### Attendees: + + +### Actionable Agenda Items: +- PR review for ratify repo +- PR review for ratify-web repo + +### Presentation/Discussion Agenda Items: +- Triage issue? Should we update PR review meeting for PM attendance. +[Susan] Should we try PR status check in PR review meeting ( we can extract any PR discussion items as agenda), and spend more time on issue discssion during community call? + +- Discuss cert/key periodic fetch design (Josh) https://hackmd.io/@18hDkt3CRm6BvYYJo4sGOw/ry6wyR4BC + - Periodic retrieval of keys and certificates implemented by interval trigger of reconcile method + - what is the customer scenario that dictates how granular the interval should be set at? KMP level? resource level? resource type level? + - We want to be able to configure which provider will enable refreshing/rotation + - We want to make interval configurable so user can override depending on their scenario + - update interval should be an optional flag and set a good default flag + - update interval should be under parameters so that it hints to interval being provider type specific + - add an interface (essentially an abstracted update notification) so that we can have a flexible implementation for future standalone ratify server efforts + +- Issue triage + - [How to write the policy for verifying Cosign signatures only](https://github.com/ratify-project/ratify/issues/1451) + + - Notify users early if keymanagementprovider resource does not exist, https://github.com/ratify-project/ratify/issues/1452 + + - The verifierReports did not include signature digest,https://github.com/ratify-project/ratify/issues/1454 +### Notes + +recording: https://youtu.be/bV_gCzmY_pI + +--- +## Jun 12 2024 + +Moderator: Akash Singhal +Notes: Susan Shi + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Josh Duffney +- Shiwei Zhang +- Luis Dieguez + +### Actionable Agenda Items: +- Quick check in on Repo move +- CNCF Sandbox review status (Recording: https://www.youtube.com/watch?v=Czv29DTbcOU and [review queue](https://github.com/orgs/cncf/projects/14/views/1)) +- PR review for ratify repo +- PR review for ratify-web repo + +### Presentation/Discussion Agenda Items: +- Demo/discussion [docker-ratify](https://github.com/shizhMSFT/docker-ratify) plugin (Shiwei) +- Discuss cert/key rotation proposal (Yi) +- Discuss cert/key design (Josh) https://hackmd.io/@18hDkt3CRm6BvYYJo4sGOw/ry6wyR4BC +- Image + release asset signing discussion (Akash) + +### Notes + +- CNCF Sandbox review status: we are queued up for next review Aug 13 +- TODO: 1.create tracking issue for README improvment + 2.Update donation issue with new repo URL. +- TODO: brain storm blog ideas + +Key Topics: + +Repo Move: The team has made progress on migrating Helm charts and GitHub references to the new ratify project org. Some outstanding items include remoduling CRD work for the V2 release and website updates. Susan is tasked with updating the PR workflow to use the default hub token. 3:26 + +CNCF Sandbox Application: The ratify application was not reviewed in the current CNCFQC round but is scheduled for review in August. The team plans to use the additional time to prepare for the donation and review process, focusing on increasing visibility and GitHub stars. Feynman and the team discussed improving the README and roadmap documentation to better present the project to CNCFQC. 5:07 + +Key Rotation Proposal: Yi and the team discussed the key rotation proposal, focusing on the implementation of periodic retrieval of key versions and the potential impact on AKB or other providers due to throttling. The proposal includes maintaining a default of two versions of keys to mitigate concerns. 50:35 + +Docker Ratify Plugin: Shiwei presented a Docker plugin prototype for ratify, demonstrating how it could integrate with Docker to verify images before pulling. The team discussed the potential of this plugin and considered it for further development and showcasing. 43:54 + +--- +## Jun 05 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Josh Duffney + +### Actionable Agenda Items: +- [1.2 blog PR](https://github.com/deislabs/ratify-web/pull/83) + +### Presentation/Discussion Agenda Items: +-[Yi] proposal for periodic retrieval. We still need alignment on the following: + - we are implementing a solution for all KMP or only limit to akv + - customer gesture for configuring the interval + - support both Pinned version and n version + +### Notes + +Key Topics: +Ratify 1.2 release and promotion: Josh will post the announcement on Twitter and other channels, Feyman will try to increase the project visibility for CNCF review, and Josh will update the blog post with the kubecon video link. 4:01 + +Repo transfer and cleanup: Susan will invite the members to the new organization, Akash will validate the Helm chart URL, and they will fix the workflow tokens and branch protection rules. 6:44 + +PR review: They went through the open PRs and decided to merge some of them after setting up the workflows, and leave some for further discussion or investigation. 14:51 + +Periodic retrieval proposal: Yi updated the proposal with more details and comparisons, and we discussed the design decisions around the retrieval method, the key versioning, the certificate rotation, and the error handling. We agreed on some points and left some for implementation phase or further research. 34:58 + +recording: https://youtu.be/3ECODsF2g6M + +--- +## May 29 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Shiwei Zhang +- Josh Duffney +- Luis + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- v1.2 readiness + +### Notes + +V 1.2 release: Feyman confirmed readiness to release V 1.2 today after getting feedback from partners and internal teams. Josh volunteered to work on the announcement blog post. 2:47 + + +Cosign keyless support: Akash added a summary section to the verification report to explain the cosign verifications. E suggested to standardize the extension field for different verifiers. Susan asked Yi to review the documentation and the user experience. 6:39 +Periodic retrieval proposal: E explained the background and the scenarios for the proposal. Suzanne suggested to have an offline discussion or a separate meeting to review the comments and the details. 15:38 + +Cherry pick changes: Bin Bin cherry picked the required changes from dev to release 1.2 branch. Akash reported some failures in the AKV E2E test and kicked off a rerun job. 19:02 + +Namespace to metric: Bin Bin added a namespace attribute to the metrics and a new template for the Grafana dashboard. He will update the documentation for multi-tenancy metrics. 21:58 + +Vulnerability scan: Susan added a trivy action to the workflow to scan the code and the images for vulnerabilities. Josh suggested to add a severity flag and an exit code to fail the build on high vulnerabilities. 33:23 + +recording: +https://youtu.be/crVSaSfbeN8 + +--- +## May 22 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Shiwei Zhang +- Josh Duffney +- Luis + +### Actionable Agenda Items: +- Can't use ratify with private [ECR repository ](https://github.com/deislabs/ratify/issues/1478), should we submit a change and ask folks on thread to validate the dev build. +- fixes we need to port from dev to rc: + - test: fix base image e2e test for v1.2.0-rc.1 + - ECR fix + - busybox testdata cve supression fix +- review [roadmap](https://github.com/deislabs/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.3.0) vs milestone [v1.3](https://github.com/deislabs/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.3.0) + + - is there an item for timestamp store and support of different kind of stores? +### Presentation/Discussion Agenda Items: +- + + +### Notes + +recording: https://youtu.be/UZu0I5NxGpY + +--- +## May 15 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Sajay Antony +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Shiwei Zhang +- Josh Duffney + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Mailing list for Ratify announcement? +- All Prs required for Ratify 1.2 is complete. Ready for release after we prepare the chart changes. +- Attestation planning for 1.4 + +### Meeting summary: +Key Topics: +- RCI readiness for 1.2 release: Susan, Akash, Bin Bin and E reviewed the PR status, test coverage and issues for the cosine improvement and multi tenancy features and agreed to cut the RC branch today. 14:11 +- Documentation for 1.2 release: Akash, E and Bin Bin have PRs for the KMP, cosign and AKB integration and multi tenancy docs and will address the feedback and merge them before the final release. Susan will send the preview links to the partners who want to try the new features. 46:35 +- Attestation scenarios and challenges: Sajay, Akash Feynman discussed the need to define the attestation discovery, acquisition and verification mechanisms and the issues around signature format, size and performance. They also agreed to do more research on how other projects like Witness and kveryno use in-toto attestations. 26:30 +- AWS ECR sync issue: Akash and Femen will reach out to Jesse from AWS to see if he can help with the issue reported by a user who is unable to sync signatures from ECR to ACR using notation. 47:58 +- Work items for 1.3 release: Susan and Femen suggested that Josh can work on some of the items in the 1.3 roadmap, such as keyless signing, certificate rotation and error handling improvements, and collaborate with Akash on the details. +### Notes + +recording: https://youtu.be/MM0cn8q1h0c + +--- +## May 8 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Sajay Antony +- Juncheng Zhu +- Feynman Zhou +- Yi Zha +- Shiwei Zhang + +### Actionable Agenda Items: +- Assign Mutator cannot pass image with namespace to Ratify. https://github.com/deislabs/ratify/issues/1458 +- Add back workflow of setting up tls certs manually. One potential issue on cert-controller: https://github.com/deislabs/ratify/issues/1458 +- Discuss the Ratify repo transfering timeline based on the validation result: https://github.com/deislabs/ratify/issues/1386 , (Feynman) +- Attestation + +### Presentation/Discussion Agenda Items: + +### Notes +- Repo move targeting End of May + +recording: https://youtu.be/N5EC_RKw6Xc + +--- +## May 1 2024 + +### Announcement: + +### Attendees: +- Luis +- Sajay +- Akash +- Susan + +### Actionable Agenda Items: +- Discussed Patch release v1.1.1 +- Buddy test v1.2: +https://hackmd.io/@akashsinghal/BJXoc-ub0 + + +### Presentation/Discussion Agenda Items: +- Reviewed multi tenancy support matrix + +### Notes + +recording: https://youtu.be/CSEVDI_Mu8w + +--- +## April 24 2024 + +### Announcement: + +### Attendees: +- Akash Singhal (MSFT) + +### Actionable Agenda Items: +- CNCF TAG Security Presentation feedback: + - Sign release artifacts (binaries + ghcr images) + - Generate provenance for release artifacts + - Currently a CVE on ratify repo with high level. Need to fix. + - What is the CVE remediation plan for Ratify? + - In-toto support: + - in-toto community is interested in collaborating on a Ratify workflow + - attestation verifier needs to consider predicate type being embedded in the artifact type + - Deep dive on in-toto framework (not just attestation) to understand how in-toto cryptographically gurantees verification across supply chain steps. There were questions on how Ratify guarantees the verifications it's performing. + - SLSA provenance support: we plan to have a verifier in the future for SLSA provenance + - Security Review: start by following a self-guided review upon which CNFC TAG will review the self-review and then highlight extra concerns + - Guide recommended to start with: https://github.com/cncf/tag-security/blob/main/assessments/guide/self-assessment.md + +### Presentation/Discussion Agenda Items: +- Release Cadence discussion: + - We should have at least one RC release for 1.2.0. Even if this delays release + - We should prioritize a v1.1.1 patch release which includes dependency updates only. + - We should set a 3 month cadence for patch releases for current release. + - We will create a vulnerability reporting and remediation guide. High severity vulnerabilities directly created from Ratify will result in emergency patch releases (document should define these parameters) + - @yizha1 to create issues to track guidance work and will work on it +### Notes +- Action Items: + - add an issue to define processes for path release + - add an issue to define processes for vulnerability reporting and patching + - add an issue for signing release artifacts + - add an issue for provenance of release artifacts + +recording: https://youtu.be/CMS8cKc7o3k + +---- +## April 17 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Akash Singhal +- Sajay Antony +- Luis Dieguez +- Juncheng Zhu +- Feynman Zhou +- Yi Zha + +### Actionable Agenda Items: + +### Presentation/Discussion Agenda Items: +- CNCF Presentation walkthrough and feedback +- Rename Policy/Verifier/Store CRDs to ClusterPolicy/ClusterVerifier/ClusterStore. + - It's a breaking change, users need to uninstall old CRDs while upgrading. + +- Review validation results for repo [move](https://github.com/deislabs/ratify/issues/1386) +### Notes + +recording: https://youtu.be/Bls6WGaxq5Y + +---- +## April 10 2024 + +### Announcement: + +### Attendees: + +- Juncheng Zhu +- Akash Singhal +- Yi Zha +- Susan Shi +- Binbin Li +- Shiwei Zhang +- Luis Dieguez + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Cosign public key verification scenarios (Yi) +- Ratify donation progress and next step (Feynman) + 1. Presentation at CNCF TAG Security on Apr 17 + 2. Ratify repos transferring timeline and potential impacts (questions from @akashsinghal) + - Ratify Packages + - Are existing binaries published still accessible via old repo path? + - Will existing binary assets automatically publish under new repository? + - Ratify's module is current github.com/deislabs/ratify. When do we plan to update this? For previously published packages visible in pkg.go.dev, do we need to take steps so user's can find future versions under the new repo path? If we update the module name, will it break out-of-tree plugins that import it? + - Ratify Registry Artifacts + - Are existing images still accessible via ghcr.io/deislabs/ratify? + - Do all existing artifacts transfer to new repo? + - Ratify Helm Charts + - Do we need to re release our helm charts with an updated CRD + core ratify images? + - Do we need to update artifact hub? + - Ratify github published page + - The helm chart is published to repo's github pages. Will the existing helm charts at deislabs.github.io still be accessible? + - Ratify Github Actions + - Will the repo transfer re trigger releases? As in when the new tags come over, will they count as tag push events that will trigger are auto release workflows? + - Do any release actions have hardcoded dependency on deislabs/ratify? + - Do we need to update the helm publishing action to point to a new github.io page? + - Ratify Website + - Do we need to update all links referencing deislabs/ratify or will github redirect to new repo location? + - Do we need to make any updates to the GH action that auto publishes new main branch changes to website? + - Tokens + - Do we need to regenerate tokens for GH actions? + - CRDs (not blocking) + - The Ratify CRDs are pinned to the group `config.ratify.deislabs.io`. What is the plan to migrate? + +### Notes + +recording:https://youtu.be/2JF3vx4-U6s + +- We shared the Ratify donation progress on Apr 10 and discussed the next steps. What have aligned these in the meeting: + - We will have a dry-run presentation by Apr 17 and target Apr 24 to give a presentation at CNCF TAG Security's meeting. @Feynman will reply to TAG Security about the schedule. @akashsinghal @luisdlp will be joinning the TAG meeting and presenting Ratify. + - To transfer Ratify GitHub repos to a new organization, GitHub will automatically redirect to the new address so it will not be disruptive to existing development process. If the token is stored in the Ratify repo, it will be automatically transferred either. + - To make sure no break to the published packages, artifacts, ghcr images, we need to validate and simulate the transferring in a personal org for a pre-check + - Ratify CRDs are pinned to the group `config.ratify.deislabs.io` will be changed in a new major version (Ratify v2) + - We will migrate only two active repos to a new organization and archive two inactive repositories in deislabs + +- Yi shared Cosign public key verification scenarios from his understanding and mentioned he wants to create a doc to articulate the scenarios and expected behaviors. +- Yi mentioned that configure Notation and verify Notary Project signature with different certificate stores are missing in Ratify docs. Yi wants to add the doc in a PR. + +---- +## April 3 2024 + +### Announcement: + +### Attendees: +- Luis Dieguez +- Akash +- Susan + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Discuss workflow from staging to main: + +Checkout from default branch staging + +1.Clone the staging branch, create dev branch based off staging branch +2.Merge staging into dev branch during developement +3.Create a PR against default branch, this PR will be required to be up to date with the target PR branch +4.Merge PR to staging +5.Workflow create a PR on Staging Push event targeting main +6.PR fails full validation + +7. Dev/maintainer notices PR is blocked on merge to main due to test failure +Option1: +7.1 create a new dev branch based off staging +7.2 open a PR against staging +7.3 PR gets merged into staging + +8. Dev/mainter works on the fix, and publish a new cycle + + +Scenario with 2 PR s in progress: + +PR1 ( fork) : Merge to staging with regression +PR2 ( fork): Rebased with staging , successfully merged into staging + +Automated1: Notice a regression + +PR3: notices there is a regression + revert change and go through review process, the "revert" gets merged into staging + +Automated1: unblocked , ( **always keep individual commit from staging to main**) + +summary: Agreed to set staging as default branch + + +### Notes +https://youtu.be/GmJhmc7HJ1k +------------------- + +## March 27 2024 + +### Announcement: + +### Attendees: +- Juncheng Zhu +- Akash Singhal +- Yi Zha +- Susan Shi +- Binbin Li +- Shiwei Zhang + +### Actionable Agenda Items: +- Cosign Trust policy scope +Scope with prefix: + +E.g. A: ghcr.io/namespace/path + B: ghcr.io/namespace + +We agreed that overlapping scope will not be allowed. + +### Presentation/Discussion Agenda Items: +- Issues/Discussion triage + +### Notes +recording: https://youtu.be/_4LjN7LayUs + +------------------- +## March 20 2024 + +### Announcement: + +### Attendees: +- Luis Dieguez +- Juncheng Zhu +- Akash Singhal +- Yi Zha +- Susan Shi +- Binbin Li + +### Actionable Agenda Items: +- Discuss CertStore deprecation experience +We agreed to depcreate CertStore in v2 release. In the v1.2 release, CertStore will take precedence over KMP for backward compatability. + +We have a few scenarios to consider: +- customer installing fresh 1.2 release +- customers using existing v1.1 release with default chart that have existing CertStore +- customer using existing v1.1 release , but with modified certStore CR + +- Yi to discuss continuous cert fetching +Cert rotation is scheduled by v1.3 +- Roadmap document - TODO: please review @all + +### Presentation/Discussion Agenda Items: +- Issues triage, please review err improvement issue at https://github.com/deislabs/ratify/issues/1321 + +### Notes + +recording: https://youtu.be/D1N6NPCYYV4 + +------------------- + +## March 13 2024 + +### Announcement: + +### Attendees: +- Akash +- Luis +- Feynman +- Shiwei +- Binbin + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Reviewed Active PRs +- Discussed support issues surfaced in the Slack channel. + - Follow up with Sertac with GK issue + +- OCI 1.1 Support + - Default to ORAS support + - There should already be issues tracking this on ORAS and Notary Project + - No other work needed + +- Reviewed the work for v1.2.0 + +- Add an issue to discuss the deprecation of pluginss (Susan) + +- Add an document to define and document process around breaking changes ( Susan) +Probably start with the release.md + + +### Notes + +recording: https://youtu.be/T1G43wHN90Y + +------------------- +## March 6 2024 + +### Attendees +- Feynman Zhou +- Akash Singhal +- Yi Zha +- Susan Shi + +### Agenda Items + +- We need to think and explore a bit more in the multi tenancy sidecar senarios where there maybe a mix of namespaces. + + +- Contirbutors, please review PRs when convinient +https://github.com/deislabs/ratify/pulls +- https://github.com/deislabs/ratify/issues/1322 + +### Notes +- The meeting ended earlier since no planned topics and low attendance +recoding: https://youtu.be/8ip_8wWNDf4 + +------------------- +## Feb 21 2024 + +### Attendees +- Feynman Zhou +- Shiwei Zhang +- Yi Zha + +### Agenda Items +- + +### Notes +- The meeting ended earlier since no planned topics and low attendance + +------------------- +## Feb 7 & 14 2024 + +### Announcement: +We anticipate low attendance due to holidays. Meetings are cancelled for Feb 7 and feb 14. + + +------------------- +## Jan 31 2024 + +### Announcement: + +### Attendees: +- Akash Singhal +- Josh Duffney +- Yi Zha +- Juncheng Zhu +- Feynman Zhou +- Binbin Li +- Susan Shi + +### Actionable Agenda Items: +- https://github.com/deislabs/ratify/issues/1287 +Please vote on name of the new CR for key/cert +- Review https://github.com/deislabs/ratify/pull/1258 +TODO:Review doc on https://ratify.dev/docs/quickstarts/creating-plugins +- Review https://github.com/deislabs/ratify/pull/1264 + +### Presentation/Discussion Agenda Items: +- Continue Cosign discussions , reviewed trust policy for cosign proprosal. +[Akash/Josh] To enable AKV scenarios for cli we need to think about how the cli pick up identiy form the VM. There might be az identity sdks available in the azure sdk. + +- Prosposal for new workflows to run full test matrix on staging. YourPR -> Staging -> Main + +### Notes +https://youtu.be/DDeGKKpLyt4 + +------------------- +## Jan 24 2024 + +### Announcement: + +### Attendees: +- Sajay Antony +- Akash Singhal +- Luis Diegue +- Josh Duffney +- Susan Shi +- Yi Zha +- Juncheng Zhu +- Feynman Zhou +- Shiwei Zhang +- Binbin Li + +### Actionable Agenda Items: +- cosign discussion + +Aligned on Option2 to introduce new a new CR (Name TBD) , but mutuatually exclusive with certificate store. + +The CRD name also need to be revised to drop the current org name + +We should also get feedback from Xinhe + +[Sajay]Regards to multiKey scenarios, we should consider introducing scoping similar to trust stores. + +TODO: we should improve our doc to give example on how specify multi keys to support key rotation scenarios. + +TODO: should we also think about revocation scenarios. + +### Presentation/Discussion Agenda Items: +- [Ratify donation things to prepare](https://hackmd.io/arB3BXdtRFWrXPSVUKHjRQ) before Apr 9, 2024 + +TODO: Create a new repo/org for Ratify +TODO: Create a issue for each checklist item + +### Notes +recording: https://youtu.be/qr4vIibl6oE + +------------------- +## Jan 17 2024 + +### Announcement: +Search is now enabled on https://ratify.dev/. Thanks for you contribution @Feynman , ShravaniAK! +### Attendees: + + +### Actionable Agenda Items: +- + +### Presentation/Discussion Agenda Items: +- [Binbin]Should we always enable CRD manager? Currently it's enabled by default but can be disabled manually. +- Ratify support Cosign signature, https://hackmd.io/KqkvtYz3T72wFuc751MhVQ + +- [Notation verifier] +We're adding support for more trust store types. Would add a type field under `spec.paratemers.verificationCertStores` +![image](https://hackmd.io/_uploads/H1-A1x8YT.png) +``` +spec: + parameters: + verificationCertStores: + ca: + certs: + - akv + - akv1 + certs1: + - akv2 + - akv3 + tsa: + certs: + - akv4 +``` +or +``` +spec: + parameters: + verificationCertStores: + ca/certs: + - AKV + - akv1 + ca/certs1: + - akv2 + - akv3 +``` + +- [Susan] Review updates to the release notes, future breaking changes for CRD might follow the same format + +#### CRD Breaking Change +##### CertificateStore +[Certificate Store](https://ratify.dev/docs/next/reference/crds/certificate-stores) is a namespaced CR. We have made a [fix](https://github.com/deislabs/ratify/pull/1134) in this release so that Certificate Store CR can be uniquely referenced by [Verifier](https://ratify.dev/docs/next/reference/crds/verifiers) CR. + +No action item if you are using the default ratify helm chart. +If you maintain custom Ratify chart or have manually applied CertificateStore CR, +please edit the Verifier CR to append the namespace of the Certificate Store. See notation verifier example [here](https://ratify.dev/docs/next/reference/crds/verifiers#notation). + + +### Notes + +recording: https://youtu.be/ceRgf6VrqsQ + +------------------- +## Jan 10 2024 + +### Announcement: + +### Attendees: + +- Luis Dieguez +- Josh Duffney +- Susan Shi +- Yi Zha +- Feynman Zhou +- Shiwei Zhang + +### Actionable Agenda Items: +- [susan] TODO: Add more details to the v1.1 release notes once after doc change at https://github.com/deislabs/ratify-web/pull/53 + +### Presentation/Discussion Agenda Items: +- Search on website PR is coming soon +- Josh will be running a session on setting up Ratify dev environment, he will be sharing his getting started dev experience .. TODO: add this recording link to our contributing.md + + + +### Notes +https://youtu.be/znTp7pxWlVM + + +------------------- +## Jan 3 2024 + +### Announcement: + +### Attendees: +- Susan Shi +- Josh Duffney +- Luis Dieguez +- Juncheng Zhu +- Yi Zha +- Feynman Zhou +- Shiwei Zhang +- Akash Singhal +- Binbin Li + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- [Support logging in External Plugin](https://github.com/deislabs/ratify/issues/1022) + - Question: Are there any known issues with messages not being returned by plugins? Either stderr or stdout? + + If possible, we should include the context , and plugin name. + + - Repo e2e test +We have 2 goals: + +1. We should run all gates before check in. +2. Reduced resource /matrix where possible + +TODO: Create a tracking issue, and look into a staging branch + +- We should discuss Ratify Donation to CNCF + +- TODO: create a new milestone for patch release + +### Notes + +https://youtu.be/HdnfXm0KX8w + +------------------- +## Dec 26 2023 + +### Announcement: + +Happy holidays! no meeting this week + +------------------- +## Dec 20 2023 + +### Announcement: + +### Attendees: + +- Yi Zha +- Luis Dieguez +- Joshua Duffney +- Susan Shi +- Akash Singhal +- Binbin Li +- Shiwei Zhang +- Feynman Zhou + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- [Validate external plugin name while applying CR](https://github.com/deislabs/ratify/issues/1089) + +[Susan] it looks like we will need introduce package dependency between Controller and executor. To detect version compact check, we might have to run the plugin to validate +- [Support logging in External Plugin](https://github.com/deislabs/ratify/issues/1022) +[Josh] will try out mapping plugin stdout to executor stdout + +- enable search on ratify website, https://github.com/deislabs/ratify-web/issues/27 +[Feyman] Will create a ratify email to use for search service registration + +- + +### Notes +https://youtu.be/FeUr07tgmzo + +------- + +## Dec 13 2023 + +### Announcement: + +### Attendees: +- Susan +- Akash +- Binbin +- Shiwei +- Feynman +- Yi +- Luis +- Josh + +### Actionable Agenda Items: +- [doc update about verifier reports](https://github.com/deislabs/ratify-web/pull/43) + +- [Yi] , +[validation results and suggestions](https://hackmd.io/VNd7br46SbCnh1TPb_wCjg?view) +and error improvement on + +``` +{ + "subject": "wabbitregistry.azurecr.io/net-monitor@sha256:d9e3524286eb0f273023e62c4fe5d9434d9d8353fdb627745752a36defec1b1e", + "isSuccess": false, + "name": "verifier-vulnerabilityreport", + "type": "vulnerabilityreport", + "message": "vulnerability report validation failed: report is older than maximum age:[24h]", + "extensions": { + "createdAt": "2023-12-12T05:22:08Z" + }, + "artifactType": "application/sarif+json" + }, + { + "isSuccess": false, + "name": "verifier-sbom", + "type": "sbom", + "message": "Original Error: (Original Error: (plugin failed with error: \"time=\\\"2023-12-13T07:08:15Z\\\" level=info msg=\\\"selected default auth provider: dockerConfig\\\"\\n\"), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-sbom, Component Type: verifier), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-sbom, Component Type: verifier", + "artifactType": "application/spdx+json" + }, + { + "isSuccess": false, + "name": "verifier-vulnerabilityreport", + "type": "vulnerabilityreport", + "message": "Original Error: (Original Error: (plugin failed with error: \"time=\\\"2023-12-13T07:08:15Z\\\" level=info msg=\\\"selected default auth provider: dockerConfig\\\"\\n\"), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-vulnerabilityreport, Component Type: verifier), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-vulnerabilityreport, Component Type: verifier", + "artifactType": "application/sarif+json" + } +``` +- holiday meeting schedules +- 1.2 traige +- +### Presentation/Discussion Agenda Items: +- _add your items_ + +### Notes + +recording: +https://youtu.be/DNidK-6viuk + +------------------- +## Dec 6 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash +- Binbin +- Shiwei +- Feynman +- Yi +- Luis + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- 1.1.0 delayed to early next week , waiting for rego for SBOM verifier +- [add multi-tenancy support discussions](https://github.com/deislabs/ratify/pull/1175) +TODO: check with gatekeeper team on Cluster/team lead roles + +recording: https://youtu.be/oCzn522Yoec + +------------------- +## Nov 29 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash +- Binbin +- Shiwei +- Feynman +- Yi + +### Actionable Agenda Items: +- Multi-tenancy model vote: https://github.com/deislabs/ratify/discussions/1192 +TODO: Please post to ratify Slack + +- [feat: add vulnerability report verifier](https://github.com/deislabs/ratify/pull/1173) + +[Susan:TODO] Create a tracking issue for adding version to CRDs + +[Yi/Akash]: does annotion string for creation time needs to be configurable? + +[Yi] For CICD pipeine, how could we reuse the existing rego? + +[Susan/Akash/Feyman] If customer requires constraints for SBOM and vul report, they would apply both constraints. + +### Presentation/Discussion Agenda Items: +- _add your items_ + +recording: https://youtu.be/UvnLIKPOv08 +t +------------------- +## Nov 22 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash +- Binbin +- Shiwei +- Feynman +- Yi + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- SBOM Prototype demo +Discussion items: + +1.How do customers attach their SBOM? +``` +${GITHUB_WORKSPACE}/bin/oras attach \ +--artifact-type application/spdx+json \ +${TEST_REGISTRY}/sbom:v0 \ +.staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json +``` + +[Akash] --artifact-type can be a comma seperated list +[Feynman] media type if not essential, we should remove the check + +2. supported license expression + +GFDL-1.3-only AND GPL-3.0-only AND LicenseRef-LGPL + +vs + +GFDL-1.3-only OR GPL-3.0-only OR LicenseRef-LGPL +3. Do we allow only package name with no package version +[Yi] we should also have rego to ensure a SBOM must exist +4. What if there are multiple SBOMs attached. Extension data +When there are multiple, should we only care about the latest one? +We should evaluate what we put into extension data +TODO: work with Yi on the verifier msg + +5. [CRD]([CR](https://github.com/deislabs/ratify/blob/main/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml)) plugin version + +TODO: Create a tracking issue for handling Plugin version + + +### notes + +recording: https://youtu.be/lnbRqFEwMBA + +------------------- +## Nov 15 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash +- Binbin +- Shiwei +- Feynman +- Yi +- Luis + +### Actionable Agenda Items: +- [docs: add multi-tenancy support discussions](https://github.com/deislabs/ratify/pull/1175) + +[Luis/Feyman] Would like to see more details customer scenario, and choose an option that delivers the best customer workflow for mutl-tenancy + +[Akash] Since cache are enrypted at test, We could try out different keys for different namespace + +[Susan] For logs, we could add namespace infomation to relevant logs, and the cluster admin can choose to forward specific log to team Leads. + +- [Venafi notation plugin support](https://github.com/deislabs/ratify-web/pull/32) +Binbin: Venafi is using certs of type: signingAuthority. Ratify currently doesn't +differentiate +between trustStore types, such as ca, tsa and signingAuthority. +This will causes a conflict if customer uses CA/TSA at the same time. + +### Presentation/Discussion Agenda Items: +- _add your items_ +recording: (https://youtu.be/sgP3mRCq1RE) +------------------- +## Nov 8 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash Singhal +- Luis Dieguez +- Yi Zha +- JunCheng Zhu +- Shiwei Zhang +- Feynman Zhou +- _add yourself_ + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- https://github.com/deislabs/ratify-web/issues/29 + +TODO: update guide so AKS customer are encouraged to use image integrity + +We can add also mention the manual work around + +TODO: Add a note to mention side by side open Ratity with AKS addon is not supported + +- https://github.com/deislabs/ratify/issues/1160 + +- discussion of default Ratify rego policy + +Question: what is the new default path once we deprecate configPolicy. + +### notes + +recording: https://youtu.be/adCCiGVZcjM + +------------------- +## Nov 1 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Akash Singhal +- Luis Dieguez +- Yi Zha +- Binbin Li +- Shiwei Zhang +- Feynman Zhou +- Sajay Antony +- _add yourself_ + +### Actionable Agenda Items: +- review [allow multiple notationCert in default chart](https://github.com/deislabs/ratify/pull/1151) +- vuln report support https://hackmd.io/GHsUE5qBRwGyhNSsajth2Q?view + +Sajay Antony: Is it possible to uplift this into the CI pipeline or a regular job that does this verification and attach a derived artifact that ratify can simply verify? Basically precanned signed response? + + +Sajay Antony: $ratify --verify --attach --policy ... + + +Binbin Li: currently policy is specified in a json config, something like: ratify --verify --config ... + + +Sajay Antony: As long as we an pre-attach the attestation then it makes this easier. +I would like to see if we can use notation to attach an attestation with all the verification pre baked. + +It is also a valid scenario that customer deploys docker hub image with CA1, and SBOM signed with CA2. Ratify should consider how to associate SBOM verification and how to specify the associated validation cert for this SBOM. +### Presentation/Discussion Agenda Items: +- _add your items_ + +### notes + +recording: https://youtu.be/__k6MIcUVPs + +------------------- +## Oct 25 2023 + +### Announcement: + +### Attendees: +- _add yourself_ + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- [helm] Make default verifier more configurable, [issue 1145](https://github.com/deislabs/ratify/issues/1145) +Can we use helmLoop? +How should we handle breaking changes in helm chart? + +- v1.1.0 Triage +TODO: we should move things out of 1.0 and close it out + +- Ratify /Chart download telemetry +TODO: maintainers please send your acct detail to Binbin to be added as artifact hub Ratify owner/contributor + +- _add your items_ +### Notes: +recording: https://youtu.be/wvED3x0pTCo + +------------------- +## Oct 18 2023 + +### Announcement: + +### Attendees: +- _add yourself_ +- Feynman Zhou +- Sajay Antony +- Susan Shi +- Akash Singhal +- Binbin Li +- Juncheng Zhu +- Luis Dieguez +- Yi Zha +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Discuss where should we keep our spec/design doc. We have two goals , having docs discoverable and been able to keep track of comments/discussions. +For future, we can keep docs in a Ratify repo. Kyverno keeps their design doc in a design repo +For existing hackMd doc, we should look at how to export them. +- performance testing doc, https://github.com/deislabs/ratify-web/pull/16 +- CRD conversion webhook: + - `metadata.name` and `metadata.namespace` are immutable. + - Install `cert-manager` to manage certs. + - Hardcode `namespace` to CRD. + This item will be moved to FUture milestone given the constraint that metadata.name is immutable. We can pull this back in based on customer need. +- Set date for next milestone +v1.1.0 temporialy set to Dec8th. + +recording: https://youtu.be/lNZk8xa6C2E + +------------------- +## Oct 11 2023 + +### Attendees: +- Sajay Antony +- Susan Shi +- Akash Singhal +- Binbin Li +- Juncheng Zhu +- Luis Dieguez +- _add yourself_ + +### Actionable Agenda Items: + + +### Presentation/Discussion Agenda Items: +- add performance testing doc, https://ratify.dev/docs/1.0/reference/performance +- Capturing some of the discussion we had around release cadence and the release milestones. +Sajay shared K8 ( doc link) follows a 3 times a year release cadence s , How do we feel if we start with the same cadence and adjust if needed? We last released end of Sep , putting 1.1.0 in late Jan , 1.2.0 late May? + +[Shiwei] Ratify could possibly have a short release cycle , e,g. monthly or every two month. +[Sajay] My vote would be to align with K8s do avoid any confusion with the community. + +[Akash] we should keep chart and app version consistent. +We discussed two patterns for our intermediary milestones. There are two advantage of having betaX milestones , #1 this will allows us to following a monthly release cadence. #2, for new features we are adding to beta, there are still room for changes based on customer feedback. Going straight to RC have a lower release cost, and also indicte the product is more stable. + +Options: + +1.0.0 (latest release) -> 1.1.0.RCX -> 1.1.0 +1.0.0 (latest release) -> 1.1.0.BetaX -> 1.1.0.RCX -> 1.1.0 + + Options: + * 1.0.0 (latest release) -> 1.1.0.RC1 -> 1.1.0 + * 1.0.0 (latest release) -> 1.1.0.Beta1 -> 1.1.0.RCX -> 1.1.0 + +Capturing summary of community meeting discussion: + +Given Ratify is still in early project stage, we have a goal of more frequent release to get early customer feedback. +We are proposing pushing a minor release every two month ( monthly release maybe too costly), if release contains complex features accross many compoments, a RC release should be considered to allow for bug report/fixes. + +- ORAS OCI store index race conditions +This probably requires ratify to add a primitive lock + +Recording: https://youtu.be/YU69CqxbGP8 + +------------------- +## Oct 4 2023 + +### Announcement: +Due to anticipating little quorum, today's community call has been cancelled. Please let us know if you have any updates or new issues through the Slack channel. + +### Attendees: +- _add yourself_ + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: + +### Notes: +------------------- +## Sep 27 2023 + +### Announcement: +Ratify v1 has been released. Spread the word in your socials. + +### Attendees: +- Akash Singhal +- Juncheng Zhu +- Ha Duong Alfie +- Binbin Li +- Luis Dieguez +- Susan Shi + +### Actionable Agenda Items: +- + +### Presentation/Discussion Agenda Items: +Triaged latest issues and assigned to V1.1 Beta release. + +### Notes: + +recording: https://youtu.be/83PEg_O6Lpc + +--------------- +## Sep 20 2023 + +### Announcement: + +### Attendees: +- Yi Zha +- Shiwei Zhang +- Sajay Antony +- Luis Dieguez +- JunCHeng Zhu +- Akash Singhal +- Feynman Zhou +- Susan Shi +- Binbin Li + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Negative testing is still WIP. We may need one more day to finalize it and determine the result tomorrow +- 1.0 Releae +- [SLSA verifying artifacts](https://slsa.dev/spec/v1.0/verifying-artifacts) +- Rego Template PR: https://github.com/deislabs/ratify-web/pull/6 +- [Failed to use a on-premises registry with Ratify reported by an user in Slack](https://cloud-native.slack.com/archives/C03T3PEKVA9/p1694526608399809). We might need to prioritize a solution for this case since it impacts the first trial experience (Feynman) + +### Notes: + +recording: https://youtu.be/NP_knZQQMBs + +--------------- +## Sep 13 2023 + +### Announcement: + +### Attendees: +- _add yourself_ + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- doc migration from ratify repo to website +- 1.0 Status check +- negative test ( by tuesday/Wed) +Q: what about security related testing? + +https://github.com/ossf/scorecard + +https://artifacthub.io/packages/helm/kyverno/kyverno?modal=security-report + +- Discuss whats is the next milestone + +referring gatekeeper milestonse, it looks like we can also follow a alpha->beta->RC -> 1.1 path + + +E.g. v3.13.0-beta.0 -> +v3.13.0-beta.1 +v3.13.0-rc.1 -> v3.13.0 + + +### Notes: + +Question: when we make doc updates in ratify repo, how does it sync to website repo? + +What should ratify keep design docs? +Export existing hackmd deisgn and check in to repo. We can also keep spec in a separate repo or directory. + +add to Next week agenda ( reading homework): +https://slsa.dev/spec/v1.0/verifying-artifacts + +https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/2572-release-cadence/README.md + +https://kubernetes.io/blog/2021/07/20/new-kubernetes-release-cadence/ + +recording: https://youtu.be/JYh-VaCXLh8 + +--------------- +## Sep 6 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Yi Zha +- Xinhe Li +- Luis Dieguez +- Feynman Zhou +- Binbin Li +- Juncheng Zhu +- _add yourself_ + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- RC8 release , will proceed with releaseing this week +- [same certificateStore name in different namespace](https://github.com/deislabs/ratify/issues/1061) Curently Ratify doesn't support namespaces, it reads all CRs from all namespace, TO be discussed: what is the expected behaviour? +- [Evaluate if Ratify CRDs is ready for 1.0](https://github.com/deislabs/ratify/issues/1060) +If negative tests show CRD is stable enough we should bump up to 1.0. Question: what about policy that is currently in alpha? any new CRD , do they start at alpha? +- GA release +- review Negative test cases for Ratify in this [doc](https://hackmd.io/NBHXfkM7QzKBZxsqnukg_A?view) +- _add your items_ + +### Notes: + +recording:https://youtu.be/U6VOJe2mtNU + +--------------- +## Aug 30 2023 + +### Announcement: + +### Attendees: +- Susan +- Feynman +- Akash +- Shiwei +- Binbin +- Juncheng +- Luis +- Xinhe + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- [Create Kubernetes support versioning strategy](https://github.com/deislabs/ratify/issues/1015) +Would be nice to have a matrix like https://istio.io/latest/docs/releases/supported-releases/#support-status-of-istio-releases +We should align our self with Gatekeeper k8 support +Regards to CRDS, we should update k8 version in CrdDocker file when we update k8 matrix. +- [Implement Readiness Probes](https://github.com/deislabs/ratify/issues/977) +We will cost this first and determine when to release this. But sounds like we do need a new endpoint. + +- [RC8 issues](https://github.com/deislabs/ratify/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.0.0-rc.8) +TODO: Create a issue for bumping up version of CRD + +- Negative test cases for Ratify is summarized in this [doc](https://hackmd.io/NBHXfkM7QzKBZxsqnukg_A?view) and tracked in [issue #982](https://github.com/deislabs/ratify/issues/982) + +Please review and add more scenarios if needed. we will review this [doc](https://hackmd.io/NBHXfkM7QzKBZxsqnukg_A?view) and assign scenarios for testing. +### Notes: +GK 3.13 introduced a 3 minute TTL. Tests are failing. There is an issue to add in the 3.14 release a flag to adjust the TTL. + +recording: https://youtu.be/HdVEBO5wf00 + +--------------- +## Aug 23 2023 + +### Announcement: + +### Attendees: +- Susan +- Akash +- Yi +- Binbin +- Juncheng +- Shiwei +- Feynman +- Toddy +- Luis +- Josh + +### Actionable Agenda Items: +- [refactor: refactor log](https://github.com/deislabs/ratify/issues/984) +We don't current have a way to get logs from extern plugin, TODO: lets create an issue so plugin developers can vote on this +- [doc: broken links in crd configuration doc](https://github.com/deislabs/ratify/pull/1008) +- [chore: update constraint templates](https://github.com/deislabs/ratify/pull/1017) +### Presentation/Discussion Agenda Items: +- [Update Terraform with getSecret](https://github.com/deislabs/ratify/issues/973) (JoshDuffney) +Terraform will assign both getCert and getSecret permissions as a worka around +- _add your items_ + +### Notes: + +Ratify Performance Load Testing for Azure, please review at https://hackmd.io/@akashsinghal/HJnMY4K22 + +recording:https://youtu.be/h4ihIX5JTbY + +--------------- + +## Aug 16 2023 + +### Announcement: + +### Attendees: +- Susan +- Akash +- Binbin +- Luis +- Shiwei +- Feynman +- Yi +- Toddy +- Xinhe +- Sajay + +### Actionable Agenda Items: +- For supporting ARM64 ratify, we should also validate high availability support in ARM64 arch. +- Moving authPRoviders support to v1.1 +### Presentation/Discussion Agenda Items: +- Consider to move the HA feature from "Experimental" to "Stable" and define the maturity criteria (Feynman) +- RC-7 Readiness ( Luis) +- TODO: create a v1.1 milestone + +### Notes: +Feature gate criteria: +https://hackmd.io/mWebogJ2QJyPzTH_Th9Yuw?view + +recording: https://youtu.be/CHjbuKzQQXU + +## Aug 09 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Feynman Zhou +- Yi Zha +- Shiwei Zhang +- Luis Dieguez +- Sajay Antony + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- [ratify does not support multiple store exists](https://github.com/deislabs/ratify/issues/974) + +- RC7 breaking changes + * chore!: update-notation ref (#940) + * TODO: Doc needed for upgrade to Ratify RC7 + * Create a issue to discuss if we need to remove OCI artifact manifest support by GA +- Considering negative testing before releasing v1.0.0 ( negative test for AKS and Gatekeeper) + * Create tracking issue for negative testing testplan and execution +### Notes: + +recording: https://youtu.be/TbAPc2r-9II + +--------------- +## Aug 02 2023 + +### Announcement: + +### Attendees: +- Luis Dieguez +- Susan Shi +- Binbin Li +- Yi Zha +- Akash Singhal +- Shiwei Zhang +- Sajay Antony +- Josh Duffney +- Toddy Mladenov + +### Actionable Agenda Items: +- _add your items_ + +### Presentation/Discussion Agenda Items: +- Merge is currently blocked due to bug in SBOM tool [issue 296 ](https://github.com/microsoft/sbom-tool/issues/296) + +Since we don't have any urgent need to merge PR, we will wait for a day for a fix. + +- Support Mutation and Verification on Init Containers, https://github.com/deislabs/ratify/issues/950 + +- [Migrate to latest Azure container registry SDK ](https://github.com/deislabs/ratify/issues/959) + +we should start the investigation asap +### Notes: + +We shoud update getCert permission to getSecret +https://github.com/duffney/secure-supply-chain-on-aks/blob/main/terraform/main.tf#L133 + +recording: https://youtu.be/DveKFKhzU-Q + +--------------- +## July 26 2023 + +### Attendees: +- Susan Shi +- Feynman Zhou +- Binbin Li +- Shiwei Zhang +- Xinhe Li +- Yi Zha +- Luis Dieguez +- Akash Singhal +- Sajay Antony +- Manish Kumar Singh +- _add yourself_ + +### Actionable Agenda Items: +- [chore: update-notation ref](https://github.com/deislabs/ratify/pull/940) +[Yi] ideally we should make this backwards compatible , we need to follow up with Junchen +- [feat: optional image mutation in helm chart](https://github.com/deislabs/ratify/pull/944) +[Feynman] we should add a warning to note mutation has been turned off +- [helm file support](https://github.com/deislabs/ratify/pull/948) + +[Feynman] Using helm file for quick start is good, but some Concerns on using [helmfile](https://helmfile.readthedocs.io/en/latest/) for scaling Ratify from single instance to HA + + Helm file is good for clean install, we should add a note about scenario user already have some components installed + + We might also want to add HA guide when User already have daper/redis installed. + +- TODO: We need to document current ratify helm upgrade behaviour +- Moved https://github.com/deislabs/ratify/issues/744 to GA , we need to provide perf results for single instance vs HA mode. +### Presentation/Discussion Agenda Items: + + +- Branching strategy and release post 1.0 + - Patches and support criteria? + +TODO: we need a github tracking item + +### Notes: + +recording: https://youtu.be/jqsQUreOK_0 + +--------------- +## July 19 2023 + +### Announcement: + +### Attendees: +- Susan +- Binbin +- Akash +- Yi +- Shiwei +- Sajay +- Feynman +- Luis +- + +### Actionable Agenda Items: +- [feat: unify caches, add ristretto and Dapr cache providers](https://github.com/deislabs/ratify/pull/901) + +- [feat: add policy crd and controller][InternetShortcut] +URL=https://df.onecloud.azure-test.net/?Microsoft_Azure_ContainerRegistries=true#dashboard +(https://github.com/deislabs/ratify/pull/933) + +### Presentation/Discussion Agenda Items: +From last wk: Remove TLS cert generation in Helm templates? https://github.com/deislabs/ratify/issues/913 + + +- RC 7 date, TODO: create 1.1 milestone, should Jimmy continue to be in the reviewer list? + +New RC7 date will be Aug25th 2023 +- Jesse will help confirm the maintainer candidate who can represent AWS +- [Implement experimental feature flag](https://github.com/deislabs/ratify/issues/932) + +- Hashicorp go plugin over GPRC investigation, https://hackmd.io/qPe4Tl5wQCW_0ot2KKBUNQ + +[Akash/Susan] Some plugins might need to share cache. Verifier plugin should have no problem accessing oras blob cache. Can verifier share other in-memoery cache. Maybe its ok cosign verifier doesn't share memory cache with notary plugin. Maybe different instance of verifier can share in memoery cache if they are running in a single grpc. + +- Migrate the docs to ratify.dev [issue 938](https://github.com/deislabs/ratify/issues/938) +- + +### Notes: +recording: https://youtu.be/VCtBkXpGSD0 + +--------------- +## July 12 2023 + +### Announcement: + +### Attendees: +- Susan Shi +- Binbin Li +- Yi Zha +- Akash Singhal +- Feynman +- Juncheng Zhu +- Shiwei Zhang +- Sajay Antony +- _add yourself_ + +### Actionable Agenda Items: + - [feat: add opa engine and support Rego policy](https://github.com/deislabs/ratify/pull/798) + +We will maintain both config policy and Rego. Some customers might just want a simple policy configuration where other customer need a more advanced rego. We will wait for more feedback before we set rego as the default. +- [feat: unify caches, add ristretto and Dapr cache providers](https://github.com/deislabs/ratify/pull/901) + +We plan to merge this into RC7 behind a feature flag. We need to differentiate between experimental and features. TODO: create a github issue for adding experimental flag. + +### Presentation/Discussion Agenda Items: +- OCI Artifact removal plan +we will be keeping this as notary removed the capability to genereate oci artifact, but still have the ability to verify oci artifact. +- Status update on GPRC investigation, https://hackmd.io/qPe4Tl5wQCW_0ot2KKBUNQ +Agreed this is not a GA feature. We should for this into 1.1 milestone for now. + +We didn't get to discuss the items below, moved to next week's meeting. + +- Build multi-arch Ratify image: https://github.com/deislabs/ratify/issues/929 +- Remove TLS cert generation in Helm templates? https://github.com/deislabs/ratify/issues/913 + + +### Notes: + +recording: https://youtu.be/Z36EYQj7YsI + +--------------- +## July 5 2023 + +### Announcement: + +### Attendees: +- Binbin Li +- Feynman Zhou +- Luis Diegues +- Susan Shi +- Toddy +- Xinhe Li + +### Actionable Agenda Items: + - [feat: add opa engine and support Rego policy](https://github.com/deislabs/ratify/pull/798) + Waiting for Akash's feedback +- PR: [Use latest sbom-tool or stay with a fixed version](https://github.com/deislabs/ratify/pull/917) +We ve decided to stay with using the latest version as dependabot will autoupdate version in src code. Binbin will check what is the latestdownload version vs dependabot version. +- [build: upgrade e2e test from notation rc3->rc7](https://github.com/deislabs/ratify/pull/919/files) +Juncheng has issue running CIs, if this continues to be an issue in future PRs, we need to review his permissions. +- [Will pluggable design for Certificate Store be planned before v1.0.0?](https://github.com/deislabs/ratify/issues/908) Certificate Store as a plugin being planned for Post v1, we are hoping to deliver the grpc infra work for v1. +### Presentation/Discussion Agenda Items: +- RC 6 , we will reuse the rc5 branch and only apply the workflow fix +- [Enable Service-to-Service Communication using gRPC](https://github.com/deislabs/ratify/issues/191) + +Should we reuse existing protos? +https://github.com/deislabs/ratify/blob/main/experimental/ratify/proto/v1/verifier.proto + +Tho it doesn't look like it matchs our existing [interface](https://github.com/deislabs/ratify/blob/555b7625d0c346ecd177729c801df1e2f5bf3ae4/pkg/verifier/api.go#L39) + +- Discussion: Remove System_error from constraint template: https://github.com/deislabs/ratify/discussions/920 + +### Notes: + +recording: https://youtu.be/n17xV2hWDuM diff --git a/charts/ratify/Chart.yaml b/charts/ratify/Chart.yaml index 173ba0f0b..945bb14a6 100644 --- a/charts/ratify/Chart.yaml +++ b/charts/ratify/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: ratify description: A Helm chart for Ratify -version: 1.12.0 -appVersion: v1.1.0 -home: https://github.com/deislabs/ratify -icon: https://raw.githubusercontent.com/deislabs/ratify/main/logo.svg +version: 1.14.0 +appVersion: v1.3.0 +home: https://github.com/ratify-project/ratify +icon: https://raw.githubusercontent.com/ratify-project/ratify/main/logo.svg diff --git a/charts/ratify/README.md b/charts/ratify/README.md index 1f4fd7bc1..af3600209 100644 --- a/charts/ratify/README.md +++ b/charts/ratify/README.md @@ -3,7 +3,7 @@ ## Get Repo Info ```console -helm repo add ratify https://deislabs.github.io/ratify +helm repo add ratify https://ratify-project.github.io/ratify helm repo update ``` @@ -30,12 +30,16 @@ _See [helm install](https://helm.sh/docs/helm/helm_install/) for command documen $ helm upgrade -n gatekeeper-system [RELEASE_NAME] ratify/ratify ``` +## Deprecation Policy + +Values marked `# DEPRECATED` in the `values.yaml` as well as **DEPRECATED** in the below parameters will NOT be supported in the next major version release. Existing functionality will remain backwards compatible until the next major version release. + ## Parameters | Parameter | Description | Default | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -| image.repository | Ratify app image | `ghcr.io/deislabs/ratify` | -| image.crdrepository | Ratify CRD install Image | `ghcr.io/deislabs/ratify-crds` | +| image.repository | Ratify app image | `ghcr.io/ratify-project/ratify` | +| image.crdrepository | Ratify CRD install Image | `ghcr.io/ratify-project/ratify-crds` | | image.tag | Image tag | `` | | image.pullPolicy | Image pull policy | `IfNotPresent` | | nameOverride | Overrides the ratify.name used to determine the ratify full name template | `` | @@ -43,10 +47,18 @@ $ helm upgrade -n gatekeeper-system [RELEASE_NAME] ratify/ratify | replicaCount | The number of Ratify replicas in deployment | 1 | | affinity | Pod affinity for the Ratify deployment | `{}` | | tolerations | Pod tolerations for the Ratify deployment | `[]` | -| notationCert | Public certificate/certificate chain used to create inline certstore used by Notation verifier. This value has been ***deprecated*** , and will be removed in future releases of Ratify. Please switch to ```notationCerts``` to specify an array of verification certificates | `` | | 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` | | cosign.enabled | Enables/disables cosign tag-based signature lookup in ORAS store. MUST be set to true for cosign verification. | `true` | -| cosign.key | Public certificate used by cosign verifier | `` | +| cosign.scopes | An array of scopes relevant to the single trust policy configured in Cosign verifier. A scope of '*' is a global wildcard character to represent all images apply. | `["*"]` | +| cosign.rekorURL | URL string reference to remote rekor server. If not specified, implementation will default to use Rekor public good instance `https://rekor.sigstore.dev`. | `` | +| cosign.tLogVerify | Enables/disables verification of presence of signature in Transparency log. | `true` | +| cosign.keyless.ctLogVerify | Enables/disables verification of presence of Secure Certificate Timestamp (SCT) in transparency log | `true` | +| cosign.keyless.certificateIdentity | String certificate identity used for exact identity match during verification. Either `certificateIdentity` or `certificateIdentityRegExp` MUST be defined, but both cannot be defined at together | `` | +| cosign.keyless.certificateIdentityRegExp | String certificate identity regular expression for identity matching during verification. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either `certificateIdentity` or `certificateIdentityRegExp` MUST be defined, but both cannot be defined together | `` | +| cosign.keyless.certificateOIDCIssuer | String certificate OIDC issuer for exact issuer matching during verification. Either `certificateOIDCIssuer` or `certificateOIDCIssuerRegExp` MUST be defined, but both cannot be defined together | `` | +| cosign.keyless.certificateOIDCIssuerRegExp | String certificate OIDC issuer regular expression for issuer matching during verification. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either `certificateOIDCIssuer` or `certificateOIDCIssuerRegExp` MUST be defined, but both cannot be defined together | `` | | vulnerabilityreport.enabled | Enables/disables installation of vulnerability report verifier | `false` | | vulnerabilityreport.passthrough | Enables/disables passthrough. All validation except `maximumAge` are disregarded and report content is added to verifier report | `false` | | vulnerabilityreport.schemaURL | URL for JSON schema to validate report against | `` | @@ -65,7 +77,7 @@ $ helm upgrade -n gatekeeper-system [RELEASE_NAME] ratify/ratify | resources.requests.memory | Memory request of Ratify Deployment | `512Mi` | | serviceAccount.create | Create new dedicated Ratify service account | `true` | | serviceAccount.name | Name of Ratify service account to create | `ratify-admin` | -| gatekeeper.version | Determines the Gatekeeper CRD versioning | `3.15.0` | +| gatekeeper.version | Determines the Gatekeeper CRD versioning | `3.17.0` | | gatekeeper.namespace | Namespace Gatekeeper is installed | `gatekeeper-system` | | instrumentation.metricsEnabled | Initializes the configured metrics provider | `true` | | instrumentation.metricsType | Specifies the metrics provider type | `prometheus` | @@ -120,11 +132,19 @@ $ helm upgrade -n gatekeeper-system [RELEASE_NAME] ratify/ratify | azureWorkloadIdentity.clientId | ClientID of AAD application/Managed identity associated with Workload Identity | `` | | azureManagedIdentity.clientId | ClientID of Managed identity | `` | | azureManagedIdentity.tenantId | TenantID of Managed Identity resource | `` | -| akvCertConfig.enabled | Enables/disables Azure Key Vault certificate store. If you are using a custom chart, certificate store should be referenced through a Verifier CR. References in ConfigMap will not be correctly resolved. | `false` | -| akvCertConfig.vaultURI | Vault URI for AKV configured | `` | -| akvCertConfig.cert1Name | Exact name of the certificate stored in AKV. This value has been ***deprecated*** , and will be removed in future releases of Ratify. Please switch to ```akvCertConfig.certificates``` to specify an array of certificates | `` | -| akvCertConfig.cert1Version | Exact version of certificate to use from AKV. This value has been ***deprecated*** , and will be removed in future releases of Ratify. Please switch to ```akvCertConfig.certificates``` to specify an array of verification certificates | `` | -| akvCertConfig.cert2Name | Exact name of the certificate stored in AKV. This value has been ***deprecated*** , and will be removed in future releases of Ratify. Please switch to ```akvCertConfig.certificates``` to specify an array of verification certificates | `` | -| akvCertConfig.cert2Version | Exact version of certificate to use from AKV. This value has been ***deprecated*** , and will be removed in future releases of Ratify. Please switch to ```akvCertConfig.certificates``` to specify an array of verification certificates | `` | -| akvCertConfig.certificates | An array of certificate objects identified by `name` and `version` stored in AKV | `` | -| akvCertConfig.tenantId | TenantID of the configured AKV resource | `` | +| azurekeyvault.enabled | Enables/disables Azure Key Vault key management provider. If you are using a custom chart, certificate store should be referenced through a Verifier CR. | `false` | +| azurekeyvault.vaultURI | Vault URI for Azure Key Vault | `` | +| azurekeyvault.tenantId | Tenant ID of the configured Azure Key Vault resource | `` | +| azurekeyvault.certificates | An array of certificate objects identified by `name` and `version` (optional) stored in Azure Key Vault | `[]` | +| azurekeyvault.keys | An array of key objects identified by `name` and `version` (optional) stored in Azure Key Vault | `[]` | +| azurekeyvault.refreshInterval | time duration to refresh the certificates/keys. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". Example: 1h, 30m, 1h30m. If it's not set, the refresh functionality will be disabled. | `` | +| notationCert | **DEPRECATED** Please switch to `notationCerts` to specify an array of verification certificates. Public certificate/certificate chain used to create inline certstore used by Notation verifier. | `` | +| akvCertConfig.enabled | **DEPRECATED** Please use `azurekeyvault.enabled` instead. Enables/disables Azure Key Vault certificate store. If you are using a custom chart, certificate store should be referenced through a Verifier CR. References in ConfigMap will not be correctly resolved. | `false` | +| akvCertConfig.vaultURI | **DEPRECATED** Please use `azurekeyvault.vaultURI` instead. Vault URI for AKV configured | `` | +| akvCertConfig.cert1Name | **DEPRECATED** Please use `azurekeyvault.certificates` instead. Exact name of the certificate stored in AKV. | `` | +| akvCertConfig.cert1Version | **DEPRECATED** Please use `azurekeyvault.certificates` instead. Exact version of certificate to use from AKV.certificates | `` | +| akvCertConfig.cert2Name | **DEPRECATED** Please use `azurekeyvault.certificates` instead. Exact name of the certificate stored in AKV. | `` | +| akvCertConfig.cert2Version | **DEPRECATED** Please use `azurekeyvault.certificates` instead. Exact version of certificate to use from AKV. | `` | +| akvCertConfig.certificates | **DEPRECATED** Please use `azurekeyvault.certificates` instead. An array of certificate objects identified by `name` and `version` stored in AKV | `` | +| akvCertConfig.tenantId | **DEPRECATED** Please use `azurekeyvault.certificates` instead. TenantID of the configured AKV resource | `` | +| cosign.key | **DEPRECATED** Please use `cosignKeys` instead. Public key used by cosign verifier | `` | diff --git a/charts/ratify/crds/certificatestore-customresourcedefinition.yaml b/charts/ratify/crds/certificatestore-customresourcedefinition.yaml index a554e497d..a07df31ab 100644 --- a/charts/ratify/crds/certificatestore-customresourcedefinition.yaml +++ b/charts/ratify/crds/certificatestore-customresourcedefinition.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: certificatestores.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io diff --git a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml index 29ddb906d..bbb7a9c13 100644 --- a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml +++ b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: keymanagementproviders.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -13,75 +12,89 @@ spec: listKind: KeyManagementProviderList plural: keymanagementproviders singular: keymanagementprovider - scope: Namespaced + scope: Cluster versions: - - additionalPrinterColumns: - - jsonPath: .status.issuccess - name: IsSuccess - type: boolean - - jsonPath: .status.brieferror - name: Error - type: string - - jsonPath: .status.lastfetchedtime - name: LastFetchedTime - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: KeyManagementProvider is the Schema for the keymanagementproviders - API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: KeyManagementProviderSpec defines the desired state of KeyManagementProvider - properties: - parameters: - description: Parameters of the key management provider - type: object - x-kubernetes-preserve-unknown-fields: true - type: - description: Name of the key management provider - type: string - type: object - status: - description: KeyManagementProviderStatus defines the observed state of - KeyManagementProvider - properties: - brieferror: - description: Truncated error message if the message is too long - type: string - error: - description: Error message if operation was unsuccessful - type: string - issuccess: - description: Is successful in loading certificate/key files - type: boolean - lastfetchedtime: - description: The time stamp of last successful certificate/key fetch - operation. If operation failed, last fetched time shows the time - of error - format: date-time - type: string + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + - jsonPath: .status.lastfetchedtime + name: LastFetchedTime + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: + KeyManagementProvider is the Schema for the keymanagementproviders + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: KeyManagementProviderSpec defines the desired state of KeyManagementProvider properties: - description: provider specific properties of the each individual certificate/key - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - issuccess - type: object - type: object - served: true - storage: true - subresources: - status: {} + refreshInterval: + default: "" + description: + Refresh interval for fetching the certificate/key files + from the provider. Only for providers that are refreshable. The + value is in the format of "1h30m" where "h" means hour and "m" means + minute. Valid time units are units are "ns", "us" (or "µs"), "ms", + "s", "m", "h". + type: string + parameters: + description: Parameters of the key management provider + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Name of the key management provider + type: string + type: object + status: + description: + KeyManagementProviderStatus defines the observed state of + KeyManagementProvider + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in loading certificate/key files + type: boolean + lastfetchedtime: + description: + The time stamp of last successful certificate/key fetch + operation. If operation failed, last fetched time shows the time + of error + format: date-time + type: string + properties: + description: provider specific properties of the each individual certificate/key + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml new file mode 100644 index 000000000..405517810 --- /dev/null +++ b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml @@ -0,0 +1,102 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedkeymanagementproviders.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedKeyManagementProvider + listKind: NamespacedKeyManagementProviderList + plural: namespacedkeymanagementproviders + singular: namespacedkeymanagementprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + - jsonPath: .status.lastfetchedtime + name: LastFetchedTime + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: + NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders + API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: + NamespacedKeyManagementProviderSpec defines the desired state + of NamespacedKeyManagementProvider + properties: + refreshInterval: + default: "" + description: + Refresh interval for fetching the certificate/key files + from the provider. Only for providers that are refreshable. The + value is in the format of "1h30m" where "h" means hour and "m" means + minute. Valid time units are units are "ns", "us" (or "µs"), "ms", + "s", "m", "h". + type: string + parameters: + description: Parameters of the key management provider + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Name of the key management provider + type: string + type: object + status: + description: + NamespacedKeyManagementProviderStatus defines the observed + state of NamespacedKeyManagementProvider + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in loading certificate/key files + type: boolean + lastfetchedtime: + description: + The time stamp of last successful certificate/key fetch + operation. If operation failed, last fetched time shows the time + of error + format: date-time + type: string + properties: + description: provider specific properties of the each individual certificate/key + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml b/charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml new file mode 100644 index 000000000..d5144bc3b --- /dev/null +++ b/charts/ratify/crds/namespacedpolicy-customresourcedefinition.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedpolicies.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedPolicy + listKind: NamespacedPolicyList + plural: namespacedpolicies + singular: namespacedpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedPolicy is the Schema for the namespacedpolicies API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: NamespacedPolicySpec defines the desired state of NamespacedPolicy + properties: + parameters: + description: Parameters for this policy + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Type of the policy + type: string + type: object + status: + description: NamespacedPolicyStatus defines the observed state of NamespacedPolicy + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if policy is not successfully applied. + type: string + issuccess: + description: Is successful while applying the policy. + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/crds/namespacedstore-customresourcedefinition.yaml b/charts/ratify/crds/namespacedstore-customresourcedefinition.yaml new file mode 100644 index 000000000..5b6f0c346 --- /dev/null +++ b/charts/ratify/crds/namespacedstore-customresourcedefinition.yaml @@ -0,0 +1,91 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedstores.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedStore + listKind: NamespacedStoreList + plural: namespacedstores + singular: namespacedstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedStore is the Schema for the namespacedstores API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: NamespacedStoreSpec defines the desired state of NamespacedStore + properties: + address: + description: Plugin path, optional + type: string + name: + description: Name of the store + type: string + parameters: + description: Parameters of the store + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from, optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version of the store plugin. Optional + type: string + required: + - name + type: object + status: + description: NamespacedStoreStatus defines the observed state of NamespacedStore + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml b/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml new file mode 100644 index 000000000..74e51a4b3 --- /dev/null +++ b/charts/ratify/crds/namespacedverifier-customresourcedefinition.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedverifiers.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedVerifier + listKind: NamespacedVerifierList + plural: namespacedverifiers + singular: namespacedverifier + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedVerifier is the Schema for the namespacedverifiers + API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier + properties: + address: + description: '# Optional. URL/file path' + type: string + artifactTypes: + description: The type of artifact this verifier handles + type: string + name: + description: Name of the verifier + type: string + parameters: + description: Parameters for this verifier + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from, optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version of the verifier plugin. Optional + type: string + required: + - artifactTypes + - name + type: object + status: + description: NamespacedVerifierStatus defines the observed state of NamespacedVerifier + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/ratify/crds/policy-customresourcedefinition.yaml b/charts/ratify/crds/policy-customresourcedefinition.yaml index 40f71392c..4a98533ab 100644 --- a/charts/ratify/crds/policy-customresourcedefinition.yaml +++ b/charts/ratify/crds/policy-customresourcedefinition.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: policies.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io diff --git a/charts/ratify/crds/store-customresourcedefinition.yaml b/charts/ratify/crds/store-customresourcedefinition.yaml index 46aa5a8be..88ef5af3b 100644 --- a/charts/ratify/crds/store-customresourcedefinition.yaml +++ b/charts/ratify/crds/store-customresourcedefinition.yaml @@ -2,8 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: stores.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io diff --git a/charts/ratify/crds/verifier-customresourcedefinition.yaml b/charts/ratify/crds/verifier-customresourcedefinition.yaml index 0d242aef8..d6cf26108 100644 --- a/charts/ratify/crds/verifier-customresourcedefinition.yaml +++ b/charts/ratify/crds/verifier-customresourcedefinition.yaml @@ -2,8 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: verifiers.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io diff --git a/charts/ratify/templates/NOTES.txt b/charts/ratify/templates/NOTES.txt new file mode 100644 index 000000000..d3ea52406 --- /dev/null +++ b/charts/ratify/templates/NOTES.txt @@ -0,0 +1,6 @@ +{{- if not (or .Values.notation.enabled .Values.cosign.enabled .Values.sbom.enabled .Values.vulnerabilityreport.enabled) }} +*********************************************************** +WARNING: All verifiers are disabled. +It's recommended that at least one is enabled for proper functionality. +*********************************************************** +{{- end }} \ No newline at end of file diff --git a/charts/ratify/templates/_helpers.tpl b/charts/ratify/templates/_helpers.tpl index e57008c31..cc56acb9e 100644 --- a/charts/ratify/templates/_helpers.tpl +++ b/charts/ratify/templates/_helpers.tpl @@ -146,4 +146,20 @@ Set the namespace exclusions for Assign {{- if and (ne .Release.Namespace $gkNamespace) (ne .Release.Namespace "kube-system") }} - {{ .Release.Namespace | quote}} {{- end }} +{{- end }} + +{{/* +Choose cosign legacy or not. Determined by if cosignKeys are provided or not +OR if azurekeyvault is enabled and keys are provided +OR if keyless is enabled and certificateIdentity, certificateIdentityRegExp, certificateOIDCIssuer, or certificateOIDCIssuerExp are provided +*/}} +{{- define "ratify.cosignLegacy" -}} +{{- $cosignKeysPresent := gt (len .Values.cosignKeys) 0 -}} +{{- $azureKeyVaultEnabled := .Values.azurekeyvault.enabled -}} +{{- $azureKeyVaultKeysPresent := gt (len .Values.azurekeyvault.keys) 0 -}} +{{- if or $cosignKeysPresent (and $azureKeyVaultEnabled $azureKeyVaultKeysPresent) .Values.cosign.keyless.certificateIdentity .Values.cosign.keyless.certificateIdentityRegExp .Values.cosign.keyless.certificateOIDCIssuer .Values.cosign.keyless.certificateOIDCIssuerExp -}} +false +{{- else }} +true +{{- end }} {{- end }} \ No newline at end of file diff --git a/charts/ratify/templates/akv-key-management-provider.yaml b/charts/ratify/templates/akv-key-management-provider.yaml index 548080600..132c14fef 100644 --- a/charts/ratify/templates/akv-key-management-provider.yaml +++ b/charts/ratify/templates/akv-key-management-provider.yaml @@ -1,4 +1,4 @@ -{{- if .Values.akvCertConfig.enabled }} +{{- if or .Values.azurekeyvault.enabled .Values.akvCertConfig.enabled }} apiVersion: config.ratify.deislabs.io/v1beta1 kind: KeyManagementProvider metadata: @@ -8,8 +8,17 @@ metadata: helm.sh/hook-weight: "5" spec: type: azurekeyvault + {{- if .Values.azurekeyvault.refreshInterval }} + refreshInterval: {{ .Values.azurekeyvault.refreshInterval }} + {{- end }} parameters: - vaultURI: {{ required "vaultURI must be provided when AKV cert config is enabled" .Values.akvCertConfig.vaultURI }} + {{- if .Values.azurekeyvault.vaultURI }} + vaultURI: {{ .Values.azurekeyvault.vaultURI }} + {{- else if .Values.akvCertConfig.vaultURI }} + vaultURI: {{ .Values.akvCertConfig.vaultURI }} + {{- else }} + {{- fail "vaultURI must be provided when azurekeyvault is enabled. please specify azurekeyvault.vaultURI" }} + {{- end }} certificates: {{- if .Values.akvCertConfig.cert1Name }} - name: {{ .Values.akvCertConfig.cert1Name }} @@ -25,6 +34,25 @@ spec: version: {{ .version }} {{- end }} {{- end }} - tenantID: {{ required "tenantID must be provided when AKV cert config is enabled" .Values.akvCertConfig.tenantId }} + {{- range .Values.azurekeyvault.certificates }} + {{- if .name }} + - name: {{ .name }} + version: {{ .version }} + {{- end }} + {{- end }} + keys: + {{- range .Values.azurekeyvault.keys }} + {{- if .name }} + - name: {{ .name }} + version: {{ .version }} + {{- end }} + {{- end }} + {{- if .Values.azurekeyvault.tenantId }} + tenantID: {{ .Values.azurekeyvault.tenantId }} + {{- else if .Values.akvCertConfig.tenantId }} + tenantID: {{ .Values.akvCertConfig.tenantId }} + {{- else }} + {{- fail "tenantID must be provided when azurekeyvault is enabled. please specify azurekeyvault.tenantId" }} + {{- end }} clientID: {{ required "clientID must be provided when use workload identity in akv" .Values.azureWorkloadIdentity.clientId }} {{ end }} \ No newline at end of file diff --git a/charts/ratify/templates/deployment.yaml b/charts/ratify/templates/deployment.yaml index 912eda48a..7a979ca43 100644 --- a/charts/ratify/templates/deployment.yaml +++ b/charts/ratify/templates/deployment.yaml @@ -88,7 +88,7 @@ spec: name: healthz protocol: TCP volumeMounts: - {{- if .Values.cosign.enabled }} + {{- if and .Values.cosign.enabled .Values.cosign.key }} - mountPath: "/usr/local/ratify-certs/cosign" name: cosign-certs readOnly: true @@ -145,7 +145,7 @@ spec: resources: {{- toYaml .Values.resources | nindent 12 }} volumes: - {{- if .Values.cosign.enabled }} + {{- if and .Values.cosign.enabled .Values.cosign.key }} - name: cosign-certs secret: secretName: {{ include "ratify.fullname" . }}-cosign-certificate diff --git a/charts/ratify/templates/inline-key-management-provider.yaml b/charts/ratify/templates/inline-key-management-provider.yaml index aafbad449..0d46ae200 100644 --- a/charts/ratify/templates/inline-key-management-provider.yaml +++ b/charts/ratify/templates/inline-key-management-provider.yaml @@ -28,4 +28,20 @@ spec: parameters: contentType: certificate value: {{ $cert | quote }} +--- +{{- end }} +{{- range $i, $key := .Values.cosignKeys }} +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: {{$fullname}}-cosign-inline-key-{{$i}} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: {{ $key | quote }} +--- {{- end }} \ No newline at end of file diff --git a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml index 854cf542b..515843f9c 100644 --- a/charts/ratify/templates/ratify-manager-role-clusterrole.yaml +++ b/charts/ratify/templates/ratify-manager-role-clusterrole.yaml @@ -31,6 +31,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: @@ -57,6 +83,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: @@ -109,6 +161,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: @@ -135,6 +213,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get + - patch + - update - apiGroups: - externaldata.gatekeeper.sh resources: diff --git a/charts/ratify/templates/secret.yaml b/charts/ratify/templates/secret.yaml index b74390f49..5e8173bfa 100644 --- a/charts/ratify/templates/secret.yaml +++ b/charts/ratify/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if .Values.cosign.enabled }} +{{- if and .Values.cosign.enabled .Values.cosign.key}} apiVersion: v1 kind: Secret metadata: diff --git a/charts/ratify/templates/verifier.yaml b/charts/ratify/templates/verifier.yaml index eea0c33c0..8ac23e5d8 100644 --- a/charts/ratify/templates/verifier.yaml +++ b/charts/ratify/templates/verifier.yaml @@ -1,5 +1,5 @@ {{- $fullname := include "ratify.fullname" . -}} ---- +{{- if .Values.notation.enabled }} apiVersion: config.ratify.deislabs.io/v1beta1 kind: Verifier metadata: @@ -14,19 +14,18 @@ spec: parameters: verificationCertStores: certs: - {{- if .Values.akvCertConfig.enabled }} + {{- if or .Values.azurekeyvault.enabled .Values.akvCertConfig.enabled }} - kmprovider-akv - {{- else }} - {{- if .Values.notationCert }} - {{- if .Values.notationCerts }} - {{- fail "Please specify notation certs with .Values.notationCerts, single certificate .Values.notationCert has been deprecated, will soon be removed." }} - {{- end }} + {{- end }} + {{- if .Values.notationCert }} + {{- if .Values.notationCerts }} + {{- fail "Please specify notation certs with .Values.notationCerts, single certificate .Values.notationCert has been deprecated, will soon be removed." }} + {{- end }} - {{$fullname}}-notation-inline-cert - {{- end }} - {{- range $i, $cert := .Values.notationCerts }} + {{- end }} + {{- range $i, $cert := .Values.notationCerts }} - {{$fullname}}-notation-inline-cert-{{$i}} - {{- end }} - {{- end }} + {{- end }} trustPolicyDoc: version: "1.0" trustPolicies: @@ -39,6 +38,7 @@ spec: - ca:certs trustedIdentities: - "*" +{{- end }} --- {{- if .Values.cosign.enabled }} apiVersion: config.ratify.deislabs.io/v1beta1 @@ -50,10 +50,36 @@ metadata: helm.sh/hook-weight: "5" spec: name: cosign - version: 1.0.0 artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json parameters: + {{- if (eq (include "ratify.cosignLegacy" .) "false") }} + trustPolicies: + - name: default + version: 1.0.0 + scopes: + {{- range $i, $scope := .Values.cosign.scopes }} + - "{{$scope}}" + {{- end }} + keys: + {{- range $i, $key := .Values.cosignKeys }} + - provider: {{$fullname}}-cosign-inline-key-{{$i}} + {{- end }} + {{- if and .Values.azurekeyvault.enabled (gt (len .Values.azurekeyvault.keys) 0) }} + - provider: kmprovider-akv + {{- end }} + tLogVerify: {{ .Values.cosign.tLogVerify }} + rekorURL: {{ .Values.cosign.rekorURL }} + {{- if or .Values.cosign.keyless.certificateIdentity .Values.cosign.keyless.certificateIdentityRegExp .Values.cosign.keyless.certificateOIDCIssuer .Values.cosign.keyless.certificateOIDCIssuerRegExp }} + keyless: + ctLogVerify: {{ .Values.cosign.keyless.ctLogVerify }} + certificateIdentity: {{ .Values.cosign.keyless.certificateIdentity }} + certificateIdentityRegExp: {{ .Values.cosign.keyless.certificateIdentityRegExp }} + certificateOIDCIssuer: {{ .Values.cosign.keyless.certificateOIDCIssuer }} + certificateOIDCIssuerRegExp: {{ .Values.cosign.keyless.certificateOIDCIssuerRegExp }} + {{- end }} + {{- else }} key: /usr/local/ratify-certs/cosign/cosign.pub + {{- end }} {{- end }} --- {{- if .Values.vulnerabilityreport.enabled }} diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml index 94c9c19e8..ee7c82d41 100644 --- a/charts/ratify/values.yaml +++ b/charts/ratify/values.yaml @@ -1,7 +1,7 @@ image: - repository: ghcr.io/deislabs/ratify - crdRepository: ghcr.io/deislabs/ratify-crds - tag: v1.1.0 + repository: ghcr.io/ratify-project/ratify + crdRepository: ghcr.io/ratify-project/ratify-crds + tag: v1.3.0 pullPolicy: IfNotPresent nameOverride: "" @@ -9,12 +9,25 @@ fullnameOverride: "" replicaCount: 1 affinity: {} tolerations: [] -notationCert: "" notationCerts: [] +cosignKeys: [] + +notation: + enabled: true cosign: enabled: true - key: "" + scopes: ["*"] # corresponds to a single trust policy + key: "" # DEPRECATED: Use cosignKeys instead + rekorURL: "" + tLogVerify: true + keyless: + ctLogVerify: true + certificateIdentity: "" + certificateIdentityRegExp: "" + certificateOIDCIssuer: "" + certificateOIDCIssuerRegExp: "" + vulnerabilityreport: enabled: false passthrough: false @@ -40,7 +53,7 @@ serviceAccount: create: true name: ratify-admin gatekeeper: - version: "3.15.0" + version: "3.17.0" namespace: # default is gatekeeper-system instrumentation: metricsEnabled: true @@ -49,7 +62,7 @@ instrumentation: # Can be used to authenticate to: # ACR -> oras.authProviders.azureWorkloadIdentityEnabled -# Key Vault -> akvCertConfig.enabled +# Key Vault -> azurekeyvault.enabled azureWorkloadIdentity: clientId: @@ -57,15 +70,13 @@ azureManagedIdentity: clientId: tenantId: -akvCertConfig: +azurekeyvault: enabled: false vaultURI: - cert1Name: - cert1Version: - cert2Name: - cert2Version: - certificates: tenantId: + certificates: [] + keys: [] + refreshInterval: oras: useHttp: false @@ -148,3 +159,14 @@ featureFlags: RATIFY_CERT_ROTATION: false # RATIFY_EXPERIMENTAL_HIGH_AVAILABILITY enables high availability mode including distributed caching. RATIFY_EXPERIMENTAL_HIGH_AVAILABILITY: false + +notationCert: "" # DEPRECATED: Use notationCerts instead +akvCertConfig: # DEPRECATED: Use azurekeyvault instead + enabled: false # DEPRECATED: Use azurekeyvault.enabled instead + vaultURI: # DEPRECATED: Use azurekeyvault.vaultURI instead + cert1Name: # DEPRECATED: Use azurekeyvault.certificates instead + cert1Version: # DEPRECATED: Use azurekeyvault.certificates 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 diff --git a/cmd/ratify/cmd/cmd_test.go b/cmd/ratify/cmd/cmd_test.go new file mode 100644 index 000000000..f2f1b2850 --- /dev/null +++ b/cmd/ratify/cmd/cmd_test.go @@ -0,0 +1,83 @@ +/* +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 cmd + +import ( + "strings" + "testing" +) + +const ( + configFilePath = "../../../config/config.json" + subject = "localhost:5000/net-monitor:v1" + storeName = "oras" + digest = "sha256:17490f904cf278d4314a1ccba407fc8fd00fb45303589b8cc7f5174ac35554f4" +) + +func TestVerify(t *testing.T) { + err := verify((verifyCmdOptions{ + subject: subject, + artifactTypes: []string{""}, + configFilePath: configFilePath, + })) + + // TODO: make ratify cli more unit testable + // unit test should not have dependency for real image + if !strings.Contains(err.Error(), "PLUGIN_NOT_FOUND") { + t.Fatalf("expected containing: %s, but got: %s", "PLUGIN_NOT_FOUND", err.Error()) + } +} + +func TestDiscover(t *testing.T) { + err := discover((discoverCmdOptions{ + subject: subject, + artifactTypes: []string{""}, + configFilePath: configFilePath, + })) + + // TODO: make ratify cli more unit testable + // unit test should not need to resolve real image + if !strings.Contains(err.Error(), "REFERRER_STORE_FAILURE") { + t.Errorf("expected containing: %s, but got: %s", "REFERRER_STORE_FAILURE", err.Error()) + } +} + +func TestShowRefManifest(t *testing.T) { + err := showRefManifest((referrerCmdOptions{ + subject: subject, + configFilePath: configFilePath, + storeName: storeName, + digest: digest, + })) + + // TODO: make ratify cli more unit testable + // unit test should not need to resolve real image + if !strings.Contains(err.Error(), "failed to resolve subject descriptor") { + t.Errorf("error expected") + } + + // validate show blob returns error + err = showBlob((referrerCmdOptions{ + subject: subject, + configFilePath: configFilePath, + storeName: storeName, + digest: "invalid-digest", + })) + + if !strings.Contains(err.Error(), "the digest of the subject is invalid") { + t.Errorf("error expected") + } +} diff --git a/cmd/ratify/cmd/discover.go b/cmd/ratify/cmd/discover.go index c4f61e8ea..858f12b52 100644 --- a/cmd/ratify/cmd/discover.go +++ b/cmd/ratify/cmd/discover.go @@ -22,14 +22,14 @@ import ( "os" "strings" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - su "github.com/deislabs/ratify/pkg/referrerstore/utils" - "github.com/deislabs/ratify/pkg/utils" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + su "github.com/ratify-project/ratify/pkg/referrerstore/utils" + "github.com/ratify-project/ratify/pkg/utils" "github.com/spf13/cobra" "github.com/xlab/treeprint" ) @@ -60,7 +60,7 @@ func NewCmdDiscover(argv ...string) *cobra.Command { Short: "Discover referrers for a subject", Example: eg, Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return discover(opts) }, } @@ -96,10 +96,6 @@ func discover(opts discoverCmdOptions) error { return err } - if subRef.Digest == "" { - fmt.Println(taggedReferenceWarning) - } - cf, err := config.Load(opts.configFilePath) if err != nil { return err @@ -109,6 +105,10 @@ func discover(opts discoverCmdOptions) error { return err } + if subRef.Digest == "" { + logger.GetLogger(context.Background(), logOpt).Warn(taggedReferenceWarning) + } + rootImage := treeprint.NewWithRoot(subRef.String()) stores, err := sf.CreateStoresFromConfig(cf.StoresConfig, config.GetDefaultPluginPath()) diff --git a/cmd/ratify/cmd/referrer.go b/cmd/ratify/cmd/referrer.go index 8520e0f2a..8921c5ede 100644 --- a/cmd/ratify/cmd/referrer.go +++ b/cmd/ratify/cmd/referrer.go @@ -22,11 +22,11 @@ import ( "os" "strings" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/ocispecs" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/utils" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/ocispecs" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + "github.com/ratify-project/ratify/pkg/utils" "github.com/spf13/cobra" ) @@ -46,7 +46,7 @@ func NewCmdReferrer(argv ...string) *cobra.Command { Use: referrerUse, Short: "Discover referrers for a subject", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Usage() }, } @@ -71,7 +71,7 @@ func NewCmdShowBlob(argv ...string) *cobra.Command { Short: "show blob at a digest", Example: eg, Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return showBlob(opts) }, } @@ -97,10 +97,10 @@ func NewCmdShowRefManifest(argv ...string) *cobra.Command { cmd := &cobra.Command{ Use: "show-manifest [OPTIONS]", - Short: "show rference manifest at a digest", + Short: "show reference manifest at a digest", Example: eg, Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return showRefManifest(opts) }, } @@ -184,10 +184,6 @@ func showRefManifest(opts referrerCmdOptions) error { return err } - if subRef.Digest == "" { - fmt.Println(taggedReferenceWarning) - } - digest, err := utils.ParseDigest(opts.digest) if err != nil { return err @@ -198,6 +194,10 @@ func showRefManifest(opts referrerCmdOptions) error { return err } + if subRef.Digest == "" { + logger.GetLogger(context.Background(), logOpt).Warn(taggedReferenceWarning) + } + stores, err := sf.CreateStoresFromConfig(cf.StoresConfig, config.GetDefaultPluginPath()) if err != nil { diff --git a/cmd/ratify/cmd/resolve.go b/cmd/ratify/cmd/resolve.go index 0ecc87b39..3f7b0b2ba 100644 --- a/cmd/ratify/cmd/resolve.go +++ b/cmd/ratify/cmd/resolve.go @@ -22,11 +22,11 @@ import ( "os" "strings" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/internal/logger" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - su "github.com/deislabs/ratify/pkg/referrerstore/utils" - "github.com/deislabs/ratify/pkg/utils" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/internal/logger" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + su "github.com/ratify-project/ratify/pkg/referrerstore/utils" + "github.com/ratify-project/ratify/pkg/utils" "github.com/spf13/cobra" ) @@ -54,7 +54,7 @@ func NewCmdResolve(argv ...string) *cobra.Command { Short: "Resolve digest of a subject that is referenced by a tag", Example: eg, Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return resolve(opts) }, } diff --git a/cmd/ratify/cmd/root.go b/cmd/ratify/cmd/root.go index d52637b29..e6b074347 100644 --- a/cmd/ratify/cmd/root.go +++ b/cmd/ratify/cmd/root.go @@ -16,8 +16,8 @@ limitations under the License. package cmd import ( - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/featureflag" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/featureflag" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -35,14 +35,14 @@ func New(use, short string) *cobra.Command { root := &cobra.Command{ Use: use, Short: short, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRun: func(_ *cobra.Command, _ []string) { if enableDebug { common.SetLoggingLevel("debug", logrus.StandardLogger()) } else { common.SetLoggingLevelFromEnv(logrus.StandardLogger()) } }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return cmd.Usage() }, SilenceUsage: true, diff --git a/cmd/ratify/cmd/serve.go b/cmd/ratify/cmd/serve.go index 65ebb513c..ab0f872f0 100644 --- a/cmd/ratify/cmd/serve.go +++ b/cmd/ratify/cmd/serve.go @@ -20,11 +20,11 @@ import ( "fmt" "time" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/httpserver" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/manager" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/httpserver" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/manager" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -58,7 +58,7 @@ func NewCmdServe(_ ...string) *cobra.Command { Short: "Run ratify as a server", Example: "ratify server", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return serve(opts) }, } diff --git a/cmd/ratify/cmd/verify.go b/cmd/ratify/cmd/verify.go index 170014133..c189fa68b 100644 --- a/cmd/ratify/cmd/verify.go +++ b/cmd/ratify/cmd/verify.go @@ -18,17 +18,16 @@ package cmd import ( "context" "errors" - "fmt" - - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/internal/logger" - e "github.com/deislabs/ratify/pkg/executor" - ef "github.com/deislabs/ratify/pkg/executor/core" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/utils" - vf "github.com/deislabs/ratify/pkg/verifier/factory" + + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/internal/logger" + e "github.com/ratify-project/ratify/pkg/executor" + ef "github.com/ratify-project/ratify/pkg/executor/core" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + "github.com/ratify-project/ratify/pkg/utils" + vf "github.com/ratify-project/ratify/pkg/verifier/factory" "github.com/spf13/cobra" ) @@ -36,6 +35,10 @@ const ( verifyUse = "verify" ) +var logOpt = logger.Option{ + ComponentType: logger.CommandLine, +} + type verifyCmdOptions struct { configFilePath string subject string @@ -51,7 +54,7 @@ func NewCmdVerify(_ ...string) *cobra.Command { Short: "Verify a subject", Example: "sample example", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return verify(opts) }, } @@ -65,12 +68,6 @@ func NewCmdVerify(_ ...string) *cobra.Command { return cmd } -func TestVerify(subject string) { - _ = verify((verifyCmdOptions{ - subject: subject, - })) -} - func verify(opts verifyCmdOptions) error { if opts.subject == "" { return errors.New("subject parameter is required") @@ -81,10 +78,6 @@ func verify(opts verifyCmdOptions) error { return err } - if subRef.Digest == "" { - fmt.Println(taggedReferenceWarning) - } - cf, err := config.Load(opts.configFilePath) if err != nil { return err @@ -94,6 +87,10 @@ func verify(opts verifyCmdOptions) error { return err } + if subRef.Digest == "" { + logger.GetLogger(context.Background(), logOpt).Warn(taggedReferenceWarning) + } + stores, err := sf.CreateStoresFromConfig(cf.StoresConfig, config.GetDefaultPluginPath()) if err != nil { diff --git a/cmd/ratify/cmd/version.go b/cmd/ratify/cmd/version.go index 9a5fbd0a9..410b710d5 100644 --- a/cmd/ratify/cmd/version.go +++ b/cmd/ratify/cmd/version.go @@ -18,7 +18,7 @@ import ( "runtime" "strings" - "github.com/deislabs/ratify/internal/version" + "github.com/ratify-project/ratify/internal/version" "github.com/spf13/cobra" ) @@ -35,7 +35,7 @@ ratify version` Short: "Show the ratify version information", Example: eg, Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return runVersion() }, } diff --git a/cmd/ratify/main.go b/cmd/ratify/main.go index cafaaaf9f..f51315c68 100644 --- a/cmd/ratify/main.go +++ b/cmd/ratify/main.go @@ -18,14 +18,14 @@ package main import ( "os" - "github.com/deislabs/ratify/cmd/ratify/cmd" - _ "github.com/deislabs/ratify/pkg/cache/dapr" // register dapr cache - _ "github.com/deislabs/ratify/pkg/cache/ristretto" // register ristretto cache - _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" // register configpolicy policy provider - _ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy" // register regopolicy policy provider - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" // register oras referrer store - _ "github.com/deislabs/ratify/pkg/verifier/cosign" // register cosign verifier - _ "github.com/deislabs/ratify/pkg/verifier/notation" // register notation verifier + "github.com/ratify-project/ratify/cmd/ratify/cmd" + _ "github.com/ratify-project/ratify/pkg/cache/dapr" // register dapr cache + _ "github.com/ratify-project/ratify/pkg/cache/ristretto" // register ristretto cache + _ "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" // register configpolicy policy provider + _ "github.com/ratify-project/ratify/pkg/policyprovider/regopolicy" // register regopolicy policy provider + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" // register oras referrer store + _ "github.com/ratify-project/ratify/pkg/verifier/cosign" // register cosign verifier + _ "github.com/ratify-project/ratify/pkg/verifier/notation" // register notation verifier ) func main() { diff --git a/config/config.go b/config/config.go index 901a0eb74..7bc3e0f9f 100644 --- a/config/config.go +++ b/config/config.go @@ -24,20 +24,20 @@ import ( "path/filepath" "sync" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/internal/logger" - exConfig "github.com/deislabs/ratify/pkg/executor/config" - "github.com/deislabs/ratify/pkg/homedir" - "github.com/deislabs/ratify/pkg/policyprovider" - pcConfig "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" - "github.com/deislabs/ratify/pkg/referrerstore" - rsConfig "github.com/deislabs/ratify/pkg/referrerstore/config" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/verifier" - vfConfig "github.com/deislabs/ratify/pkg/verifier/config" - vf "github.com/deislabs/ratify/pkg/verifier/factory" "github.com/pkg/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/internal/logger" + exConfig "github.com/ratify-project/ratify/pkg/executor/config" + "github.com/ratify-project/ratify/pkg/homedir" + "github.com/ratify-project/ratify/pkg/policyprovider" + pcConfig "github.com/ratify-project/ratify/pkg/policyprovider/config" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" + "github.com/ratify-project/ratify/pkg/referrerstore" + rsConfig "github.com/ratify-project/ratify/pkg/referrerstore/config" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + "github.com/ratify-project/ratify/pkg/verifier" + vfConfig "github.com/ratify-project/ratify/pkg/verifier/config" + vf "github.com/ratify-project/ratify/pkg/verifier/factory" "github.com/sirupsen/logrus" ) diff --git a/config/configManager.go b/config/configManager.go index e43f253d3..cce09c602 100644 --- a/config/configManager.go +++ b/config/configManager.go @@ -16,16 +16,17 @@ limitations under the License. package config import ( + "context" "os" "time" - ef "github.com/deislabs/ratify/pkg/executor/core" "github.com/fsnotify/fsnotify" "github.com/pkg/errors" + ef "github.com/ratify-project/ratify/pkg/executor/core" "github.com/sirupsen/logrus" ) -type GetExecutor func() *ef.Executor +type GetExecutor func(context.Context) *ef.Executor var ( configHash string @@ -38,7 +39,7 @@ func GetExecutorAndWatchForUpdate(configFilePath string) (GetExecutor, error) { cf, err := Load(configFilePath) if err != nil { - return func() *ef.Executor { return &ef.Executor{} }, err + return func(context.Context) *ef.Executor { return &ef.Executor{} }, err } configHash = cf.fileHash @@ -46,7 +47,7 @@ func GetExecutorAndWatchForUpdate(configFilePath string) (GetExecutor, error) { stores, verifiers, policyEnforcer, err := CreateFromConfig(cf) if err != nil { - return func() *ef.Executor { return &ef.Executor{} }, err + return func(context.Context) *ef.Executor { return &ef.Executor{} }, err } executor = ef.Executor{ @@ -59,12 +60,12 @@ func GetExecutorAndWatchForUpdate(configFilePath string) (GetExecutor, error) { err = watchForConfigurationChange(configFilePath) if err != nil { - return func() *ef.Executor { return &ef.Executor{} }, err + return func(context.Context) *ef.Executor { return &ef.Executor{} }, err } logrus.Info("configuration successfully loaded.") - return func() *ef.Executor { return &executor }, nil + return func(context.Context) *ef.Executor { return &executor }, nil } func reloadExecutor(configFilePath string) { diff --git a/config/config_cli.json b/config/config_cli.json new file mode 100644 index 000000000..04170bf07 --- /dev/null +++ b/config/config_cli.json @@ -0,0 +1,55 @@ +{ + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "cosignEnabled": true, + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "configPolicy" + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "name": "cosign", + "artifactTypes": "application/vnd.dev.cosign.artifact.sig.v1+json", + "key": ".staging/cosign/cosign.pub" + }, + { + "name": "notation", + "artifactTypes": "application/vnd.cncf.notary.signature", + "verificationCerts": [ + "~/.config/notation/localkeys/notation.crt" + ], + "trustPolicyDoc": { + "version": "1.0", + "trustPolicies": [ + { + "name": "default", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level": "strict" + }, + "trustStores": [ + "ca:certs" + ], + "trustedIdentities": [ + "*" + ] + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/config/crd/bases/config.ratify.deislabs.io_certificatestores.yaml b/config/crd/bases/config.ratify.deislabs.io_certificatestores.yaml index abafa948b..84d2d221b 100644 --- a/config/crd/bases/config.ratify.deislabs.io_certificatestores.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_certificatestores.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: certificatestores.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -24,14 +23,19 @@ spec: description: CertificateStore is the Schema for the certificatestores API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -70,14 +74,19 @@ spec: description: CertificateStore is the Schema for the certificatestores API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml index 29ddb906d..2a3658da0 100644 --- a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: keymanagementproviders.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -13,7 +12,7 @@ spec: listKind: KeyManagementProviderList plural: keymanagementproviders singular: keymanagementprovider - scope: Namespaced + scope: Cluster versions: - additionalPrinterColumns: - jsonPath: .status.issuccess @@ -32,14 +31,19 @@ spec: API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -50,6 +54,14 @@ spec: description: Parameters of the key management provider type: object x-kubernetes-preserve-unknown-fields: true + refreshInterval: + default: "" + description: Refresh interval for fetching the certificate/key files + from the provider. Only for providers that are refreshable. The + value is in the format of "1h30m" where "h" means hour and "m" means + minute. Valid time units are units are "ns", "us" (or "µs"), "ms", + "s", "m", "h". + type: string type: description: Name of the key management provider type: string diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..0054b1e5b --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedkeymanagementproviders.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedKeyManagementProvider + listKind: NamespacedKeyManagementProviderList + plural: namespacedkeymanagementproviders + singular: namespacedkeymanagementprovider + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + - jsonPath: .status.lastfetchedtime + name: LastFetchedTime + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NamespacedKeyManagementProviderSpec defines the desired state + of NamespacedKeyManagementProvider + properties: + parameters: + description: Parameters of the key management provider + type: object + x-kubernetes-preserve-unknown-fields: true + refreshInterval: + default: "" + description: Refresh interval for the key management provider. Only + used if the key management provider is refreshable. Valid time units + are "ns", "us" (or "µs"), "ms", "s", "m", "h". + type: string + type: + description: Name of the key management provider + type: string + type: object + status: + description: NamespacedKeyManagementProviderStatus defines the observed + state of NamespacedKeyManagementProvider + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in loading certificate/key files + type: boolean + lastfetchedtime: + description: The time stamp of last successful certificate/key fetch + operation. If operation failed, last fetched time shows the time + of error + format: date-time + type: string + properties: + description: provider specific properties of the each individual certificate/key + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml new file mode 100644 index 000000000..3e7e776c5 --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedpolicies.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedpolicies.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedPolicy + listKind: NamespacedPolicyList + plural: namespacedpolicies + singular: namespacedpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedPolicy is the Schema for the namespacedpolicies API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NamespacedPolicySpec defines the desired state of NamespacedPolicy + properties: + parameters: + description: Parameters for this policy + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Type of the policy + type: string + type: object + status: + description: NamespacedPolicyStatus defines the observed state of NamespacedPolicy + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if policy is not successfully applied. + type: string + issuccess: + description: Is successful while applying the policy. + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml new file mode 100644 index 000000000..97f08f98a --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedstores.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedstores.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedStore + listKind: NamespacedStoreList + plural: namespacedstores + singular: namespacedstore + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedStore is the Schema for the namespacedstores API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NamespacedStoreSpec defines the desired state of NamespacedStore + properties: + address: + description: Plugin path, optional + type: string + name: + description: Name of the store + type: string + parameters: + description: Parameters of the store + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from, optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version of the store plugin. Optional + type: string + required: + - name + type: object + status: + description: NamespacedStoreStatus defines the observed state of NamespacedStore + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml new file mode 100644 index 000000000..b61b07734 --- /dev/null +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml @@ -0,0 +1,104 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: namespacedverifiers.config.ratify.deislabs.io +spec: + group: config.ratify.deislabs.io + names: + kind: NamespacedVerifier + listKind: NamespacedVerifierList + plural: namespacedverifiers + singular: namespacedverifier + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.issuccess + name: IsSuccess + type: boolean + - jsonPath: .status.brieferror + name: Error + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: NamespacedVerifier is the Schema for the namespacedverifiers + API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier + properties: + address: + description: URL/file path. Optional + type: string + artifactTypes: + description: The type of artifact this verifier handles + type: string + name: + description: Name of the verifier. Deprecated + type: string + parameters: + description: Parameters for this verifier + type: object + x-kubernetes-preserve-unknown-fields: true + source: + description: OCI Artifact source to download the plugin from. Optional + properties: + artifact: + description: OCI Artifact source to download the plugin from + type: string + authProvider: + description: AuthProvider to use to authenticate to the OCI Artifact + source, optional + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + type: + description: Type of the verifier. Optional + type: string + version: + description: Version of the verifier plugin. Optional + type: string + required: + - artifactTypes + - name + type: object + status: + description: NamespacedVerifierStatus defines the observed state of NamespacedVerifier + properties: + brieferror: + description: Truncated error message if the message is too long + type: string + error: + description: Error message if operation was unsuccessful + type: string + issuccess: + description: Is successful in finding the plugin + type: boolean + required: + - issuccess + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/config.ratify.deislabs.io_policies.yaml b/config/crd/bases/config.ratify.deislabs.io_policies.yaml index 63f999782..a4ad95ba0 100644 --- a/config/crd/bases/config.ratify.deislabs.io_policies.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_policies.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: policies.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -24,14 +23,19 @@ spec: description: Policy is the Schema for the policies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -62,14 +66,19 @@ spec: description: Policy is the Schema for the policies API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/config/crd/bases/config.ratify.deislabs.io_stores.yaml b/config/crd/bases/config.ratify.deislabs.io_stores.yaml index a2914bd2e..3a306450f 100644 --- a/config/crd/bases/config.ratify.deislabs.io_stores.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_stores.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: stores.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -24,14 +23,19 @@ spec: description: Store is the Schema for the stores API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -80,14 +84,19 @@ spec: description: Store is the Schema for the stores API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object diff --git a/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml index ce2646f03..8d08f76e5 100644 --- a/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml @@ -3,8 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.9.2 - creationTimestamp: null + controller-gen.kubebuilder.io/version: v0.15.0 name: verifiers.config.ratify.deislabs.io spec: group: config.ratify.deislabs.io @@ -24,14 +23,19 @@ spec: description: Verifier is the Schema for the verifiers API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -83,14 +87,19 @@ spec: description: Verifier is the Schema for the verifiers API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object @@ -98,20 +107,20 @@ spec: description: VerifierSpec defines the desired state of Verifier properties: address: - description: '# Optional. URL/file path' + description: URL/file path. Optional type: string artifactTypes: description: The type of artifact this verifier handles type: string name: - description: Name of the verifier + description: Name of the verifier. Deprecated type: string parameters: description: Parameters for this verifier type: object x-kubernetes-preserve-unknown-fields: true source: - description: OCI Artifact source to download the plugin from, optional + description: OCI Artifact source to download the plugin from. Optional properties: artifact: description: OCI Artifact source to download the plugin from @@ -122,6 +131,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + type: + description: Type of the verifier. Optional + type: string version: description: Version of the verifier plugin. Optional type: string diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 26aa14b25..83786a4e6 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,6 +7,10 @@ resources: - bases/config.ratify.deislabs.io_certificatestores.yaml - bases/config.ratify.deislabs.io_policies.yaml - bases/config.ratify.deislabs.io_keymanagementproviders.yaml + - bases/config.ratify.deislabs.io_namespacedpolicies.yaml + - bases/config.ratify.deislabs.io_namespacedstores.yaml + - bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml + - bases/config.ratify.deislabs.io_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -17,6 +21,10 @@ patchesStrategicMerge: #- patches/webhook_in_certificatestores.yaml #- patches/webhook_in_policies.yaml #- patches/webhook_in_keymanagementproviders.yaml + #- patches/webhook_in_namespacedpolicies.yaml + #- patches/webhook_in_namespacedstores.yaml + #- patches/webhook_in_namespacedkeymanagementproviders.yaml + #- patches/webhook_in_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -26,8 +34,12 @@ patchesStrategicMerge: #- patches/cainjection_in_certificatestores.yaml #- patches/cainjection_in_policies.yaml #- patches/cainjection_in_keymanagementproviders.yaml + #- patches/cainjection_in_namespacedpolicies.yaml + #- patches/cainjection_in_namespacedstores.yaml + #- patches/cainjection_in_namespacedkeymanagementproviders.yaml + #- patches/cainjection_in_namespacedverifiers.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: -- kustomizeconfig.yaml + - kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_clusterpolicies.yaml b/config/crd/patches/cainjection_in_clusterpolicies.yaml new file mode 100644 index 000000000..8e6a64b27 --- /dev/null +++ b/config/crd/patches/cainjection_in_clusterpolicies.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: clusterpolicies.config.ratify.deislabs.io diff --git a/config/crd/patches/cainjection_in_namespacedkeymanagementproviders.yaml b/config/crd/patches/cainjection_in_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..d99842389 --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedkeymanagementproviders.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: namespacedkeymanagementproviders.config.ratify.deislabs.io diff --git a/config/crd/patches/cainjection_in_namespacedpolicies.yaml b/config/crd/patches/cainjection_in_namespacedpolicies.yaml new file mode 100644 index 000000000..be47ac51f --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedpolicies.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: namespacedpolicies.config.ratify.deislabs.io diff --git a/config/crd/patches/cainjection_in_namespacedstores.yaml b/config/crd/patches/cainjection_in_namespacedstores.yaml new file mode 100644 index 000000000..db6068033 --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedstores.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: namespacedstores.config.ratify.deislabs.io diff --git a/config/crd/patches/cainjection_in_namespacedverifiers.yaml b/config/crd/patches/cainjection_in_namespacedverifiers.yaml new file mode 100644 index 000000000..90b322c8d --- /dev/null +++ b/config/crd/patches/cainjection_in_namespacedverifiers.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: namespacedverifiers.config.ratify.deislabs.io diff --git a/config/crd/patches/webhook_in_clusterpolicies.yaml b/config/crd/patches/webhook_in_clusterpolicies.yaml new file mode 100644 index 000000000..de0e79ec2 --- /dev/null +++ b/config/crd/patches/webhook_in_clusterpolicies.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clusterpolicies.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml b/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml new file mode 100644 index 000000000..2d1f077c1 --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedkeymanagementproviders.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedkeymanagementproviders.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_namespacedpolicies.yaml b/config/crd/patches/webhook_in_namespacedpolicies.yaml new file mode 100644 index 000000000..057d373aa --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedpolicies.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedpolicies.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_namespacedstores.yaml b/config/crd/patches/webhook_in_namespacedstores.yaml new file mode 100644 index 000000000..f4ba8b69a --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedstores.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedstores.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_namespacedverifiers.yaml b/config/crd/patches/webhook_in_namespacedverifiers.yaml new file mode 100644 index 000000000..789395102 --- /dev/null +++ b/config/crd/patches/webhook_in_namespacedverifiers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: namespacedverifiers.config.ratify.deislabs.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/namespacedkeymanagementprovider_editor_role.yaml b/config/rbac/namespacedkeymanagementprovider_editor_role.yaml new file mode 100644 index 000000000..671f37599 --- /dev/null +++ b/config/rbac/namespacedkeymanagementprovider_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedkeymanagementproviders. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedkeymanagementprovider-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedkeymanagementprovider-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get diff --git a/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml b/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml new file mode 100644 index 000000000..f9d6a8418 --- /dev/null +++ b/config/rbac/namespacedkeymanagementprovider_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedkeymanagementproviders. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedkeymanagementprovider-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedkeymanagementprovider-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get diff --git a/config/rbac/namespacedpolicy_editor_role.yaml b/config/rbac/namespacedpolicy_editor_role.yaml new file mode 100644 index 000000000..fb1e31c21 --- /dev/null +++ b/config/rbac/namespacedpolicy_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedpolicies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedpolicy-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedpolicy-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get diff --git a/config/rbac/namespacedpolicy_viewer_role.yaml b/config/rbac/namespacedpolicy_viewer_role.yaml new file mode 100644 index 000000000..4dc2f98b2 --- /dev/null +++ b/config/rbac/namespacedpolicy_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedpolicies. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedpolicy-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedpolicy-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get diff --git a/config/rbac/namespacedstore_editor_role.yaml b/config/rbac/namespacedstore_editor_role.yaml new file mode 100644 index 000000000..6f983a8c3 --- /dev/null +++ b/config/rbac/namespacedstore_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedstores. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedstore-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedstore-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get diff --git a/config/rbac/namespacedstore_viewer_role.yaml b/config/rbac/namespacedstore_viewer_role.yaml new file mode 100644 index 000000000..f3b276be8 --- /dev/null +++ b/config/rbac/namespacedstore_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedstores. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedstore-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedstore-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get diff --git a/config/rbac/namespacedverifier_editor_role.yaml b/config/rbac/namespacedverifier_editor_role.yaml new file mode 100644 index 000000000..d4ff9be23 --- /dev/null +++ b/config/rbac/namespacedverifier_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit namespacedverifiers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedverifier-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedverifier-editor-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get diff --git a/config/rbac/namespacedverifier_viewer_role.yaml b/config/rbac/namespacedverifier_viewer_role.yaml new file mode 100644 index 000000000..8a63ab316 --- /dev/null +++ b/config/rbac/namespacedverifier_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view namespacedverifiers. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: namespacedverifier-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ratify + app.kubernetes.io/part-of: ratify + app.kubernetes.io/managed-by: kustomize + name: namespacedverifier-viewer-role +rules: +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - get + - list + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 36974c9aa..f0f702745 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -57,6 +57,32 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedpolicies/status + verbs: + - get + - patch + - update - apiGroups: - config.ratify.deislabs.io resources: @@ -135,3 +161,81 @@ rules: - get - patch - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedstores/status + verbs: + - get + - patch + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedkeymanagementproviders/status + verbs: + - get + - patch + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/finalizers + verbs: + - update +- apiGroups: + - config.ratify.deislabs.io + resources: + - namespacedverifiers/status + verbs: + - get + - patch + - update \ No newline at end of file diff --git a/config/samples/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml similarity index 82% rename from config/samples/config_v1beta1_keymanagementprovider_akv.yaml rename to config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml index ae25f7f6a..7f9229014 100644 --- a/config/samples/config_v1beta1_keymanagementprovider_akv.yaml +++ b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml @@ -1,13 +1,13 @@ apiVersion: config.ratify.deislabs.io/v1beta1 kind: KeyManagementProvider metadata: - name: keymanagementprovider-inline + name: keymanagementprovider-akv spec: type: azurekeyvault parameters: vaultURI: https://yourkeyvault.vault.azure.net/ certificates: - name: yourCertName - version: yourCertVersion # Optional, fetch latest version if empty + version: yourCertVersion # Optional, fetch latest version if empty tenantID: - clientID: + clientID: diff --git a/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml new file mode 100644 index 000000000..3b820f616 --- /dev/null +++ b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml @@ -0,0 +1,14 @@ +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 + version: yourCertVersion # Optional, fetch latest version if empty + tenantID: + clientID: diff --git a/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_inline.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_inline.yaml new file mode 100644 index 000000000..ec9dffc7c --- /dev/null +++ b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_inline.yaml @@ -0,0 +1,29 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-inline +spec: + type: inline + parameters: + contentType: certificate + value: | + -----BEGIN CERTIFICATE----- + MIIDQzCCAiugAwIBAgIUDxHQ9JxxmnrLWTA5rAtIZCzY8mMwDQYJKoZIhvcNAQEL + BQAwKTEPMA0GA1UECgwGUmF0aWZ5MRYwFAYDVQQDDA1SYXRpZnkgU2FtcGxlMB4X + DTIzMDYyOTA1MjgzMloXDTMzMDYyNjA1MjgzMlowKTEPMA0GA1UECgwGUmF0aWZ5 + MRYwFAYDVQQDDA1SYXRpZnkgU2FtcGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + MIIBCgKCAQEAshmsL2VM9ojhgTVUUuEsZro9jfI27VKZJ4naWSHJihmOki7IoZS8 + 3/3ATpkE1lGbduJ77M9UxQbEW1PnESB0bWtMQtjIbser3mFCn15yz4nBXiTIu/K4 + FYv6HVdc6/cds3jgfEFNw/8RVMBUGNUiSEWa1lV1zDM2v/8GekUr6SNvMyqtY8oo + ItwxfUvlhgMNlLgd96mVnnPVLmPkCmXFN9iBMhSce6sn6P9oDIB+pr1ZpE4F5bwa + gRBg2tWN3Tz9H/z2a51Xbn7hCT5OLBRlkorHJl2HKKRoXz1hBgR8xOL+zRySH9Qo + 3yx6WvluYDNfVbCREzKJf9fFiQeVe0EJOwIDAQABo2MwYTAdBgNVHQ4EFgQUKzci + EKCDwPBn4I1YZ+sDdnxEir4wHwYDVR0jBBgwFoAUKzciEKCDwPBn4I1YZ+sDdnxE + ir4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQEL + BQADggEBAGh6duwc1MvV+PUYvIkDfgj158KtYX+bv4PmcV/aemQUoArqM1ECYFjt + BlBVmTRJA0lijU5I0oZje80zW7P8M8pra0BM6x3cPnh/oZGrsuMizd4h5b5TnwuJ + hRvKFFUVeHn9kORbyQwRQ5SpL8cRGyYp+T6ncEmo0jdIOM5dgfdhwHgb+i3TejcF + 90sUs65zovUjv1wa11SqOdu12cCj/MYp+H8j2lpaLL2t0cbFJlBY6DNJgxr5qync + cz8gbXrZmNbzC7W5QK5J7fcx6tlffOpt5cm427f9NiK2tira50HU7gC3HJkbiSTp + Xw10iXXMZzSbQ0/Hj2BF4B40WfAkgRg= + -----END CERTIFICATE----- diff --git a/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml new file mode 100644 index 000000000..a947d5966 --- /dev/null +++ b/config/samples/clustered/policy/config_v1alpha1_policy_json.yaml @@ -0,0 +1,8 @@ +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: Policy # Policy applies to the cluster. +metadata: + name: "configpolicy" # Ensure that metadata.name is either 'regopolicy' or 'configpolicy' +spec: + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/policy/config_v1alpha1_policy_rego.yaml b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml similarity index 83% rename from config/samples/policy/config_v1alpha1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml index f80978309..45c3695f4 100644 --- a/config/samples/policy/config_v1alpha1_policy_rego.yaml +++ b/config/samples/clustered/policy/config_v1alpha1_policy_rego.yaml @@ -1,7 +1,7 @@ apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: Policy +kind: Policy # Policy applies to the cluster. metadata: - name: "regopolicy" + name: "regopolicy" # Ensure that metadata.name is either 'regopolicy' or 'configpolicy' spec: parameters: passthroughEnabled: false diff --git a/config/samples/clustered/policy/config_v1beta1_policy_json.yaml b/config/samples/clustered/policy/config_v1beta1_policy_json.yaml new file mode 100644 index 000000000..00c35331b --- /dev/null +++ b/config/samples/clustered/policy/config_v1beta1_policy_json.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Policy # Policy applies to the cluster. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "config-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/policy/config_v1beta1_policy_rego.yaml b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml similarity index 74% rename from config/samples/policy/config_v1beta1_policy_rego.yaml rename to config/samples/clustered/policy/config_v1beta1_policy_rego.yaml index 89ac6a056..6bc1451d6 100644 --- a/config/samples/policy/config_v1beta1_policy_rego.yaml +++ b/config/samples/clustered/policy/config_v1beta1_policy_rego.yaml @@ -1,9 +1,9 @@ apiVersion: config.ratify.deislabs.io/v1beta1 -kind: Policy +kind: Policy # Policy applies to the cluster. metadata: - name: "ratify-policy" + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. spec: - type: "rego-policy" + type: "rego-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. parameters: passthroughEnabled: false policy: | diff --git a/config/samples/config_v1beta1_store_dynamic.yaml b/config/samples/clustered/store/config_v1beta1_store_dynamic.yaml similarity index 100% rename from config/samples/config_v1beta1_store_dynamic.yaml rename to config/samples/clustered/store/config_v1beta1_store_dynamic.yaml diff --git a/config/samples/config_v1beta1_store_oras.yaml b/config/samples/clustered/store/config_v1beta1_store_oras.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras.yaml diff --git a/config/samples/config_v1beta1_store_oras_http.yaml b/config/samples/clustered/store/config_v1beta1_store_oras_http.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras_http.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras_http.yaml diff --git a/config/samples/config_v1beta1_store_oras_k8secretAuth.yaml b/config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml similarity index 100% rename from config/samples/config_v1beta1_store_oras_k8secretAuth.yaml rename to config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml diff --git a/config/samples/config_v1alpha1_store_oras_http.yaml b/config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml similarity index 100% rename from config/samples/config_v1alpha1_store_oras_http.yaml rename to config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml diff --git a/config/samples/config_v1alpha1_verifier_notation.yaml b/config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml similarity index 100% rename from config/samples/config_v1alpha1_verifier_notation.yaml rename to config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml diff --git a/config/samples/config_v1beta1_verifier_complete_licensechecker.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_complete_licensechecker.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml diff --git a/config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml new file mode 100644 index 000000000..89719fe6d --- /dev/null +++ b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml @@ -0,0 +1,15 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: default + scopes: + - "*" + keys: + - provider: ratify-cosign-inline-key-0 + tLogVerify: false \ No newline at end of file diff --git a/config/samples/config_v1beta1_verifier_cosign_keyless.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless_legacy.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_cosign_keyless.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless_legacy.yaml diff --git a/config/samples/config_v1beta1_verifier_cosign.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_cosign.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml diff --git a/config/samples/config_v1beta1_verifier_dynamic.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_dynamic.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml diff --git a/config/samples/config_v1beta1_verifier_notation_kmprovider.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml similarity index 75% rename from config/samples/config_v1beta1_verifier_notation_kmprovider.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml index 407f08f02..4da8453a7 100644 --- a/config/samples/config_v1beta1_verifier_notation_kmprovider.yaml +++ b/config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml @@ -7,12 +7,9 @@ spec: artifactTypes: application/vnd.cncf.notary.signature parameters: verificationCertStores: - certs: - - kmprovider-akv - - kmprovider-akv1 - certs1: - - kmprovider-akv2 - - kmprovider-akv3 + ca: + ca-certs: + - ratify-notation-inline-cert-0 trustPolicyDoc: version: "1.0" trustPolicies: @@ -22,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/config/samples/config_v1beta1_verifier_notation.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml similarity index 76% rename from config/samples/config_v1beta1_verifier_notation.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml index 8da1278ba..ea2d24c8e 100644 --- a/config/samples/config_v1beta1_verifier_notation.yaml +++ b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml @@ -6,10 +6,11 @@ spec: name: notation artifactTypes: application/vnd.cncf.notary.signature parameters: - verificationCertStores: - certs: - - ratify-notation-inline-cert-0 - trustPolicyDoc: + verificationCertStores: + ca: + ca-certs: + - kmprovider-akv + trustPolicyDoc: version: "1.0" trustPolicies: - name: default @@ -18,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml similarity index 74% rename from config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml index 1920a14fe..8584b9938 100644 --- a/config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml +++ b/config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml @@ -6,10 +6,11 @@ spec: name: notation artifactTypes: application/vnd.cncf.notary.signature parameters: - verificationCertStores: - certs: - - default/ratify-notation-inline-cert-0 - trustPolicyDoc: + verificationCertStores: + ca: + ca-certs: + - default/ratify-notation-inline-cert-0 + trustPolicyDoc: version: "1.0" trustPolicies: - name: default @@ -18,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/config/samples/config_v1beta1_verifier_partial_licensechecker.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_partial_licensechecker.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml diff --git a/config/samples/config_v1beta1_verifier_sbom.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_sbom.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml diff --git a/config/samples/config_v1beta1_verifier_sbom_deny.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_sbom_deny.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml diff --git a/config/samples/config_v1beta1_verifier_schemavalidator.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_schemavalidator.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml diff --git a/config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml diff --git a/config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml diff --git a/config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml b/config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml similarity index 100% rename from config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml rename to config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml diff --git a/config/samples/config_v1beta1_keymanagementprovider_inline.yaml b/config/samples/config_v1beta1_keymanagementprovider_inline.yaml deleted file mode 100644 index 8c71a965a..000000000 --- a/config/samples/config_v1beta1_keymanagementprovider_inline.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1beta1 -kind: KeyManagementProvider -metadata: - name: keymanagementprovider-inline -spec: - type: inline - parameters: - contentType: certificate - value: | - -----BEGIN CERTIFICATE----- - MIIDWDCCAkCgAwIBAgIBUTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL - MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEb - MBkGA1UEAxMSd2FiYml0LW5ldHdvcmtzLmlvMCAXDTIyMTIwMjA4MDg0NFoYDzIx - MjIxMjAzMDgwODQ0WjBaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNV - BAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEbMBkGA1UEAxMSd2FiYml0LW5l - dHdvcmtzLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnoskJWB0 - ZsYcfbTvCYQMLqWaB/yN3Jf7Ryxvndrij83fWEQPBQJi8Mk8SpNqm2x9uP3gsQDc - L/73a0p6/D+hza2jQQVhebe/oB0LJtUoD5LXlJ83UQdZETLMYAzeBNcBR4kMecrY - CnE6yjHeiEWdAH+U7Mt39zJh+9lGIcbk0aUE5UOp8o3t5RWFDcl9hQ7QOXROwmpO - thLUIiY/bcPpsg/2nH1nzFjqiBef3sgopFCTgtJ7qF8B83Xy/+hJ5vD29xsbSwuB - 3iLE7qLxu2NxdIa4oL0Y2QKMh/getjI0xnvwAmPkFiFbzC7LFdDfd6+gA5GpUXxL - u6UmwucAgiljGQIDAQABoycwJTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI - KwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAFvRW/mGjnnMNFKJc/e3o/+yiJor - dcrq/1UzyD7eNmOaASXz8rrrFT/6/TBXExPuB2OIf9OgRJFfPGLxmzCwVgaWQbK0 - VfTN4MQzRrSwPmNYsBAAwLxXbarYlMbm4DEmdJGyVikq08T2dZI51GC/YXEwzlnv - ldN0dBflb/FKkY5rAp0JgpHLGKeStxFvB62noBjWfrm7ShCf9gkn1CjmgvP/sYK0 - pJgA1FHPd6EeB6yRBpLV4EJgQYUJoOpbHz+us62jKj5fAXsX052LPmk9ArmP0uJ1 - CJLNdj+aShCs4paSWOObDmIyXHwCx3MxCvYsFk/Wsnwura6jGC+cNsjzSx4= - -----END CERTIFICATE----- diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml new file mode 100644 index 000000000..7b9affff2 --- /dev/null +++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml @@ -0,0 +1,13 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedKeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://yourkeyvault.vault.azure.net/ + certificates: + - name: yourCertName + version: yourCertVersion # Optional, fetch latest version if empty + tenantID: + clientID: diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml new file mode 100644 index 000000000..28aeaacb2 --- /dev/null +++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedKeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + refreshInterval: 1m + parameters: + vaultURI: https://yourkeyvault.vault.azure.net/ + certificates: + - name: yourCertName + version: yourCertVersion # Optional, fetch latest version if empty + tenantID: + clientID: diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml new file mode 100644 index 000000000..bc9dd6c75 --- /dev/null +++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_inline.yaml @@ -0,0 +1,29 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedKeyManagementProvider +metadata: + name: keymanagementprovider-inline +spec: + type: inline + parameters: + contentType: certificate + value: | + -----BEGIN CERTIFICATE----- + MIIDQzCCAiugAwIBAgIUDxHQ9JxxmnrLWTA5rAtIZCzY8mMwDQYJKoZIhvcNAQEL + BQAwKTEPMA0GA1UECgwGUmF0aWZ5MRYwFAYDVQQDDA1SYXRpZnkgU2FtcGxlMB4X + DTIzMDYyOTA1MjgzMloXDTMzMDYyNjA1MjgzMlowKTEPMA0GA1UECgwGUmF0aWZ5 + MRYwFAYDVQQDDA1SYXRpZnkgU2FtcGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A + MIIBCgKCAQEAshmsL2VM9ojhgTVUUuEsZro9jfI27VKZJ4naWSHJihmOki7IoZS8 + 3/3ATpkE1lGbduJ77M9UxQbEW1PnESB0bWtMQtjIbser3mFCn15yz4nBXiTIu/K4 + FYv6HVdc6/cds3jgfEFNw/8RVMBUGNUiSEWa1lV1zDM2v/8GekUr6SNvMyqtY8oo + ItwxfUvlhgMNlLgd96mVnnPVLmPkCmXFN9iBMhSce6sn6P9oDIB+pr1ZpE4F5bwa + gRBg2tWN3Tz9H/z2a51Xbn7hCT5OLBRlkorHJl2HKKRoXz1hBgR8xOL+zRySH9Qo + 3yx6WvluYDNfVbCREzKJf9fFiQeVe0EJOwIDAQABo2MwYTAdBgNVHQ4EFgQUKzci + EKCDwPBn4I1YZ+sDdnxEir4wHwYDVR0jBBgwFoAUKzciEKCDwPBn4I1YZ+sDdnxE + ir4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQEL + BQADggEBAGh6duwc1MvV+PUYvIkDfgj158KtYX+bv4PmcV/aemQUoArqM1ECYFjt + BlBVmTRJA0lijU5I0oZje80zW7P8M8pra0BM6x3cPnh/oZGrsuMizd4h5b5TnwuJ + hRvKFFUVeHn9kORbyQwRQ5SpL8cRGyYp+T6ncEmo0jdIOM5dgfdhwHgb+i3TejcF + 90sUs65zovUjv1wa11SqOdu12cCj/MYp+H8j2lpaLL2t0cbFJlBY6DNJgxr5qync + cz8gbXrZmNbzC7W5QK5J7fcx6tlffOpt5cm427f9NiK2tira50HU7gC3HJkbiSTp + Xw10iXXMZzSbQ0/Hj2BF4B40WfAkgRg= + -----END CERTIFICATE----- diff --git a/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml new file mode 100644 index 000000000..bc4b32951 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_json.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "config-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + artifactVerificationPolicies: + default: "all" diff --git a/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml new file mode 100644 index 000000000..9aaec1393 --- /dev/null +++ b/config/samples/namespaced/policy/config_v1beta1_policy_rego.yaml @@ -0,0 +1,31 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedPolicy # NamespacedPolicy only applies to specified namespace. +metadata: + name: "ratify-policy" # metadata.name MUST be set to ratify-policy since v1beta1. +spec: + type: "rego-policy" # Ensure that spec.type is either 'rego-policy' or 'config-policy' in v1beta1. + parameters: + passthroughEnabled: false + policy: | + package ratify.policy + + default valid := false + + # all artifacts MUST be valid + valid { + not failed_verify(input) + } + + # all reports MUST pass the verification + failed_verify(reports) { + [path, value] := walk(reports) + value == false + path[count(path) - 1] == "isSuccess" + } + + # each artifact MUST have at least one report + failed_verify(reports) { + [path, value] := walk(reports) + path[count(path) - 1] == "verifierReports" + count(value) == 0 + } diff --git a/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml b/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml new file mode 100644 index 000000000..1e9926345 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_dynamic.yaml @@ -0,0 +1,8 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-dynamic +spec: + name: dynamic + source: + artifact: wabbitnetworks.azurecr.io/test/sample-store-plugin:v1 diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras.yaml new file mode 100644 index 000000000..d9774331e --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + cosignEnabled: true + ttl: 10 diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml new file mode 100644 index 000000000..dfd1bc390 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras_http.yaml @@ -0,0 +1,11 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + cosignEnabled: true + ttl: 10 + useHttp: true \ No newline at end of file diff --git a/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml b/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml new file mode 100644 index 000000000..965bced51 --- /dev/null +++ b/config/samples/namespaced/store/config_v1beta1_store_oras_k8secretAuth.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedStore +metadata: + name: store-oras +spec: + name: oras + parameters: + cacheEnabled: true + ttl: 10 + useHttp: true + authProvider: + name: k8Secrets + secrets: + - secretName: ratify-dockerconfig \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml new file mode 100644 index 000000000..e8c0ac4cb --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml @@ -0,0 +1,24 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-license-checker +spec: + name: licensechecker + artifactTypes: application/vnd.ratify.spdx.v0 + parameters: + allowedLicenses: + - MIT + - GPL-2.0-only + - OpenSSL + - BSD-2-Clause AND BSD-3-Clause + - Zlib + - MPL-2.0 AND MIT + - ISC + - Apache-2.0 + - MIT AND BSD-2-Clause AND GPL-2.0-or-later, + - MIT AND LicenseRef-AND AND BSD-2-Clause AND LicenseRef-AND AND GPL-2.0-or-later + - MPL-2.0 AND LicenseRef-AND AND MIT + - BSD-2-Clause AND LicenseRef-AND AND BSD-3-Clause + - NONE + - NOASSERTION + - "" \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml new file mode 100644 index 000000000..44b4bda95 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml @@ -0,0 +1,15 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: default + scopes: + - "*" + keys: + - provider: default/ratify-cosign-inline-key-0 + tLogVerify: false diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml new file mode 100644 index 000000000..f973277ca --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_keyless.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + rekorURL: https://rekor.sigstore.dev \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml new file mode 100644 index 000000000..7c80620f4 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_cosign_legacy.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + key: /usr/local/ratify-certs/cosign/cosign.pub \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml new file mode 100644 index 000000000..a795a608b --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_dynamic.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-dynamic +spec: + name: dynamic + artifactTypes: application/vnd.ratify.spdx.v0 + source: + artifact: wabbitnetworks.azurecr.io/test/sample-verifier-plugin:v1 diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml new file mode 100644 index 000000000..83f0d72e6 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml @@ -0,0 +1,24 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + ca: + ca-certs: + - default/ratify-notation-inline-cert-0 + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:ca-certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml new file mode 100644 index 000000000..e076c6c98 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_kmprovider.yaml @@ -0,0 +1,24 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + ca: + ca-certs: + - kmprovider-akv + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:ca-certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml new file mode 100644 index 000000000..83f0d72e6 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml @@ -0,0 +1,24 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + ca: + ca-certs: + - default/ratify-notation-inline-cert-0 + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:ca-certs + trustedIdentities: + - "*" diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml new file mode 100644 index 000000000..7761e77ba --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_partial_licensechecker.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-license-checker +spec: + name: licensechecker + artifactTypes: application/vnd.ratify.spdx.v0 + parameters: + allowedLicenses: + - MIT diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml new file mode 100644 index 000000000..d7dee04b7 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml @@ -0,0 +1,9 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-sbom +spec: + name: sbom + artifactTypes: application/spdx+json + parameters: + nestedReferences: application/vnd.cncf.notary.signature \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml new file mode 100644 index 000000000..414bf6ac8 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_sbom_deny.yaml @@ -0,0 +1,15 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-sbom +spec: + name: sbom + version: 2.0.0-alpha.1 + artifactTypes: application/spdx+json + parameters: + disallowedLicenses: + - Zlib + disallowedPackages: + - name: musl-utils + version: 1.2.3-r4 + nestedReferences: application/vnd.cncf.notary.signature \ No newline at end of file diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml new file mode 100644 index 000000000..b432778c2 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-schemavalidator +spec: + name: schemavalidator + artifactTypes: application/vnd.aquasecurity.trivy.report.sarif.v1 + parameters: + schemas: + application/sarif+json: https://json.schemastore.org/sarif-2.1.0-rtm.5.json diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml new file mode 100644 index 000000000..ad489fea3 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml @@ -0,0 +1,10 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-schemavalidator +spec: + name: schemavalidator + artifactTypes: application/vnd.aquasecurity.trivy.report.sarif.v1 + parameters: + schemas: + application/sarif+json: https://json.schemastore.org/sourcehut-build-0.65.0.json diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml new file mode 100644 index 000000000..879d843b6 --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml @@ -0,0 +1,14 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-vulnerabilityreport +spec: + name: vulnerabilityreport + artifactTypes: application/sarif+json + parameters: + maximumAge: 24h + disallowedSeverities: + - high + - critical + denylistCVEs: + - CVE-2021-44228 # Log4Shell diff --git a/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml new file mode 100644 index 000000000..adece04ba --- /dev/null +++ b/config/samples/namespaced/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml @@ -0,0 +1,11 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: NamespacedVerifier +metadata: + name: verifier-vulnerabilityreport +spec: + name: vulnerabilityreport + artifactTypes: application/sarif+json + parameters: + maximumAge: 24h + denylistCVEs: + - CVE-2021-44228 # Log4Shell diff --git a/config/samples/policy/config_v1alpha1_policy_json.yaml b/config/samples/policy/config_v1alpha1_policy_json.yaml deleted file mode 100644 index 127bab714..000000000 --- a/config/samples/policy/config_v1alpha1_policy_json.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1alpha1 -kind: Policy -metadata: - name: "configpolicy" -spec: - parameters: - artifactVerificationPolicies: - default: "all" diff --git a/config/samples/policy/config_v1beta1_policy_json.yaml b/config/samples/policy/config_v1beta1_policy_json.yaml deleted file mode 100644 index 1fbf1adba..000000000 --- a/config/samples/policy/config_v1beta1_policy_json.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: config.ratify.deislabs.io/v1beta1 -kind: Policy -metadata: - name: "ratify-policy" -spec: - type: "config-policy" - parameters: - artifactVerificationPolicies: - default: "all" diff --git a/crd.Dockerfile b/crd.Dockerfile index 2635b42e3..6606aa0af 100644 --- a/crd.Dockerfile +++ b/crd.Dockerfile @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM alpine as builder +FROM alpine@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d as builder ARG TARGETOS ARG TARGETARCH diff --git a/dev.helmfile.yaml b/dev.helmfile.yaml index 4fd0eb057..bec894af9 100644 --- a/dev.helmfile.yaml +++ b/dev.helmfile.yaml @@ -2,15 +2,15 @@ repositories: - name: gatekeeper url: https://open-policy-agent.github.io/gatekeeper/charts - name: ratify - url: ghcr.io/deislabs/ratify-chart-dev # PRERELEASE: Change to 'https://deislabs.github.io/ratify' before copying to helmfile.yaml + url: ghcr.io/ratify-project/ratify-chart-dev # PRERELEASE: Change to 'https://ratify-project.github.io/ratify' before copying to helmfile.yaml oci: true # PRERELEASE: Remove before copying to helmfile.yaml - + releases: - name: gatekeeper namespace: gatekeeper-system createNamespace: true chart: gatekeeper/gatekeeper - version: 3.15.0 + version: 3.17.0 wait: true set: - name: enableExternalData @@ -34,14 +34,14 @@ releases: command: "bash" args: - "-c" - - "kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml && kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "kubectl apply -f https://ratify-project.github.io/ratify/library/default/template.yaml && kubectl apply -f https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - events: ["postuninstall"] showlogs: true command: "kubectl" args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/template.yaml" + - "https://ratify-project.github.io/ratify/library/default/template.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -49,7 +49,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -62,6 +62,10 @@ releases: - "certificatestores.config.ratify.deislabs.io" - "policies.config.ratify.deislabs.io" - "keymanagementproviders.config.ratify.deislabs.io" + - "namespacedkeymanagementproviders.config.ratify.deislabs.io" + - "namespacedpolicies.config.ratify.deislabs.io" + - "namespacedstores.config.ratify.deislabs.io" + - "namespacedverifiers.config.ratify.deislabs.io" - events: ["postuninstall"] showlogs: true command: "kubectl" @@ -72,7 +76,7 @@ releases: - "-n" - "gatekeeper-system" set: - - name: notationCert - value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt") | quote }} + - name: notationCerts[0] + value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/notation.crt") | quote }} - name: featureFlags.RATIFY_CERT_ROTATION value: true diff --git a/dev.high-availability.helmfile.yaml b/dev.high-availability.helmfile.yaml index 3beac173d..29c40fe8a 100644 --- a/dev.high-availability.helmfile.yaml +++ b/dev.high-availability.helmfile.yaml @@ -6,21 +6,21 @@ repositories: - name: bitnami url: https://charts.bitnami.com/bitnami - name: ratify - url: ghcr.io/deislabs/ratify-chart-dev # PRERELEASE: Change to 'https://deislabs.github.io/ratify' before copying to helmfile.yaml + url: ghcr.io/ratify-project/ratify-chart-dev # PRERELEASE: Change to 'https://ratify-project.github.io/ratify' before copying to helmfile.yaml oci: true # PRERELEASE: Remove before copying to helmfile.yaml - + releases: - name: dapr namespace: dapr-system createNamespace: true chart: dapr/dapr - version: 1.11.1 + version: 1.13.2 wait: true - name: gatekeeper namespace: gatekeeper-system createNamespace: true chart: gatekeeper/gatekeeper - version: 3.15.0 + version: 3.17.0 wait: true set: - name: enableExternalData @@ -63,14 +63,14 @@ releases: command: "bash" args: - "-c" - - "export SIGN_KEY=$(openssl rand 16 | hexdump -v -e '/1 \"%02x\"' | base64) && curl https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/dapr/dapr-redis-secret.yaml | yq e '.data.signingKey = strenv(SIGN_KEY)' | kubectl apply -f -" + - "export SIGN_KEY=$(openssl rand 16 | hexdump -v -e '/1 \"%02x\"' | base64) && curl https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/dapr/dapr-redis-secret.yaml | yq e '.data.signingKey = strenv(SIGN_KEY)' | kubectl apply -f -" - events: ["presync"] showlogs: true command: "kubectl" args: - "apply" - "-f" - - "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/dapr/dapr-redis.yaml" + - "https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/dapr/dapr-redis.yaml" - "-n" - "gatekeeper-system" - events: ["presync"] @@ -78,14 +78,14 @@ releases: command: "bash" args: - "-c" - - "kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml && kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "kubectl apply -f https://ratify-project.github.io/ratify/library/default/template.yaml && kubectl apply -f https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - events: ["postuninstall"] showlogs: true command: "kubectl" args: - "delete" - "-f" - - "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/dapr/dapr-redis-secret.yaml" + - "https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/dapr/dapr-redis-secret.yaml" - "-n" - "gatekeeper-system" - "--ignore-not-found=true" @@ -95,7 +95,7 @@ releases: args: - "delete" - "-f" - - "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/dapr/dapr-redis.yaml" + - "https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/dapr/dapr-redis.yaml" - "-n" - "gatekeeper-system" - "--ignore-not-found=true" @@ -105,7 +105,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/template.yaml" + - "https://ratify-project.github.io/ratify/library/default/template.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -113,7 +113,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -125,6 +125,10 @@ releases: - "verifiers.config.ratify.deislabs.io" - "certificatestores.config.ratify.deislabs.io" - "policies.config.ratify.deislabs.io" + - "namespacedkeymanagementproviders.config.ratify.deislabs.io" + - "namespacedpolicies.config.ratify.deislabs.io" + - "namespacedstores.config.ratify.deislabs.io" + - "namespacedverifiers.config.ratify.deislabs.io" - events: ["postuninstall"] showlogs: true command: "kubectl" @@ -141,8 +145,8 @@ releases: value: true - name: logger.level value: debug - - name: notationCert - value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt") | quote }} + - name: notationCerts[0] + value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/ratify-project/ratify/main/test/testdata/notation.crt") | quote }} - name: replicaCount value: 2 - name: provider.cache.type diff --git a/docs/README.md b/docs/README.md index d6949b16d..85b8a8836 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ List of implemented design and discussion documents. Please open a PR to update ### Implemented +* [Cosign Upgrade 2024](design/Cosign%20Upgrade%202024.md) * [Vulnerability Report Verifier](design/Ratify%20Vulnerability%20Report%20Verifier.md) * [Load Testing Pipeline](design/Load%20Testing%20Pipeline.md) * [TLS Certificate Rotation](design/TLS%20Certificate%20Rotation.md) @@ -24,6 +25,7 @@ List of implemented design and discussion documents. Please open a PR to update * [Verification Result Cache at Executor Level](design/Verification%20Result%20Cache%20at%20Executor%20Level.md) ### Discussion +* [Cosign Upgrade Discussion 2024](discussion/Cosign%20Upgrade%20Discussion%202024.md) * [Gatekeeper Timeout Constraint](discussion/Gatekeeper%20Timeout%20Constraint.md) * [Image Platform Selection](discussion/Image%20Platform%20Selection.md) * [Negative Test Cases v1.0.0](discussion/Negative%20test%20cases%20for%20Ratify.md) diff --git a/docs/design/Authentication Provider Support For ORAS Store.md b/docs/design/Authentication Provider Support For ORAS Store.md index 378e31a1b..fa7b63eb3 100644 --- a/docs/design/Authentication Provider Support For ORAS Store.md +++ b/docs/design/Authentication Provider Support For ORAS Store.md @@ -4,7 +4,7 @@ Author: Akash Singhal (@akashsinghal) General Design Document for [Ratify Auth](https://hackmd.io/LFWPWM7wT_icfIPZbuax0Q#Auth-using-metadata-service-endpoint-in-k8s) -Linked PR: https://github.com/deislabs/ratify/pull/123 +Linked PR: https://github.com/ratify-project/ratify/pull/123 ## Goals @@ -153,7 +153,7 @@ type OrasStoreConf struct { } ``` -Update `Create` [method](https://github.com/deislabs/ratify/blob/6edd4ceedc21cf704857eae56b2197e0e28f0f93/pkg/referrerstore/oras/oras.go#L68) in oras.go +Update `Create` [method](https://github.com/ratify-project/ratify/blob/6edd4ceedc21cf704857eae56b2197e0e28f0f93/pkg/referrerstore/oras/oras.go#L68) in oras.go ``` func (s *orasStoreFactory) Create(version string, storeConfig config.StorePluginConfig) (referrerstore.ReferrerStore, error) { diff --git a/docs/design/Azure Kubernetes Workload Identity AuthProvider.md b/docs/design/Azure Kubernetes Workload Identity AuthProvider.md index 90836b038..6c0ffa63c 100644 --- a/docs/design/Azure Kubernetes Workload Identity AuthProvider.md +++ b/docs/design/Azure Kubernetes Workload Identity AuthProvider.md @@ -19,7 +19,7 @@ User would need to follow instructions [here](https://azure.github.io/azure-work The official steps for setting up Workload Identity on AKS can be found [here](https://azure.github.io/azure-workload-identity/docs/quick-start.html). 1. Create ACR -2. Create OIDC enabled AKS cluster by follow steps [here](https://docs.microsoft.com/en-us/azure/aks/cluster-configuration#oidc-issuer-preview) +2. Create OIDC enabled AKS cluster by follow steps [here](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer#create-an-aks-cluster-with-oidc-issuer) 3. Save the cluster's OIDC URL: `az aks show --resource-group --name --query "oidcIssuerProfile.issuerUrl" -otsv` 4. Install Mutating Admission Webhook onto AKS cluster by following steps [here](https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html) 5. As the guide linked above shows, it's possible to use the AZ workload identity CLI or the regular az CLI to perform remaining setup. Following steps follow the AZ CLI. diff --git a/docs/design/Config Policy Provider Refactor.md b/docs/design/Config Policy Provider Refactor.md index f09e86679..edd1496d1 100644 --- a/docs/design/Config Policy Provider Refactor.md +++ b/docs/design/Config Policy Provider Refactor.md @@ -13,7 +13,7 @@ We'd like to redesign the `configPolicy` provider to enhance the Ratify while ad # Design Considerations -The new policy provider should cover but not limited to address these issues: [#351](https://github.com/deislabs/ratify/issues/351), [#528](https://github.com/deislabs/ratify/issues/528), [#448](https://github.com/deislabs/ratify/issues/448), [35](https://github.com/deislabs/ratify/issues/35) +The new policy provider should cover but not limited to address these issues: [#351](https://github.com/ratify-project/ratify/issues/351), [#528](https://github.com/ratify-project/ratify/issues/528), [#448](https://github.com/ratify-project/ratify/issues/448), [35](https://github.com/ratify-project/ratify/issues/35) ## Targets 1. Avoid introducing breaking changes to existing interfaces. @@ -245,7 +245,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validation[1].nestedReports) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -309,7 +309,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validation[1].nestedReports) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -367,7 +367,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validate[1]) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -466,7 +466,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validate[1]) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } # check if there is an invalid subject diff --git a/docs/design/Cosign Upgrade 2024.md b/docs/design/Cosign Upgrade 2024.md new file mode 100644 index 000000000..03782ae63 --- /dev/null +++ b/docs/design/Cosign Upgrade 2024.md @@ -0,0 +1,574 @@ +# Cosign Upgrade 2024 +Author: Akash Singhal (@akashsinghal) + +Tracked issues in scope: +- [Support Cosign verification with multiple keys](https://github.com/ratify-project/ratify/issues/1191) +- [Support for Cosign verification with keys managed in KMS](https://github.com/ratify-project/ratify/issues/1190) +- [Support Cosign verification with RSA key](https://github.com/ratify-project/ratify/issues/1189) + +Ratify currently supports keyless cosign verification which includes an optional custom Rekor server specification. Transparency log verification only occurs for keyless scenarios. Keyed verification is limited to a single public key specified as a value provided in the helm chart. The chart creates a `Secret` for the cosign key and mounts it at a well-known path in the Ratify container. Users must manually update the `Secret` to update the key. There is no support for multiple keys. There is no support for keys stored KMS. There is only support for ECDSA keys, and not RSA or ED25519. There is no support for certificates. + +## Support key configuration as K8s resource + +Currently, cosign verifier looks for a single key that already exists at a specified path in the Ratify container. Ratify helm chart contains a `Secret` which is mounted at the specified mount point. Ratify's key management experience should be decoupled from secrets and mount paths. Instead, it should be a first class key management experience similar to how certificates are managed via `CertificateStore`. + +### User Experience + +A new resource `KeyManagementProvider` will be introduced and the `CertificateStore` will be deprecated. `CertificateStore` will be maintained until Ratify v2.0.0. Only one resource type can be enabled at the same time. If a user attempts to apply the opposite type resource when one already exists for the opposing type, then a warning message will be shown in the Ratify logs. + +Compared to the `CertificateStore`, the `KeyManagementProvider` (KMP) config spec will be updated to be more flexible. A new `name` field will be used only in CLI scenarios to mirror CRD name functionality as a unique identifier. This enables multiple KMP of same type to be used. The `type` field corresponds to the existing `provider` field in the `CertificateStore`. + +Inline Key Management Provider with keys +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: ratify-cosign-inline-key + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +Azure Key Vault KeyManagementProvider with keys +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: kmp-akv + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: azurekeyvault + parameters: + vaultURI: VAULT_URI + keys: + - name: KEY_NAME + version: KEY_VERSION + tenantID: TENANT_ID + clientID: CLIENT_ID +``` + +Azure Key Vault Key Management Provider with keys + certificates +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: kmp-akv + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: azurekeyvault + parameters: + vaultURI: VAULT_URI + keys: + - name: KEY_NAME + version: KEY_VERSION + certificates: + - name: CERTIFICATE_NAME + version: CERTIFICATE_VERSION + tenantID: TENANT_ID + clientID: CLIENT_ID +``` + +CLI Config additions: The `keyManagementProviders` would be a new top level section in the `config.json`. +```json +{ + ... + "keyManagementProviders": { + { + "name": "ratify-notation-inline-cert-kmprovider", + "type": "inline", + "contentType": "key", + "value": "---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------" + }, + { + "name": "ratify-notation-inline-cert-kmprovider-2", + "type": "inline", + "contentType": "key", + "value": "---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------" + }, + } + ... +} +``` + +### Implementation Details + +- New API `GetKeys` + - based on the existing `CertificateStore` API + - return an a new map from `KMPMapKey` (`name` & `version`) to `PublicKey` which contains `crypto.PublicKey` & `ProviderType`. + - makes querying for keys by `name` and `version` much easier in cosign implementation + - `ProviderType` is useful for special AKV consideration in cosign implementation. +- Global `Key`s map + - follow same pattern as `Certificates` map which is updated on reconcile of the `CertificateStore` K8s resource. + - how do we store the certificates so they are partioned by namespace, resource unique name, and optionally certificate/key name + version? + - `Certificates` map will map `/` to a map of certificates for that particular resource + - map will be keyed by a special struct `KMPMapKey` which contains a `Name` and `Version` field + - each unique map key will map to a `PublicKey` which contains `crypto.PublicKey` and `ProviderType`. + - Inline provider will store a single key mapped to a single map entry + - AKV provider + - If only certificate/key name provided. Fetch the latest version and only populate the `Name` field for the map key + - If both name and version provided. Fetch specific version and populate both `Name` and `Version`. + - Note: A generic `Name` based fetched content will be considered uniquely different than a `Name` + `Version` content EVEN if the unversioned 'latest` is equal to a specified matching version. +- Cosign will be promoted to a built-in verifier and the plugin version will be removed + - MUST maintain complete feature experience parity + - Need to maintain legacy config and functionality +- Repurpose Inline Certificate Provider + - add new `contentType` field to spec: "key" or "certificate". If not provided, default to "certificate" for backwards compatability. +- Repurpose Azure Key Vault Certificate Provider + - use `GetKeys` API to fetch keys from configured key vault + - add new `keys` field to spec +- Multitenancy: supported by namespaced keys in the global `Keys` map + +### How do we support CLI scenario? + - CLI mode should support specification of a list of keys within the cosign verifier. Cosign plugin will handle directory configuration and key reading within implementation. Currently, one a single `key` path can be provided. This will be updated to support a `file` parameter in the trust Policy `keys` section (see trust policy details [here](#trust-policy)). The `provider` field is NOT required and is not allowed to be configured when the `file` is specified. For backward compatability, the existing `key` field will also be honored but that will default to previous legacy verification implementation. + - example: + ```json + { + ... + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "type": "cosign", + "name": "cosign-wabbit-networks", + "artifactTypes": "application/vnd.dev.cosign.artifact.sig.v1+json", + "trustPolicies": [ + { + "name": "wabbit-networks-images", + "keys": [ + { + "file": "/path/to/key1.pub" + }, + { + "file": "/path/to/key2.pub" + } + ] + } + ] + }, + } + ... + } + ``` + - `KeyManagementProvider` should be able to be configured via section in the `config.json`. This includes inline + any existing or future providers + - The current AKV integration relies on workload identity. The CLI scenario will use a non-Workload Identity scheme for authentication to managed identity. The AzureKeyVault provider must be updated to support a generic `DefaultAzureCredential` which has built in keychain support for many different types of authentication schemes. + +## Support Multiple keys for cosign verification + +Currently, there is only support for a single key per cosign verifier. + +- Goals: + - Cosign verifier should be updated to trust a set of keys and perform verification using that trusted key set + - Cosign verifier MUST match the verification logic of Cosign CLI for a single Key + - Cosign verifier should be able to operate a loose ('All keys are equally trusted and ONLY one is requred to be used in validation') or strict (ALL keys are trusted and ALL keys must be used in validation) enforcement. + +### How does Cosign CLI verification work today? + +- Image manifest pushed to same repository as subject (tag schema: `sha256-abc123.sig`) +- Image manifest is updated every time new signature is added. Each layer is a different signature (possibly from different signing keys) +```json +# Sample Cosign OCI Image Manifest +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "size": 451, + "digest": "sha256:bbacdc3dc4b33c868c6c27192cb1d1bda619c1454abb57ebea806eac13250d3c" + }, + "layers": [ + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEQCIGoZazNt1/eEBc08AmcqXY+yNQQJY2ziZPiIopDjHnU5AiAUgeWBy90GgqDdnWpxd1b3lbqzgr2UV2ZyR59eDOyxVg==" + } + }, + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEUCIBVcGGZR6pvJ2SQ8KI0TZY02SLFSSOFJuJph2Qcm8cWZAiEArL+BF5BGRGxELVBeJtAUy8i7L1uuBeUT9fF6TrFYVLQ=" + } + }, + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEQCIEIsK5yr1kGS1dgRmbudT55oghADdyAOfUwlnayPKAmmAiBTdZkhgHkZdw11tullz7HNOg5O1r5U/j4m2RE39b92Vg==" + } + } + ] +} +``` +- Image signature succeeds if AT LEAST one of the signatures verifies true with the corresponding key + - Validation occurs on each signature in the manifest against the SAME key + - Cosign only supports validation against a SINGLE key at a time + +### Trust Policy +Cosign verifier should support multiple trust policies based on the KeyManagementProviders (KMP) enabled and the desired verification scenario. Please refer to [this](#user-scenarios) section for common user scenarios. At a high level users must be able to have: + - multiple KMP `inline` key resources (each `inline` will have a single key) + - multiple keys defined in a single AKV KMP + - multiple KMP `inline` certificate resources (each `inline` may have a single cert or a cert chain) + - multiple certificates definine in a single AKV KMP + - mix of keys + certificates in a single AKV KMP + - a way to specify specific keys/certificates within a KMP by name and version + - a way to scope a trust policy to a particular registry/repo/image an wildcard characters '*' + - multiple trust policies + - a way to specify enforcement for that specific trust policy + - skip: don't perform any verification for an image reference that matches this policy + - any: at least one of the keys/certificates trusted in the policy must result in a successful verification for overall cosign verification to be true + - all: ALL keys/certificates trusted in the policy must result in a successful verification for overall cosign verification to be true + - a way to define certificates to be used in a trust policy for Trusted Timestamp verification `tsaCertificates` + - a way to define options per trust policy for transparency log verification `tLogVerify` + - a way to define options per trust policy for keyless verification under a section called `keyless` + - certificate transparency log lookup `ctLogVerify` + - `rekorURL` for custom rekor instances + - `certificateIdentity`: certificate identity verifier should be configured to trust (OPTIONAL iff certificateIdentityExp is defined) + - `certificateIdentityExp`: wildcard expressions mapping to certificate identitie(s) verifier should be configured to trust (OPTIONAL iff certificateIdentity is defined. Overrides certificateIdentity if also specified) + - `certificateOIDCIssuer`: certificate OIDC Issuer verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuerExp is defined) + - `certificateOIDCIssuerExp`: wildcard expressions mapping to certificate OIDC Issuer(s) verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuer is defined. Overrides certificateOIDCIssuer if also specified) + - a way to scope the policy based on `artifactType` if we have nested verification `artifactTypeScopes` + - if the user defines `artifactTypeScopes`, then the `scopes` are applied first and then `artifactTypeScopes`. `artifactTypeScopes` are only enforced if the subject image manifest being verified contains a `subject` field (basically is a referrer). + - a way to specify local path keys using a `file` (Consider adding support for external URL and env variables to load file) + +> NOTE: The sample below is an experience goal and not all configurations will be implemented initially + +Sample +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: wabbit-networks-images + scopes: + - wabbitnetworks.myregistry.io/carrots/* + - wabbitnetworks.myregistry.io/beets/* + artifactTypeScopes: # OPTIONAL: applied after scopes; only considered for nested verification scenarios. + - application/sarif+json + - application/spdx+json + keys: # list of keys that are trusted. Only the keys in KMS are considered + - provider: inline-keys-1 # REQUIRED: if name is not provided, all keys are assumed to be trusted in KeyManagementProvider resource specified + - provider: inline-keys-2 + - provider: akv-wabbit-networks + name: wabbit-networks-io # OPTIONAL: key name (inline will not support name since each inline has only one key/certificate) + version: 1234567890 # OPTIONAL: key version (inline will not support version) + - file: /path/to/key.pub # absolute file path to local key path. Useful for CLI scenarios + certificates: # list of certificates that are trusted. Only the certificates in KMS are considered + - provider: nline-certs-1 + tsaCertificates: + - provider: inline-certs-tsa-1 + tLogVerify: true # transparency log verification (default to false) + keyless: + ctLogVerify: false # certificate transparency log verification boolean (default to true) + rekorURL: customrekor.io # rekor URL for transparency log verification (default to sigstore's public-good endpoint) + certificateIdentity: wabbit-identity # certificate identity verifier should be configured to trust (OPTIONAL iff certificateIdentityExp is defined) + certificateIdentityExp: # wildcard expressions mapping to certificate identitie(s) verifier should be configured to trust (OPTIONAL iff certificateIdentity is defined. Overrides certificateIdentity if also specified) + certificateOIDCIssuer: wabbit-issuer # certificate OIDC Issuer verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuerExp is defined) + certificateOIDCIssuerExp: # wild card expressions mapping to certificate OIDC Issuer(s) verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuer is defined. Overrides certificateOIDCIssuer if also specified) + enforcement: any # skip (don't perform any verification and auto pass), any (at least one key/cert used in successfull verification is overall success), all (all keys/certs must be used for overall success) + - name: verification-bypass + scopes: + - wabbitnetworks.myregistry.io/golden + enforcement: skip +``` + +To start, support will include multiple `trustPolicies` with `keys` list specified. Each `key` entry can provider a `provider`, `name`, `version` OR a `file` path. Trust policies will support `scopes`. The behavior will match an equivalent `enforcement` of `any`. Future work will add support for remainig configs. + +The `provider` field, where the name of the `KeyManagementProvider` (KMP) can be specified, is assumed to be referencing a `KMP` in the same namespace as the cosign verifier. If the user would like to specify a `provider` in a different namespace, the user must append `/` to the front of the `KMP` name. + +#### Scopes +The `scopes` section determines which trust policy will apply given an image reference. A global `*` wildcard character can be used as the default fallback of all other trust policies with `scopes` that do not match. Only a single trust policy can have a `*` scope. Wild card support is limited to suffix of a scope and denoted by '*' as the final character in the scope. Scopes with absolute or wild cards CANNOT overlap. On verifier creation, cosign will enforce that all scopes are deterministically exclusive. This is important so users cannot accidentally match multiple scopes depending on the image reference. + +### User Scenarios + +#### 1 Signature, 2 trusted keys +Bob has a container image he built. His trusted pool contains 2 self-managed keys to sign the image using `cosign`. Only ONE of the keys is used for signing at a time. There is only ONE signature. Bob's organization utilizes and trusts both keys. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature using AT LEAST one key from a trusted pool. + +- Bob installs Ratify on the cluster +- Bob applies 2 new inline `KeyManagementProvider` CR onto the cluster, `inline-key-1` & `inline-key-2`. Each CR will have a key Bob's organization trusts. +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-1 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-2 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: multiple-trusted-keys + keys: + - provider: inline-key-1 + - provider: inline-key-2 +``` +- Bob attempts to deploy a pod from an image that has cosign signature signed with key in KeyManagementProvider `inline-key-1`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature signed with key in KeyManagementProvider `inline-key-2`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature signed with key NOT in `inline-key-1` or `inline-key-2`. Pod FAILS verification and blocked. + +#### 2 Signatures but only 1 is from a key that he trusts + +Bob has a container image that he imported from another registry. An existing cosign signature, signed by an entity he doesn't trust, is already associated with the image. After vetting the image, he utilizes 1 self-managed key to sign the image using `cosign`. Now the image has 2 cosign signatures. Bob only trusts his key. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature using only HIS key that he trusts. + +- Bob installs Ratify on the cluster +- Bob applies a new inline `KeyManagementProvider` CR onto the cluster, `inline-key` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: single-trusted-key + keys: + - provider: inline-key +``` +- Bob attempts to deploy a pod from the vetted image that has 2 cosign signatures, one of which is signed with key in KeyManagementProvider `inline-key`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature(s) signed with a different key in `inline-key`. Pod FAILS verification and blocked. + +#### 2 Signatures 2 Keys: both keys must be used + +Bob has a container image that is produced from a build pipeline and tested via a testing pipeline. After each pipeline, the image is signed with cosign using a SEPARATE key. Now the image has 2 cosign signatures. Bob trusts both keys and requires BOTH keys to be used. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature from his build AND test pipeline. + +- Bob installs Ratify on the cluster +- Bob applies a new inline `KeyManagementProvider` CR onto the cluster, `inline-key-build` & `inline-key-test` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-build + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-test + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + provider: inline + parameters: + type: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: build-test-verification + keys: + - provider: inline-key-build + - provider: inline-key-test + enforcement: all +``` +- Bob attempts to deploy a pod from the vetted image that has 1 cosign signature, which is signed with key in KeyManagementProvider `inline-key-build`. Pod FAILS verification and blocked. +- Bob attempts to deploy a pod from the vetted image that has 2 cosign signatures, which is signed with keys in KeyManagementProvider `inline-key-build` & `inline-key-test`. Pod passes verification and is created. + +> **NOTE**: Based on Cosign's own verification implementation, there does not seem to be a way to enforce that ALL signatures found are valid. This behavior is unchartered as far as we can tell and would require Ratify to determine if we want to support this directly. For that reason, the strict ALL signature scenario is not yet included. + +#### Extra Considerations +- How does Kyverno handle multiple Keys? + - Each key specified is a separate cosign verification operation + - User specifies enforcement type by specifying `count` of verification operations that must succeed. + - e.g 2 keys specified and 2 signatures on the image. each verification is against both signatures for a single key + - If count is 2, then 2 verifications must return true +- How will this work with OCI 1.1 enabled cosign? + - Cosign OCI 1.1 support is experimental + - Currently, an arbitrary signature from list of referrers is chosen to perform verifications on IF multiple cosign artifacts are found. + - This logic will be updated in the future once 1.1 is GA but for now it's left as a TODO in the Cosign code base. + +### Implementation Details + +#### Handling multiple verifications at once +- Ratify will largely mirror how cosign performs multi-signature verification + - cannot invoke cosign's multi signature verification API since the API exposed also includes the registry content fetch which is independent in Ratify + +## Support RSA, EC, and ED25519 keys for verification + +Each key specified will encapsulate a single verification context for all signatures found in the artifact that the verifier is running for. + +Verification flow per key: +- Take raw key content (byte array) and [`LoadPublicKeyRaw()`](https://github.com/sigstore/cosign/blob/daf1eeb5ff2022d11466b8eccda25768b2dd2992/pkg/signature/keys.go#L91C1-L91C1) +- The `LoadPublicKeyRaw()` method in the cosign keys library will automatically detect which key type (based on the key header) and return the appropriate signature verifier +- Verifier is then passed to cosign's `VerifySignature()` method as a verification option + +## Support keyless verification with OIDC identities +Newer versions of Cosign require that the user defines which identities and OIDC issuers they trust for keyless verifications. Ratify's Cosign verifier should `certificate-identity`, `certificate-identity-exp`, `certificate-oidc-issuer`, `certificate-oidc-issuer-regexp` to match Cosign cli behavior. The identity and issuer specification is REQUIRED and will be enforced during config validation. + +### Implementation +- add 4 parameters to the `keyless` section of the trust policy config +- add new config validation logic to make sure at least the issuer & identity is defined or accompanying regexp +- pass through issuer & identity parameters as options to the cosign verifier `VerifyImage` function. + +## Dev Work Items +- Introduce new `KeyManagementProvider` CRD to replace `CertificateStore` (~ 2 weeks) + - maintain old `CertificateStore` controllers and source code for backwards compat + - define new `KeyManagementProvider` CRD + controllers + - port certificate providers implementation to new `KMP` object + - refactor to factory paradigm + - refactor to define rigid config schema (currently, only a generic map of attributes passed) + - add enforcement so only `KeyManagementProvider` OR `CertificateStore` can be enabled at a time + - add id support for certificates in the global `Certificates` map +- Add Key support to `KeyManagementProvider` (~ 2-3 weeks) + - update API + - update Inline provider with `type` field + - update AKV provider for `key` fetching logic + - update controllers to add new key maps for global + namespaced keys + - update controllers to add new certs maps for namespaced certs +- Cosign Verifier: migrate to a built in verifier (~ 1-2 weeks) +- Cosign verifier multi key support (~ 3 weeks) + - add `trustPolicies` + - support multiple `keys` each with `provider`, and optional `name`, `version` + - support `scopes` + - add multi key verification logic + - preserve existing file path based key support for backwards compatability +- Add more robust Cosign Keyless support (~1.5 weeks) + - add new section to Trust Policy + - add support for Trust policy to use keyless vs key when both are specified? + - add new support for `ctLogVerify` configuration +- Add RSA and ED25519 key support (~ 0.5 week) + - auto detect key type based on parsing library + - pick cosign verifier according to format +- Add docs and walkthroughs (~ 1 week) + - redo cosign walk through + - update all walkthroughs and samples to use `KeyManagementProvider` + - add reference docs for `KeyManagementProvider` + - mark `CertificateStore` docs as deprecated +- New e2e tests for different scenarios (~ 1 week) + +## Future Considerations +- Add `KeyManagementProvider` support to CLI + - Update `verify` command group to create `KeyManagementProvider` from config + - Update AKV provider for non Workload Identity auth +- Add Plugin support to `KeyManagementProvider` +- Add deprecation headers and warnings to `CertificateStore` CRD and code files +- Support `enforcement` with `skip`, `any`, and `all` +- Support attestations with cosign signature embedded +- Support certificate based verification of cosign signatures +- Verify image annotations +- Custom signature repository +- Transparency log and SCT verification options +- Keyless signature verification options + - Custom Rekor server + - Github workflows \ No newline at end of file diff --git a/docs/design/Metrics.md b/docs/design/Metrics.md index 010708dd1..b92a95a4d 100644 --- a/docs/design/Metrics.md +++ b/docs/design/Metrics.md @@ -32,7 +32,7 @@ OpenTelemetry exposes configurable providers throughout the metrics workflow: 1. Counter: Value that accumulates over time. (e.g request count, signatures verified) 2. Gauge: Point-in-time value of a continuous data stream (e.g speed, pressure) 3. Histogram: Ratify-side aggregation of measurements. Bascially a complex aggregation of Counters where each bin is bounded from the min value (0) to the upper bin boundary. For example if we had bin boundaries [0, 1, 2, 3, 4, 5] and the measured value is 3.5, then the resulting histogram would be [0, 0, 0, 1, 1]. -- Meter: Wraps a collection of instruments related to a specific scope. In Ratify's case, we'd have a single Meter with all of our instruments. The scope would be the Ratify application (`github.com/deislabs/ratify`) +- Meter: Wraps a collection of instruments related to a specific scope. In Ratify's case, we'd have a single Meter with all of our instruments. The scope would be the Ratify application (`github.com/ratify-project/ratify`) - Exporter: The vendor-specific metric reader implementation. Each exporter is responsible for consuming the metrics published to the data stream according to their vendor specification. The first provider we would support is Prometheus. - View: Defines/overrides the behavior for how metrics should be collected (e.g changing the name of the instrument, changing the bin values of histogram instrument) - Meter Provider: Creates the Meter and binds to the specified metric Exporter. It also is resonsible for mutating the metric data stream according to the Views specified in the options. diff --git a/docs/design/Policy Provider refactor (deprecated).md b/docs/design/Policy Provider refactor (deprecated).md index ca90bdea7..241fca76f 100644 --- a/docs/design/Policy Provider refactor (deprecated).md +++ b/docs/design/Policy Provider refactor (deprecated).md @@ -1,7 +1,7 @@ # Ratify Policy Provider Author: Akash Singhal (@akashsinghal) -Prerequisite: Read through the Executor Policy design section of this [doc](https://github.com/deislabs/ratify/tree/main/docs#executor-policy-specification) for more information on approaches to policy provider. +Prerequisite: Read through the Executor Policy design section of this [doc](https://github.com/ratify-project/ratify/tree/main/docs#executor-policy-specification) for more information on approaches to policy provider. Currently there's no scaffolding for multiple policy providers. The default config policy provider is built in. We need to add support for a policy plugin to be specified and selected. We also need to expand the policy provider plugin diff --git a/docs/design/Ratify Vulnerability Report Verifier.md b/docs/design/Ratify Vulnerability Report Verifier.md index 2eab2e353..a3eb19b88 100644 --- a/docs/design/Ratify Vulnerability Report Verifier.md +++ b/docs/design/Ratify Vulnerability Report Verifier.md @@ -205,7 +205,7 @@ Common policies such as age enforcement and severity filtering can be implemente Currently supports date filtering based on OCI annotation image creation and a list of disallowed severities. -PR can be found [here](https://github.com/deislabs/ratify/pull/1123) +PR can be found [here](https://github.com/ratify-project/ratify/pull/1123) [![asciicast](https://asciinema.org/a/622368.svg)](https://asciinema.org/a/622368) diff --git a/docs/design/Verification Result Cache at Executor Level.md b/docs/design/Verification Result Cache at Executor Level.md index 14ec5b4da..2915e2a0b 100644 --- a/docs/design/Verification Result Cache at Executor Level.md +++ b/docs/design/Verification Result Cache at Executor Level.md @@ -4,7 +4,7 @@ Author: Binbin Li (@binbin-li) ## Background -Jimmy noticed that Gatekeeper audit could trigger tons of requests to Ratify if there are many pods deployed, which might overwhelm the upstream services like remote registries. Related issue: [201](https://github.com/deislabs/ratify/issues/201) However, as the discussion happened offline, the audit result can be cached in api server chache. And we could also configure a new CRD to batch evaluation requests in a single ED request. +Jimmy noticed that Gatekeeper audit could trigger tons of requests to Ratify if there are many pods deployed, which might overwhelm the upstream services like remote registries. Related issue: [201](https://github.com/ratify-project/ratify/issues/201) However, as the discussion happened offline, the audit result can be cached in api server chache. And we could also configure a new CRD to batch evaluation requests in a single ED request. Since the discussion is not finished yet, we'll just focus on how to implement the cache instead of whether we need to have it. @@ -88,7 +88,7 @@ Components that Ratify would change/add: b. A map field that maps overall/registry/repo to a TTL value. c. We also need to determine the default behavior/TTL value if it's not specified. 2. A cache interface and implementation that supports adding and evicting entries automatically. -3. As proposed by Akash in this [issue](https://github.com/deislabs/ratify/issues/507), we can possibly add a cache lock to API cache as well. +3. As proposed by Akash in this [issue](https://github.com/ratify-project/ratify/issues/507), we can possibly add a cache lock to API cache as well. 4. Test on them. ### Stage 2 diff --git a/docs/design/kmp-periodic-retrieval.md b/docs/design/kmp-periodic-retrieval.md new file mode 100644 index 000000000..20bec5f33 --- /dev/null +++ b/docs/design/kmp-periodic-retrieval.md @@ -0,0 +1,104 @@ +# Periodic Key & Certificate Retrieval for KMP + +Author: Josh Duffney (@duffney) + +Tracked issues in scope: + +- https://github.com/ratify-project/ratify/issues/1131 + +Proposal ref: + +- https://github.com/ratify-project/ratify/blob/dev/docs/proposals/Automated-Certificate-and-Key-Updates.md + +## Problem Statement + +In v1.2.0 and earlier, Ratify does not support automatic refreshing, requiring manual updates to the KMP resource to accommodate key and certificate changes. This also means that Ratify continues to use the cached versions of keys or certificates, leading to potential issues such as: + +**Signature Verification Failures**: When images are signed with a newly rotated key version, Ratify’s cached key version fails to verify these signatures. + +**Persisting Old Images**: Signature verification may fail for older images if Ratify only caches the latest key version. + +**Usage of Disabled Keys**: Disabled keys, perhaps due to compromise, may still be used by Ratify from its cache, posing security risks. + +Manual updates to KMP resources to accommodate key rotation are cumbersome and prone to misconfigurations, potentially causing image verification failures and service downtime. + +## Proposed Solution + +To address these challenges, this proposal suggests automating the update process of KMP resources in Ratify. This can be achieved by implementing a requeue mechanism on the KMP resource at a user defined interval. + +## Implementation Details + +Kubernetes has a built-in feature that allows the controller's reconcile methods to be requeued, which is responsible for populating the certificate and key values from the providers in Ratify's KMP resources. This is achieved by passing {Requeue: true, RequeueAfter: interval} to the ctrl.Request returned by the KMP controller's reconcile method. + +However, not all providers support being refreshed. For example, an Inline provider would not benefit from being requeued after the resource is created. To address this, the KMP interface will be updated with an isRefreshable method. This allows the provider author to indicate whether the provider supports refreshing the certificates and keys for the resource. + +A new spec field called interval will be added to the keymanagementprovider_types.go file to determine when the refresh will occur. The interval can be specified as Xs, Xm, Xh, etc., indicating how often the KMP resource should refresh its certificates and keys. + +Refreshing the resources involves calling the getCertificate and getKeys methods of the provider configured for each resource. If a resource is not refreshable, these methods will only be called once to set up the resource and populate the in-memory maps containing the provider's keys and certificates. If the provider is refreshable, the get methods will be called again each time the interval triggers. + +An example of this implementation can be found below: + +```go +// keymanagementprovider_controller.go +func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + //Do not requeue + return ctrl.Result{} + //Requeue + return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil +} +``` + +```go +type KeyManagementProvider interface { + // Returns an array of certificates and the provider specific cert attributes + GetCertificates(ctx context.Context) (map[KMPMapKey][]*x509.Certificate, KeyManagementProviderStatus, error) + // Returns an array of keys and the provider specific key attributes + GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error) + // Returns if the provider supports refreshing of certificates & keys + IsRefreshable() bool +} +``` + +```yml +## Example KMP Resource +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: +name: keymanagementprovider-akv +spec: + type: azurekeyvault + interval: "1m" # defines the requeue interval of the resource. Aslo supports 1s,1m,1h formats. + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +--- +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: +name: keymanagementprovider-inline +spec: + type: inline + parameters: + contentType: key + value: ${Public_Key} +``` + +## Dev Work Items + +Suggested steps to implement the proposed solution: + +- Add `isRefreshable` method to the KMP interface +- Implement a `refresh` interface that encapsulates the reconcile logic +- Add an `Interval` field to the KMP CRD spec that supports the format "Xs,Xm,Xh" + +## Open Questions + +- How frequently should Ratify fetch the latest key or certificate versions from the KMS? +- How should Ratify support manual updates to the KMP cache? + +## Future Considerations + +- Event-Driven Key & Certificate Retrieval: Implementing an event-driven mechanism to fetch the latest key or certificate versions from the KMS based on specific triggers or events. diff --git a/docs/discussion/Cosign Upgrade Discussion 2024.md b/docs/discussion/Cosign Upgrade Discussion 2024.md new file mode 100644 index 000000000..ee4876d71 --- /dev/null +++ b/docs/discussion/Cosign Upgrade Discussion 2024.md @@ -0,0 +1,710 @@ +# Cosign Upgrade 2024 +Author: Akash Singhal (@akashsinghal) + +Tracked issues in scope: +- [Support Cosign verification with multiple keys](https://github.com/ratify-project/ratify/issues/1191) +- [Support for Cosign verification with keys managed in KMS](https://github.com/ratify-project/ratify/issues/1190) +- [Support Cosign verification with RSA key](https://github.com/ratify-project/ratify/issues/1189) +- [Support keyless verification with OIDC identities](https://github.com/ratify-project/ratify/issues/1323) + +Ratify currently supports keyless cosign verification which includes an optional custom Rekor server specification. Transparency log verification only occurs for keyless scenarios. Keyed verification is limited to a single public key specified as a value provided in the helm chart. The chart creates a `Secret` for the cosign key and mounts it at a well-known path in the Ratify container. Users must manually update the `Secret` to update the key. There is no support for multiple keys. There is no support for keys stored KMS. There is only support for ECDSA keys, and not RSA or ED25519. There is no support for certificates. + +## Support key configuration as K8s resource + +Currently, cosign verifier looks for a single key that already exists at a specified path in the Ratify container. Ratify helm chart contains a `Secret` which is mounted at the specified mount point. Ratify's key management experience should be decoupled from secrets and mount paths. Instead, it should be a first class key management experience similar to how certificates are managed via `CertificateStore`. + +### Option 1: Extend existing Certificate Store + +#### User Experience + +Inline certificate provider with key +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: CertificateStore +metadata: + name: ratify-cosign-inline-key + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + provider: inline + parameters: + type: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +Azure Key Vault certificate provider with keys +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: CertificateStore +metadata: + name: certstore-akv + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + provider: azurekeyvault + parameters: + vaultURI: VAULT_URI + keys: + - name: KEY_NAME + version: KEY_VERSION + tenantID: TENANT_ID + clientID: CLIENT_ID +``` + +Azure Key Vault certificate provider with keys + certificates +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: CertificateStore +metadata: + name: certstore-akv + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + provider: azurekeyvault + parameters: + vaultURI: VAULT_URI + keys: + - name: KEY_NAME + version: KEY_VERSION + certificates: + - name: CERTIFICATE_NAME + version: CERTIFICATE_VERSION + tenantID: TENANT_ID + clientID: CLIENT_ID +``` +#### Implementation Details + +- New API `GetKeys` + - added to the `CertificateStore` API + - return an array of `Key`s. + - contains `byte` array content +- Global `Key`s map + - follow same pattern as `Certificates` map which is updated on reconcile of the `CertificateStore` K8s resource. + - how do we store the certificates so they are partioned by namespace, resource unique name, and optionally certificate/key name + version? + - `Certificates` map will map `/` to a map of certificates for that particular resource + - map will be keyed by a special struct `KMPMapKey` which contains a `Name` and `Version` field + - each unique map key will map to an array x.509 certificates + - Inline provider will store all certs in a single map entry + - AKV provider + - If only certificate/key name provided. Fetch the latest version and only populate the `Name` field for the map key + - If both name and version provided. Fetch specific version and populate both `Name` and `Version`. + - Note: A generic `Name` based fetched content will be considered uniquely different than a `Name` + `Version` content EVEN if the unversioned 'latest` is equal to a specified matching version. +- Can we promote Cosign to be a built-in verifier like Notation is? + - This would allow us to use `CertificateStore` without having to build support for external plugins accessing certificates. + - It will also be slightly more performant if it can share the in-memory ORAS store cache. + - **UPDATE 1/18/24: Cosign verifier will be built-in** +- ~~How do we share certificates and keys with external plugins?~~ + - ~~All certs are loaded in an in-memory map currently. The map cannot be shared with external processes.~~ + - ~~External plugins should NOT be pulling certificates/keys per invocation (external KMS would be stressed. result in very expensive calls)~~ + - ~~Option 1: Serialize and add `Certificates` & `Keys` map into the external plugin json input~~ + - ~~Embed each map during plugin invocation~~ + - ~~namespace context aware for multi-tenancy~~ + - ~~Is security a concern for embedding certs + keys to invoke plugins not related to signature verification?~~ + - ~~Cosign verifier will be updated to deserialize and consume the maps~~ + - ~~Option 2: Store certs + keys on disk at well-known path~~ + - ~~`CertificateStore` will be refactored to store fetched certs from providers in a directory on disk~~ + ```diff + - .ratify/certificate_store/ + - -/ + - keys/ + - key1.rsa + - certificates/ + - cert1.pem + - cert2.pem + ``` + - ~~Configuration loading from JSON (CLI) or CRD reconcile loop will be responsible for deleting the directories of cert providers as they are removed~~ + - ~~External plugins will have to have the `CertificateStore` config passed in as JSON input in order to create instance of the cert provider objects~~ + - ~~External plugins will need to be namespace aware in order to know which directory to access~~ + - ~~Pros:~~ + - ~~Cosign plugin can manage accessing its own keys + certificates without executor having to coordinate~~ + - ~~Other external plugins will not get unnecessary keys + certs provided in json input~~ + - ~~Cons:~~ + - ~~Keys + certs must be added/deleted off disk~~ + - ~~What happens if keys + certs are left on disk due to Ratify failure?~~ + - ~~Keys + certs will be able to be accessed by any process with filesystem access~~ + - ~~CLI scenario will require fetching + deleting certs + keys off disk per invocation~~ + - ~~Existing cert providers used by notation will need to be updated to read/write certs from disk~~ + - ~~External plugins will need to be aware of namespace context in order to only access resources in namespaced directory~~ + - ~~Option 3 (NOT recommended): Add certificates + keys to shared cache~~ + - ~~Define two new cache keys partitioned by namespace~~ + - ~~CANNOT use the ristretto in-memory cache as it is since external cosign process must be able to access it~~ + - ~~This would require hard dependency on HA mode which is a bad experience. Why should a user need HA distributed cache just for Cosign?~~ + - ~~Implement a new cache provider for cache sharing between processes on same host~~ + - ~~NOT trivial~~ + - ~~Require a new http cache server and clients~~ + - ~~Cache provider initialization in cosign plugin implementation~~ + - ~~One advantage is that not only will cert + key sharing be solved but also oras store credentials can easily shared which is currently not possible for external plugins~~ +- Extend Inline Certificate Provider + - add new `type` field to spec: "key" or "certificate". If not provided, default to "certificate" for backwards compatability. +- Extend Azure Key Vault Certificate Provider + - use `GetKeys` API to fetch keys from configured key vault + - add new `keys` field to spec +- Multitenancy considerations + - Need a separate keys map for namespaced and global certificate store keys + +### **Option 2: Renaming CertificateStore to KeyManagementProvider** SELECTED IMPLEMENTATION +- Same as Option 1 except we introduce a new CRD that will replace `CertificateStore` due to potential naming confusion +- Is the existing name confusing enough to warrant changing to KeyManagementProvider? +- How would we support CRD name change? + - Introduce new `KeyManagementProvider` CRD resource and then deprecate `CertificateStore` + +#### Proposed Config Changes + +Compared to the `CertificateStore`, the `KeyManagementProvider` config spec could be updated to be more flexible. A new `name`` field will be used only in CLI scenarios to mirror CRD name functionality as a unique identifier. This enables multiple KMS of same type to be used. + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: ratify-notation-inline-cert-kmprovider + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ +``` + +CLI Config additions: The `keyManagementSystems` would be a new top level section in the `config.json`. +```json +{ + ... + "keyManagementSystems": { + { + "name": "ratify-notation-inline-cert-kmprovider", + "type": "inline", + "contentType": "key", + "value": "---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------" + }, + { + "name": "ratify-notation-inline-cert-kmprovider-2", + "type": "inline", + "contentType": "key", + "value": "---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------" + }, + } + ... +} +``` + +**Do we want to consider making the inline provider accept an array of content?** + +### Option 3: Introduce new CRD `KeyStore` separate from `CertificateStore` + +All of the same considerations as Option 1. It will be a completely new CRD with an inline and AKV provider equivalent. + +Pros: +- More intuitive naming: embedding 'key' specification in cert store might be confusing +- No breaking changes or backwards compatability requirements +Cons: +- Yet one more resource type user must be concerned to configure +- Providers will share almost the exact same implementation as certificate stores + +### How do we support CLI scenario? + - CLI mode should support specification of a list of keys within the cosign verifier. Cosign plugin will handle directory configuration and key reading within implementation. Currently, one a single `key` path can be provided. This will be updated to support an absolute `filePath` to a key per entry in the `keys` field of the Cosign trust policy (see trust policy details [here](#trust-policy)). The `provider` field is NOT required and is ignored if provided when the `filePath` is specified. For backward compatability, the existing `key` field will also be honored. + - example `config.json`: + ```json + { + ... + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "type": "cosign", + "name": "cosign-wabbit-networks", + "artifactTypes": "application/vnd.dev.cosign.artifact.sig.v1+json", + "trustPolicies": [ + { + "name": "wabbit-networks-images", + "keys": [ + { + "filePath": "/path/to/key1.pub" + }, + { + "filePath": "/path/to/key2.pub" + } + ] + } + ] + }, + } + ... + } + ``` + - Should we have a separate local directory cert store provider that gets configured like other cert stores or should we scope it only to the cosign plugin? + - In K8s scenarios, it will not be an encouraged pattern to specify local directory. This is why implementing directory cert reading as a cert store provider may not make sense. + - However, if a user decides to do a mixture of cert stores + local directory then they would need to specify in 2 different sections (cosign plugin config + cert store) + - **UPDATE 3/11/24**: We will provide a local `filePath` for configuring local public keys + - The current AKV integration relies on workload identity. The CLI scenario will use a non-WI scheme for authentication to managed identity + +## Support Multiple keys for cosign verification + +Currently, there is only support for a single key per cosign verifier. + +- Goals: + - Cosign verifier should be updated to trust a set of keys and perform verification using that trusted key set + - Cosign verifier MUST match the verification logic of Cosign CLI for a single Key + - Cosign verifier should be able to operate a loose ('All keys are equally trusted and ONLY one is requred to be used in validation') or strict (ALL keys are trusted and ALL keys must be used in validation) enforcement. + +### How does Cosign CLI verification work today? + +- Image manifest pushed to same repository as subject (tag schema: `sha256-abc123.sig`) +- Image manifest is updated every time new signature is added. Each layer is a different signature (possibly from different signing keys) +```json +# Sample Cosign OCI Image Manifest +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "size": 451, + "digest": "sha256:bbacdc3dc4b33c868c6c27192cb1d1bda619c1454abb57ebea806eac13250d3c" + }, + "layers": [ + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEQCIGoZazNt1/eEBc08AmcqXY+yNQQJY2ziZPiIopDjHnU5AiAUgeWBy90GgqDdnWpxd1b3lbqzgr2UV2ZyR59eDOyxVg==" + } + }, + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEUCIBVcGGZR6pvJ2SQ8KI0TZY02SLFSSOFJuJph2Qcm8cWZAiEArL+BF5BGRGxELVBeJtAUy8i7L1uuBeUT9fF6TrFYVLQ=" + } + }, + { + "mediaType": "application/vnd.dev.cosign.simplesigning.v1+json", + "size": 254, + "digest": "sha256:75a45fb043a8abfe52f256cb1a424d8808e886a3f40886228cdfa9b8d75c805d", + "annotations": { + "dev.cosignproject.cosign/signature": "MEQCIEIsK5yr1kGS1dgRmbudT55oghADdyAOfUwlnayPKAmmAiBTdZkhgHkZdw11tullz7HNOg5O1r5U/j4m2RE39b92Vg==" + } + } + ] +} +``` +- Image signature succeeds if AT LEAST one of the signatures verifies true with the corresponding key + - Validation occurs on each signature in the manifest in parallel against the SAME key + - Cosign only supports validation against a SINGLE key at a time + +### Trust Policy +Cosign verifier should support multiple trust policies based on the KeyManagementProviders (KMP) enabled and the desired verification scenario. Please refer to [this](#user-scenarios) section for common user scenarios. At a high level users must be able to have: + - multiple KMP `inline` key resources (each `inline` will have a single key) + - multiple keys defined in a single AKV KMP + - multiple KMP `inline` certificate resources (each `inline` may have a single cert or a cert chain) + - multiple certificates definine in a single AKV KMP + - mix of keys + certificates in a single AKV KMP + - a way to specify specific keys/certificates within a KMP by name and version + - a way to specify specific AKV keys/certificate by `Certificate ID` or `Key ID` URL. + - a way to scope a trust policy to a particular registry/repo/image + - multiple trust policies + - a way to specify enforcement for that specific trust policy + - skip: don't perform any verification for an image reference that matches this policy + - any: at least one of the keys/certificates trusted in the policy must result in a successful verification for overall cosign verification to be true + - all: ALL keys/certificates trusted in the policy must result in a successful verification for overall cosign verification to be true + - a way to define certificates to be used in a trust policy for Trusted Timestamp verification `tsaCertificates` + - a way to define options per trust policy for transparency log verification `tLogVerify` + - a way to define options per trust policy for keyless verification under a section called `keyless` + - certificate transparency log lookup `ctLogVerify` + - `rekorURL` for custom rekor instances + - `certificateIdentity`: certificate identity verifier should be configured to trust (OPTIONAL iff certificateIdentityExp is defined) + - `certificateIdentityExp`: wildcard expressions mapping to certificate identitie(s) verifier should be configured to trust (OPTIONAL iff certificateIdentity is defined. Overrides certificateIdentity if also specified) + - `certificateOIDCIssuer`: certificate OIDC Issuer verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuerExp is defined) + - `certificateOIDCIssuerExp`: wildcard expressions mapping to certificate OIDC Issuer(s) verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuer is defined. Overrides certificateOIDCIssuer if also specified) + - a way to scope the policy based on `artifactType` if we have nested verification `artifactTypeScopes` + - if the user defines `artifactTypeScopes`, then the `scopes` are applied first and then `artifactTypeScopes`. `artifactTypeScopes` are only enforced if the subject image manifest being verified contains a `subject` field (basically is a referrer). + - a way to specify local path keys using a `filePath` + +> NOTE: The sample below is an experience goal and not all configurations will be implemented initially + +Sample +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: wabbit-networks-images + scopes: + - wabbitnetworks.myregistry.io/carrots + - wabbitnetworks.myregistry.io/beets + artifactTypeScopes: # OPTIONAL: applied after scopes; only considered for nested verification scenarios. + - application/sarif+json + - application/spdx+json + keys: # list of keys that are trusted. Only the keys in KMS are considered + - provider: inline/inline-keys-1 # REQUIRED: if name is not provided, all keys are assumed to be trusted in KeyManagementProvider resource specified + - provider: inline/inline-keys-2 + - provider: azurekeyvault/akv-wabbit-networks + name: wabbit-networks-io # OPTIONAL: key name (inline will not support name since each inline has only one key/certificate) + version: 1234567890 # OPTIONAL: key version (inline will not support version) + - provider: wabbit-local-key # REQUIRED: can be any name if filePath is specified + filePath: /path/to/key.pub # absolute file path to local key path. Useful for CLI scenarios + certificates: # list of certificates that are trusted. Only the certificates in KMS are considered + - provider: inline/inline-certs-1 + tsaCertificates: + - provider: inline/inline-certs-tsa-1 + tLogVerify: true # transparency log verification (default to false) + keyless: + ctLogVerify: false # certificate transparency log verification boolean (default to true) + rekorURL: customrekor.io # rekor URL for transparency log verification (default to sigstore's public-good endpoint) + certificateIdentity: wabbit-identity # certificate identity verifier should be configured to trust (OPTIONAL iff certificateIdentityExp is defined) + certificateIdentityExp: # wildcard expressions mapping to certificate identitie(s) verifier should be configured to trust (OPTIONAL iff certificateIdentity is defined. Overrides certificateIdentity if also specified) + certificateOIDCIssuer: wabbit-issuer # certificate OIDC Issuer verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuerExp is defined) + certificateOIDCIssuerExp: # wild card expressions mapping to certificate OIDC Issuer(s) verifier should be configured to trust (OPTIONAL iff certificateOIDCIssuer is defined. Overrides certificateOIDCIssuer if also specified) + enforcement: any # skip (don't perform any verification and auto pass), any (at least one key/cert used in successfull verification is overall success), all (all keys/certs must be used for overall success) + - name: verification-bypass + scopes: + - wabbitnetworks.myregistry.io/golden + enforcement: skip +``` + +To start, only a single `trustPolicies` entry + `keys` with `provider`, `name`, `version`, `filePath`, and `scopes` will be supported. The behavior will match an equivalent `enforcement` of `any`. Future, work will add support for remainig configs. + +The `provider` field, where the name of the `KeyManagementProvider` (KMP) can be specified, is assumed to be referencing a `KMP` in the ratify installatio namespace (currently the Verifier is a cluster scoped resource). If the user would like to specify a `provider` in the cluster scope, the user must append `cluster/` to the front of the `KMP` name. If the user would like to specify a a `KMP` in a different namespace, the user must append the namespace to the front of the name reference using a `/` delimiter. For example, a KMP, `test-kmp` in the `test` namespace would be referenced `test/test-kmp`. +### User Scenarios + +#### 1 Signature, 2 trusted keys +Bob has a container image he built. His trusted pool contains 2 self-managed keys to sign the image using `cosign`. Only ONE of the keys is used for signing at a time. There is only ONE signature. Bob's organization utilizes and trusts both keys. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature using AT LEAST one key from a trusted pool. + +- Bob installs Ratify on the cluster +- Bob applies 2 new inline `KeyManagementProvider` CR onto the cluster, `inline-key-1` & `inline-key-2`. Each CR will have a key Bob's organization trusts. +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-1 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-2 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: multiple-trusted-keys + keys: + - provider: inline/inline-key-1 + - provider: inline/inline-key-2 +``` +- Bob attempts to deploy a pod from an image that has cosign signature signed with key in KeyManagementProvider `inline-key-1`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature signed with key in KeyManagementProvider `inline-key-2`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature signed with key NOT in `inline-key-1` or `inline-key-2`. Pod FAILS verification and blocked. + +#### 2 Signatures but only 1 is from a key that he trusts + +Bob has a container image that he imported from another registry. An existing cosign signature, signed by an entity he doesn't trust, is already associated with the image. After vetting the image, he utilizes 1 self-managed key to sign the image using `cosign`. Now the image has 2 cosign signatures. Bob only trusts his key. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature using only HIS key that he trusts. + +- Bob installs Ratify on the cluster +- Bob applies a new inline `KeyManagementProvider` CR onto the cluster, `inline-key` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: single-trusted-key + keys: + - provider: inline/inline-key +``` +- Bob attempts to deploy a pod from the vetted image that has 2 cosign signatures, one of which is signed with key in KeyManagementProvider `inline-key`. Pod is verified and created successfully. +- Bob attempts to deploy a pod from an image that has cosign signature(s) signed with a different key in `inline-key`. Pod FAILS verification and blocked. + +#### 2 Signatures 2 Keys: both keys must be used + +Bob has a container image that is produced from a build pipeline and tested via a testing pipeline. After each pipeline, the image is signed with cosign using a SEPARATE key. Now the image has 2 cosign signatures. Bob trusts both keys and requires BOTH keys to be used. Bob wants to ensure all container images entering his K8s cluster are verified to have a valid cosign signature from his build AND test pipeline. + +- Bob installs Ratify on the cluster +- Bob applies a new inline `KeyManagementProvider` CR onto the cluster, `inline-key-build` & `inline-key-test` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-build + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + type: inline + parameters: + contentType: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` +```yaml +apiVersion: config.ratify.deislabs.io/v1alpha1 +kind: KeyManagementProvider +metadata: + name: inline-key-test + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + provider: inline + parameters: + type: key + value: | + ---------- BEGIN RSA KEY ------------ + ****** + ---------- END RSA KEY ------------ + +``` + +- Bob applies the cosign verifier CR +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: build-test-verification + keys: + - provider: inline/inline-key-build + - provider: inline/inline-key-test + enforcement: all +``` +- Bob attempts to deploy a pod from the vetted image that has 1 cosign signature, which is signed with key in KeyManagementProvider `inline-key-build`. Pod FAILS verification and blocked. +- Bob attempts to deploy a pod from the vetted image that has 2 cosign signatures, which is signed with keys in KeyManagementProvider `inline-key-build` & `inline-key-test`. Pod passes verification and is created. + +> **NOTE**: Based on Cosign's own verification implementation, there does not seem to be a way to enforce that ALL signatures found are valid. This behavior is unchartered as far as we can tell and would require Ratify to determine if we want to support this directly. For that reason, the strict ALL signature scenario is not yet included. + +#### Extra Considerations +- How does Kyverno handle multiple Keys? + - Each key specified is a separate cosign verification operation + - User specifies enforcement type by specifying `count` of verification operations that must succeed. + - e.g 2 keys specified and 2 signatures on the image. each verification is against both signatures for a single key + - If count is 2, then 2 verifications must return true +- How will this work with OCI 1.1 enabled cosign? + - Cosign OCI 1.1 support is experimental + - Currently, an arbitrary signature from list of referrers is chosen to perform verifications on IF multiple cosign artifacts are found. + - This logic will be updated in the future once 1.1 is GA but for now it's left as a TODO in the Cosign code base. + +### How does `scopes` matching work? + +`scopes` are associated per `trustPolicy`. They function to apply on top of a validation image reference and match a SINGLE trust policy to use for verification. Ratify needs to decide how to implement scope matching based on the scenarios to support. Scopes could support regular expressions however they are not as user friendly. Ratify could also define its own domain/repository pattern syntax. Or, Ratify could support both side-by-side; however, this would require having behavior to rectify if both are used at once or used for different policies. The other concern is if multiple trust policies are defined each with scopes that can apply. For example, let's take Trust Policy A which has scope `*` (any image reference works). Then, let's define Trust Policy B with scope `ghcr.io`. Finally, define Trust Policy C with scope `ghcr.io/ratify-project/ratify`. If our image to validate has reference: `ghcr.io/ratify-project/ratify:v1.2.0`, which Trust Policy should apply? Ideally, we should match to he policy that is most specific first, so Trust Policy C would be selected. + +#### Scenarios to Support +1. Wildcard: `*` +2. Registry wide scope: `ghcr.io` +3. Wildcard registry domain scope: `*.azurecr.io` +4. Intermediate repository paths (repository path may reference a subpath but not an absolute path): `ghcr.io/ratify-project/*` + +#### How does notation do this? + +Notation's Trust Policy supports a generic `*` wildcard OR a an absolute repository path `ghcr.io/absolute/path`. There is no support for registry or wildcard registry domains. Furthermore, since only absolute repository paths are supported, there is no concern of a trust policy whose scope is a super set of another more tightly scoped trust policy. + +#### Regex based matching +- Every element in `scopes` is a string regular expression. How do we figure out which policy has the tighest scope if multiple trust policies have regex that match the entire image reference? +- Regex is the most flexible and can satisfy all other requirements. However, it does require user has general understanding of regex and how to format expressions. + +#### Custom pattern syntax +- Use pure sub string matching: give a string scope `s` and image reference `r`, a trust policy matches if all of `s` is a substring of `r`. This would support scope from domain all the way to tags. It could also work for partial path patterns like image reference whose path contains `/somepath/`. The tightest scope would be the longest string scope that still has a full matching substring. The trust policy selected would be the policy with a matching scope that is character-wise the longest. +- Sub-string matching does not allow for specifying positional matching behavior like "regex" does. Let's say our goal is to match image references which end with repository `/test`. In pure sub string matching if there's an intermediate repo named `test`, it will consider that a match which is NOT correct. + +#### Potential Solution +Introduce a hybrid solution that is based on substring matching: +- "tighest scope" is defined as a scope string that is longest AND has a matching substring in the image reference. +- `*` is a wildcard reserved for a trust policy that matches everything. It will have the loosest scope and will only be used iff there is NOT a tighter scope found. +- Partial path patterns like `/somepath/` are NOT supported or recommended +- wild card registry domain scope can be achieved by specifying the domain string as a scope string (example: use only ACRs --> `.azurecr.io`) +- Regex is NOT supported +- String scope will be used as an exact substring matching pattern. If there are multiple policies whose scopes match, then the policy with the longer submatch string will win. + +- Problems: + - Scope A: '.azurecr.io' and Scope B: 'test/happy/test2'. Which scope is "tighter"? Scope A scopes to a particular registry domain but Scope B works for any registry domain + +#### 3/27/24 CC discussion +- Overlapping scopes is a security risk since users can inadvertently create overlapping scopes with wildcards when adding a new trust policy +- We need to enforce non overlapping scopes on verifier creation and fail if there is +- We will support: + - `*` global scope will have least precedence and will not be considered an overlapping scope + - multiple policies cannot define a `*` global scope. This must be validated + - support all the way down to image level. + - wildcard `*` character will denote if there is a set of 0+ characters in that section of the pattern. Wildcard characters can only be used after the pattern defined. You can only specify 1 wild card character per scope. + - example: `ghcr.io/namspace/*` will consider a scope match if there is any image reference which starts with this pattern. + - Why can't we support multiple wild card characters? Short answer is that scope conflicts are guaranteed. + - wildcard character before and after a pattern can lead to unintendend conflicts. Say we have 2 scopes in 2 different policies, `*/namespace/*` and `*/reponame/*`. Let's take our image reference: ghcr.io/namespace/reponame:v1. Here, both policies could match. This is NOT allowed. + - Why can't we support wild card characters before or after the scope? + - Scopes that support wildcards before or after CANNOT be mixed. There's always a possibility for overlap between `ghcr.io/namespace/*` & `*/reponame:v1`. + - The main restriction is that determining scope conflicts is happening on verifier create at which point we need an ABSOLUTE way to determine if scopes are overlapping, since the image ref is not known + - How do we support registries with unique domains (ACR, ECR, Jfrog)? You want to allows for refs with `*.azurecr.io` which is technically same as `*.azurecr.io*`. + +### Implementation Details + +#### Handling multiple verifications at once +- Ratify will largely mirror how cosign performs multi-signature verification + - cannot invoke cosign's multi signature verification API since the API exposed also includes the registry content fetch which is independent in Ratify +- separate go routines per signature and collect all results PER KEY + +## Support RSA and ED25519 keys for verification + +Each key specified will encapsulate a single verification context for all signatures found in the artifact that the verifier is running for. + +Verification flow per key: +- Take raw key content (byte array) and [`LoadPublicKeyRaw()`](https://github.com/sigstore/cosign/blob/daf1eeb5ff2022d11466b8eccda25768b2dd2992/pkg/signature/keys.go#L91C1-L91C1) +- The `LoadPublicKeyRaw()` method in the cosign keys library will automatically detect which key type (based on the key header) and return the appropriate signature verifier +- Verifier is then passed to cosign's `VerifySignature()` method as a verification option + +## Support keyless verification with OIDC identities +Newer versions of Cosign require that the user defines which identities and OIDC issuers they trust for keyless verifications. Ratify's Cosign verifier should `certificate-identity`, `certificate-identity-exp`, `certificate-oidc-issuer`, `certificate-oidc-issuer-regexp` to match Cosign cli behavior. The identity and issuer specification is REQUIRED and will be enforced during config validation. + +### Implementation +- add 4 parameters to the `keyless` section of the trust policy config +- add new config validation logic to make sure at least the issuer & identity is defined or accompanying regexp +- pass through issuer & identity parameters as options to the cosign verifier `VerifyImage` function. + +## Dev Work Items (WIP) +- Introduce new `KeyManagementProvider` CRD to replace `CertificateStore` (~ 2 weeks) + - maintain old `CertificateStore` controllers and source code for backwards compat + - define new `KeyManagementProvider` CRD + controllers + - port certificate providers implementation to new `KMP` object + - refactor to factory paradigm + - refactor to define rigid config schema (currently, only a generic map of attributes passed) + - add enforcement so only `KeyManagementProvider` OR `CertificateStore` can be enabled at a time + - add id support for certificates in the global `Certificates` map +- Add Plugin support to `KeyManagementProvider` (~ 1 week) +- Add deprecation headers and warnings to `CertificateStore` CRD and code files (~ 0.5 weeks) +- Add Key support to `KeyManagementProvider` (~ 2-3 weeks) + - update API + - update Inline provider with `type` field + - update AKV provider for `key` fetching logic + - update controllers to add new key maps for global + namespaced keys + - update controllers to add new certs maps for namespaced certs +- ~~Update key/certificate store logic to provide cert + key access to external plugins (see [above](#implementation-details))~~ + - ~~Option 1 or 2 depending on what's decided~~ + - ~~NOTE: If we make cosign a built-in verifier, we will NOT have to do this.~~ +- Cosign Verifier: migrate to a built in verifier (~ 1-2 weeks) +- Cosign verifier multi key support (~ 3 weeks) + - add `trustPolicies` + - support multiple `keys` each with `provider`, and optional `name`, `version` + - add multi key verification logic including concurrent signature verification using routines + - preserve existing file path based key support for backwards compatability +- Add more robust Cosign Keyless support (~1.5 weeks) + - add new section to Trust Policy + - add support for Trust policy to use keyless vs key when both are specified? + - add new support for `ctLogVerify` configuration +- Add RSA and ED25519 key support (~ 0.5 week) + - auto detect key type based on parsing library + - pick cosign verifier according to format +- Add docs and walkthroughs (~ 1 week) + - redo cosign walk through + - update all walkthroughs and samples to use `KeyManagementProvider` + - add reference docs for `KeyManagementProvider` + - mark `CertificateStore` docs as deprecated +- New e2e tests for different scenarios (~ 1 week) +- Add `KeyManagementProvider` support to CLI (~ 2 weeks) + - Update `verify` command group to create `KeyManagementProvider` from config + - Update AKV provider for non Workload Identity auth + +## Future Considerations +- Support `scopes` for repo based `trustPolicies` (this includes wildcard regex support) +- Support `enforcement` with `skip`, `any`, and `all` +- Support attestations with cosign signature embedded +- Support certificate based verification of cosign signatures +- Verify image annotations +- Custom signature repository +- Transparency log and SCT verification options +- Keyless signature verification options + - Custom Rekor server + - Github workflows \ No newline at end of file diff --git a/docs/discussion/Negative test cases for Ratify.md b/docs/discussion/Negative test cases for Ratify.md index ee594c709..e7a63e622 100644 --- a/docs/discussion/Negative test cases for Ratify.md +++ b/docs/discussion/Negative test cases for Ratify.md @@ -104,13 +104,13 @@ time=2023-09-20T13:08:43.696147959Z level=error msg=Error: plugin init failure, ```stdout "verifierReports": [ { - "subject": "ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", + "subject": "ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", "referenceDigest": "sha256:57be2c1c3d9c23ef7c964bba05c7aa23b525732e9c9af9652654ccc3f4babb0e", "artifactType": "application/vnd.cncf.notary.signature", "verifierReports": [ { "isSuccess": false, - "message": "Original Error: (Original Error: (artifact \"ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b\" has no applicable trust policy. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy), Error: verify signature failure, Code: VERIFY_SIGNATURE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://github.com/notaryproject/notaryproject/tree/main/specs, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", + "message": "Original Error: (Original Error: (artifact \"ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b\" has no applicable trust policy. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy), Error: verify signature failure, Code: VERIFY_SIGNATURE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://github.com/notaryproject/notaryproject/tree/main/specs, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", "name": "verifier-notation", "type": "notation", "extensions": null @@ -129,7 +129,7 @@ The image verification fails. ```stdout "verifierReports": [ { - "subject": "ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", + "subject": "ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", "referenceDigest": "sha256:57be2c1c3d9c23ef7c964bba05c7aa23b525732e9c9af9652654ccc3f4babb0e", "artifactType": "application/vnd.cncf.notary.signature", "verifierReports": [ @@ -151,7 +151,7 @@ The image verification fails. The image verification fails: ```stdout -time=2023-09-22T13:50:40.440640495Z level=info msg=verify result for subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { +time=2023-09-22T13:50:40.440640495Z level=info msg=verify result for subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { "verifierReports": [ { "isSuccess": false, @@ -233,14 +233,14 @@ time=2023-09-21T16:49:39.660797685Z level=info msg=Reconciling Policy configpoli time=2023-09-21T16:49:39.660900987Z level=error msg=failed to get Policy: Policy.config.ratify.deislabs.io "configpolicy" not found time=2023-09-21T16:49:49.266265821Z level=info msg=received request POST /ratify/gatekeeper/v1/mutate time=2023-09-21T16:49:49.266514626Z level=info msg=start request POST /ratify/gatekeeper/v1/mutate component-type=server go.version=go1.20.8 trace-id=acb4f156-8132-4cfb-9df6-e73266c5c6c7 -time=2023-09-21T16:49:49.26667593Z level=info msg=mutating image ghcr.io/deislabs/ratify/notary-image:signed component-type=server go.version=go1.20.8 trace-id=acb4f156-8132-4cfb-9df6-e73266c5c6c7 +time=2023-09-21T16:49:49.26667593Z level=info msg=mutating image ghcr.io/ratify-project/ratify/notary-image:signed component-type=server go.version=go1.20.8 trace-id=acb4f156-8132-4cfb-9df6-e73266c5c6c7 time=2023-09-21T16:49:49.266880934Z level=warning msg=Error: cache not set, Code: CACHE_NOT_SET, Component Type: cache, Detail: failed to set auth cache for ghcr.io component-type=referrerStore go.version=go1.20.8 trace-id=acb4f156-8132-4cfb-9df6-e73266c5c6c7 ``` #### TC15 ```stdout -time=2023-09-21T16:24:03.422952769Z level=error msg=Reconciler error Policy=regopolicy controller=policy controllerGroup=config.ratify.deislabs.io controllerKind=Policy error=failed to create policy enforcer: failed to create policy provider: Original Error: (Original Error: (failed to create policy engine: failed to create policy query, err: failed to prepare rego query, err: 1 error occurred: policy.rego:13: rego_unsafe_var_error: var fals is unsafe), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://github.com/deislabs/ratify/blob/main/docs/reference/providers.md#policy-providers, Detail: failed to create OPA engine), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://github.com/deislabs/ratify/blob/main/docs/reference/providers.md#policy-providers, Detail: failed to create policy provider name=regopolicy namespace= reconcileID=71adeaf9-a6f0-4974-88cf-34bd2be47a99 +time=2023-09-21T16:24:03.422952769Z level=error msg=Reconciler error Policy=regopolicy controller=policy controllerGroup=config.ratify.deislabs.io controllerKind=Policy error=failed to create policy enforcer: failed to create policy provider: Original Error: (Original Error: (failed to create policy engine: failed to create policy query, err: failed to prepare rego query, err: 1 error occurred: policy.rego:13: rego_unsafe_var_error: var fals is unsafe), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://github.com/ratify-project/ratify/blob/main/docs/reference/providers.md#policy-providers, Detail: failed to create OPA engine), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://github.com/ratify-project/ratify/blob/main/docs/reference/providers.md#policy-providers, Detail: failed to create policy provider name=regopolicy namespace= reconcileID=71adeaf9-a6f0-4974-88cf-34bd2be47a99 ``` #### TC16 @@ -293,8 +293,8 @@ Ratify Logs: ```stdout time=2023-09-21T20:49:55.965807677Z level=info msg=received request POST /ratify/gatekeeper/v1/mutate time=2023-09-21T20:49:55.965917879Z level=info msg=start request POST /ratify/gatekeeper/v1/mutate component-type=server go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa -time=2023-09-21T20:49:55.966001082Z level=info msg=mutating image ghcr.io/deislabs/ratify/notary-image:signed component-type=server go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa -time=2023-09-21T20:49:57.916645653Z level=debug msg=subject descriptor cache miss for value: ghcr.io/deislabs/ratify/notary-image:signed component-type=referrerStore go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa +time=2023-09-21T20:49:55.966001082Z level=info msg=mutating image ghcr.io/ratify-project/ratify/notary-image:signed component-type=server go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa +time=2023-09-21T20:49:57.916645653Z level=debug msg=subject descriptor cache miss for value: ghcr.io/ratify-project/ratify/notary-image:signed component-type=referrerStore go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa time=2023-09-21T20:49:57.916739655Z level=debug msg=auth cache miss component-type=referrerStore go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa time=2023-09-21T20:49:57.917014862Z level=error msg=Error saving value to redis: error saving state: rpc error: code = DeadlineExceeded desc = context deadline exceeded component-type=cache go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa time=2023-09-21T20:49:57.917058964Z level=warning msg=Error: cache not set, Code: CACHE_NOT_SET, Component Type: cache, Detail: failed to set auth cache for ghcr.io component-type=referrerStore go.version=go1.20.8 trace-id=084fab3d-0f0d-4f32-9a9b-03db3c3df5fa @@ -306,19 +306,19 @@ Audit also begins to fail with timeout of 4.9 second. ```stdout time=2023-09-21T20:52:45.703791311Z level=info msg=received request POST /ratify/gatekeeper/v1/verify time=2023-09-21T20:52:45.703840611Z level=info msg=start request POST /ratify/gatekeeper/v1/verify component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:45.703937712Z level=info msg=verifying subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:50.604600679Z level=debug msg=subject descriptor cache miss for value: ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac +time=2023-09-21T20:52:45.703937712Z level=info msg=verifying subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac +time=2023-09-21T20:52:50.604600679Z level=debug msg=subject descriptor cache miss for value: ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac time=2023-09-21T20:52:50.604668279Z level=debug msg=auth cache miss component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac time=2023-09-21T20:52:50.60472728Z level=error msg=Error saving value to redis: error saving state: rpc error: code = DeadlineExceeded desc = context deadline exceeded component-type=cache go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac time=2023-09-21T20:52:50.60474728Z level=warning msg=Error: cache not set, Code: CACHE_NOT_SET, Component Type: cache, Detail: failed to set auth cache for ghcr.io component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:50.60483898Z level=warning msg=Original Error: (Original Error: (Head "https://ghcr.io/v2/deislabs/ratify/notary-image/manifests/sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b": context deadline exceeded), Error: repository operation failure, Code: REPOSITORY_OPERATION_FAILURE, Plugin Name: oras), Error: get subject descriptor failure, Code: GET_SUBJECT_DESCRIPTOR_FAILURE, Plugin Name: oras, Component Type: referrerStore, Detail: failed to resolve the subject descriptor component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:50.60485408Z level=debug msg=cache miss for subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac +time=2023-09-21T20:52:50.60483898Z level=warning msg=Original Error: (Original Error: (Head "https://ghcr.io/v2/ratify-project/ratify/notary-image/manifests/sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b": context deadline exceeded), Error: repository operation failure, Code: REPOSITORY_OPERATION_FAILURE, Plugin Name: oras), Error: get subject descriptor failure, Code: GET_SUBJECT_DESCRIPTOR_FAILURE, Plugin Name: oras, Component Type: referrerStore, Detail: failed to resolve the subject descriptor component-type=referrerStore go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac +time=2023-09-21T20:52:50.60485408Z level=debug msg=cache miss for subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac time=2023-09-21T20:52:50.60487278Z level=error msg=Error saving value to redis: error saving state: rpc error: code = DeadlineExceeded desc = context deadline exceeded component-type=cache go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:50.60488398Z level=warning msg=unable to insert cache entry for subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac -time=2023-09-21T20:52:50.60490018Z level=info msg=verify result for subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { +time=2023-09-21T20:52:50.60488398Z level=warning msg=unable to insert cache entry for subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b component-type=server go.version=go1.20.8 trace-id=384a4c5e-6654-47a8-bdaa-16823df527ac +time=2023-09-21T20:52:50.60490018Z level=info msg=verify result for subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { "verifierReports": [ { - "subject": "ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", + "subject": "ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", "isSuccess": false, "message": "verification failed: Error: referrer store failure, Code: REFERRER_STORE_FAILURE, Component Type: referrerStore, Detail: could not resolve descriptor for a subject from any stores" } @@ -362,7 +362,7 @@ time=2023-09-21T22:17:50.216846963Z level=error msg=Error saving value to redis: ```stdout level=error msg=Reconciler error CertificateStore=default/certstore-incorrect-cert controller=certificatestore controllerGroup=config.ratify.deislabs.io controllerKind=CertificateStore error=Error fetching certificates in store certstore-incorrect-cert with inline provider, error: Error: cert invalid, Code: CERT_INVALID, Component Type: certProvider name=certstore-incorrect-cert namespace=default reconcileID=6a444f61-fed0-4d0a-b6e1-08bedbe90712 time=2023-09-22T01:55:54.866028606Z level=warning msg=no certificate fetched for certStore certstore-incorrect-cert component-type=verifier go.version=go1.20.8 trace-id=4b6580f6-8b08-4c0b-a1ab-d4264298a6c9 -time=2023-09-22T01:55:54.866327608Z level=info msg=verify result for subject ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { +time=2023-09-22T01:55:54.866327608Z level=info msg=verify result for subject ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b: { "verifierReports": [ { "isSuccess": false, diff --git a/docs/discussion/Ratify Error Handling Scenarios.md b/docs/discussion/Ratify Error Handling Scenarios.md new file mode 100644 index 000000000..02b309f9e --- /dev/null +++ b/docs/discussion/Ratify Error Handling Scenarios.md @@ -0,0 +1,638 @@ +Ratify Error Handling Scenarios +=== +Author: Binbin Li + +Table of Contents +================= + +- [Ratify Error Handling Scenarios](#ratify-error-handling-scenarios) +- [Table of Contents](#table-of-contents) +- [Overview](#overview) +- [Objective](#objective) + - [Areas for Enhancement](#areas-for-enhancement) +- [Stages](#stages) + - [Diagnostic Review](#diagnostic-review) + - [Error Message Clarification](#error-message-clarification) + - [Nested Error Refactoring](#nested-error-refactoring) +- [User Scenarios](#user-scenarios) + - [Configuration](#configuration) + - [KMP](#kmp) + - [Invalid spec.type | Juncheng](#invalid-spectype--juncheng) + - [Current Error](#current-error) + - [Improvements](#improvements) + - [Invalid spec.parameters.contentType | Juncheng](#invalid-specparameterscontenttype--juncheng) + - [Current Error](#current-error-1) + - [Improvement](#improvement) + - [Invalid certificate provided for inline provider | Juncheng](#invalid-certificate-provided-for-inline-provider--juncheng) + - [Current Error](#current-error-2) + - [Improvement](#improvement-1) + - [Invalid key provided for inline provider | Juncheng](#invalid-key-provided-for-inline-provider--juncheng) + - [Current Error](#current-error-3) + - [Wrong vaultURI for AKV provider | Juncheng](#wrong-vaulturi-for-akv-provider--juncheng) + - [Current Error](#current-error-4) + - [Wrong certificate name for AKV provider | Juncheng](#wrong-certificate-name-for-akv-provider--juncheng) + - [Current Error](#current-error-5) + - [Invalid certificate version for AKV provider | Juncheng](#invalid-certificate-version-for-akv-provider--juncheng) + - [Current Error](#current-error-6) + - [Wrong tenantID or clientID for AKV provider | Juncheng](#wrong-tenantid-or-clientid-for-akv-provider--juncheng) + - [Current Error](#current-error-7) + - [Store](#store) + - [useHttp is enabled for public registry | Binbin](#usehttp-is-enabled-for-public-registry--binbin) + - [Current Error](#current-error-8) + - [Invalid authProvider(we support multiple providers, probably docker/Azure/AWS are main use case), e.g. wrong clientID for azureWorkloadIdentity](#invalid-authproviderwe-support-multiple-providers-probably-dockerazureaws-are-main-use-case-eg-wrong-clientid-for-azureworkloadidentity) + - [Verifier](#verifier) + - [Notation verifier references invalid certStore | Susan](#notation-verifier-references-invalid-certstore--susan) + - [Current Error](#current-error-9) + - [Improvement](#improvement-2) + - [Invalid artifactTypes in notation verifier | Susan](#invalid-artifacttypes-in-notation-verifier--susan) + - [Current Error](#current-error-10) + - [Improvement](#improvement-3) + - [invalid(non-existing) trustStores | Susan](#invalidnon-existing-truststores--susan) + - [Current Error](#current-error-11) + - [Improvement](#improvement-4) + - [Unsupported registryScope | Susan](#unsupported-registryscope--susan) + - [Current Error](#current-error-12) + - [Improvement](#improvement-5) + - [Invalid trustedIdentity | Susan](#invalid-trustedidentity--susan) + - [Current Error](#current-error-13) + - [Certificate has no matching trusted identity | Susan](#certificate-has-no-matching-trusted-identity--susan) + - [Current Error](#current-error-14) + - [Cosign verifier references invalid key provider | Binbin](#cosign-verifier-references-invalid-key-provider--binbin) + - [Current Error](#current-error-15) + - [Invalid artifactTypes in cosign verifier(same question) | Binbin](#invalid-artifacttypes-in-cosign-verifiersame-question--binbin) + - [Current Error](#current-error-16) + - [Improvement](#improvement-6) + - [Unsupported scope is provided in cosign verifier | Binbin](#unsupported-scope-is-provided-in-cosign-verifier--binbin) + - [Current Error](#current-error-17) + - [Conflict scopes from multiple policies in cosign verifier | Binbin](#conflict-scopes-from-multiple-policies-in-cosign-verifier--binbin) + - [Current Error](#current-error-18) + - [Invalid cert identity/cert OIDC issuer for keyless cosign verifier | Binbin](#invalid-cert-identitycert-oidc-issuer-for-keyless-cosign-verifier--binbin) + - [Current Error](#current-error-19) + - [Configured keyless cosign verifier to verify a keyed signature | Binbin](#configured-keyless-cosign-verifier-to-verify-a-keyed-signature--binbin) + - [Current Error](#current-error-20) + - [Cosign verifier configured a wrong public key | Binbin](#cosign-verifier-configured-a-wrong-public-key--binbin) + - [Current Error](#current-error-21) + - [Cosign verifier references non-existent kmp | Binbin](#cosign-verifier-references-non-existent-kmp--binbin) + - [Current Error](#current-error-22) + - [Policy](#policy) + - [`metadata.name` is changed other than ratify-policy | Binbin](#metadataname-is-changed-other-than-ratify-policy--binbin) + - [Current Error](#current-error-23) + - [Invalid spec.type | Binbin](#invalid-spectype--binbin) + - [Current Error](#current-error-24) + - [Invalid policy value except `all` and `or` in config policy | Binbin](#invalid-policy-value-except-all-and-or-in-config-policy--binbin) + - [Current Error](#current-error-25) + - [Invalid policy string is provided in rego policy | Binbin](#invalid-policy-string-is-provided-in-rego-policy--binbin) + - [Current Error](#current-error-26) + - [Access Control](#access-control) + - [ACR](#acr) + - [`AcrPull` role is not assigned | Juncheng](#acrpull-role-is-not-assigned--juncheng) + - [Current Error](#current-error-27) + - [Improvement](#improvement-7) + - [AKV](#akv) + - [`Key Vault Secrets User` role is not assigned for notation signature | Juncheng](#key-vault-secrets-user-role-is-not-assigned-for-notation-signature--juncheng) + - [Current Error](#current-error-28) + - [Improvement](#improvement-8) + - [`Key Vault Crypto User` role is not assigned for cosign signature | Juncheng](#key-vault-crypto-user-role-is-not-assigned-for-cosign-signature--juncheng) + - [Current Error](#current-error-29) + - [Improvement](#improvement-9) + - [Signature Verification](#signature-verification) + - [No matching verifier for notation signature | Akash](#no-matching-verifier-for-notation-signature--akash) + - [Test Error](#test-error) + - [Improvment](#improvment) + - [No matching verifier for cosign signature | Akash](#no-matching-verifier-for-cosign-signature--akash) + - [Test Error](#test-error-1) + - [Image is not signed | Akash](#image-is-not-signed--akash) + - [Current Error](#current-error-30) + - [Improvement](#improvement-10) + - [Image is signed with notation signature, but KMP provides a different certificate resulting to verification failure | Akash](#image-is-signed-with-notation-signature-but-kmp-provides-a-different-certificate-resulting-to-verification-failure--akash) + - [Current Error](#current-error-31) + - [Improvement](#improvement-11) + - [Image is signed with cosign signature, but KMP provides a different key resulting to verification failure | Akash](#image-is-signed-with-cosign-signature-but-kmp-provides-a-different-key-resulting-to-verification-failure--akash) + - [Current Error](#current-error-32) + - [Images are signed by unknown identities so that verification should fail. (Notary Project signatures) | Akash](#images-are-signed-by-unknown-identities-so-that-verification-should-fail-notary-project-signatures--akash) + - [Current Error](#current-error-33) + - [Improvement](#improvement-12) + - [Images are signed by unknown keys so that verification should fail (Cosign key-pair signatures) | Akash](#images-are-signed-by-unknown-keys-so-that-verification-should-fail-cosign-key-pair-signatures--akash) + - [Current Error](#current-error-34) + - [Revocation: Images are signed with revoked certificates](#revocation-images-are-signed-with-revoked-certificates) + - [Timestamp: Images are signed before certificate expired but cert is expired now (Time-stamp support is required and users specify TSA root certificate)](#timestamp-images-are-signed-before-certificate-expired-but-cert-is-expired-now-time-stamp-support-is-required-and-users-specify-tsa-root-certificate) + - [Images are signed with multiple signatures, but some signature is missing matching verifier | Binbin](#images-are-signed-with-multiple-signatures-but-some-signature-is-missing-matching-verifier--binbin) + - [Current Error](#current-error-35) + - [Images are signed with multiple signatures, and policy requires all signatures pass. | Binbin](#images-are-signed-with-multiple-signatures-and-policy-requires-all-signatures-pass--binbin) + - [Current Error](#current-error-36) + - [Images are signed with multiple signatures, and no matching verifiers configured | Binbin](#images-are-signed-with-multiple-signatures-and-no-matching-verifiers-configured--binbin) + - [Current Error](#current-error-37) + - [Images are signed with multiple signatures, and policy requires at least one signature pass | Binbin](#images-are-signed-with-multiple-signatures-and-policy-requires-at-least-one-signature-pass--binbin) + - [Current Error](#current-error-38) + - [Improvement](#improvement-13) + - [certificate is rotated, but Ratify didn't refresh it and previous cert is expired | Binbin](#certificate-is-rotated-but-ratify-didnt-refresh-it-and-previous-cert-is-expired--binbin) + - [Current Error](#current-error-39) + - [key is rotated, but Ratify didn't refresh it | Binbin](#key-is-rotated-but-ratify-didnt-refresh-it--binbin) + - [Current Error](#current-error-40) + - [Timeout during keyless cosign verification](#timeout-during-keyless-cosign-verification) + - [Timeout from requests to remote registry](#timeout-from-requests-to-remote-registry) + - [Timeout during cert revocation evaluation for notation signature](#timeout-during-cert-revocation-evaluation-for-notation-signature) + +# Overview + +Ratify has received a lot of valuable feedback on the error handling. Recognizing that the current errors are either too cryptic or excessively detailed, we are now committed to refining our error handling processes to improve overall user satisfaction. + +# Objective + +The target of this improvement is mainly focusing on the user expericence. So we'll start from current behavior in different user scenarios. Then we could compare the current behaivor and expected behavior to assess areas/directions we could improve. + +Our enhancement efforts are aimed at elevating the user experience. We will begin by examining the existing error responses across various user scenarios. By contrasting the present and ideal outcomes, we will identify and prioritize improvements. + +## Areas for Enhancement +We have pinpointed several key areas for potential enhancement: +1. Error message or detail for each specific scenario. Currently different issues could result to identical error, leading to user confusion. We aim to differentiate the error feedback more clearly. +2. How to wrap up the nested error. While nested error offer comprehensive details, they can overwhelm users and are often too lengthy for practical disolay. +3. How to handle the error originating from dependencies? + +# Stages +I plan to complete the error handling improvement in 3 stages. +## Diagnostic Review +Initially, we will conduct a thorough review of the most common error-generating scenarios. We’ll evaluate the clarity of the errors for both customers and Ratify developers. +## Error Message Clarification +We will ensure that each scenario yields a precise error message that pinpoints the root cause, including those stemming from dependencies. +## Nested Error Refactoring +We will restructure the nested error framework to maintain clarity and retain essential information for the users. + +# User Scenarios + +In general, there are 3 major scenarios could produce errors. +- Configration Error(excluding certificate) +- Access Controll Error +- Signature Verififcation Failures(including policy evaluation failure) + +Feel free to add more test cases under each section. + +## Configuration + +### KMP + +#### Invalid spec.type | Juncheng +##### Current Error +Error log: +``` +time=2024-07-15T23:43:55.170100941Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: key management provider factory with name notazurekeyvault not found name=keymanagementprovider-akv namespace= reconcileID=baff8d41-5889-4a39-85ff-efd9d02c8372 +``` + +##### Improvements +KMP does not support type of ... + +#### Invalid spec.parameters.contentType | Juncheng +##### Current Error +Error log 1: +``` +time=2024-07-17T12:54:37.380991457Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-inline controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Error: config invalid, Code: CONFIG_INVALID, Component Type: keyManagementProvider, Detail: content type notcertificate is not supported name=keymanagementprovider-inline namespace= reconcileID=4bbc444a-3326-45fb-b1fd-5fb09c6aa563 +``` +Error log 2: +``` +time=2024-07-17T14:14:29.313249456Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-inline controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Error: config invalid, Code: CONFIG_INVALID, Component Type: keyManagementProvider, Detail: contentType parameter is not set name=keymanagementprovider-inline namespace= reconcileID=f1340181-e790-45e2-9c46-30ddb85441d1 +``` + +##### Improvement + +#### Invalid certificate provided for inline provider | Juncheng +##### Current Error + +Error log: +``` +time=2024-07-17T12:57:02.094428327Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-inline controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Error: cert invalid, Code: CERT_INVALID, Component Type: certProvider, Detail: failed to decode pem block name=keymanagementprovider-inline namespace= reconcileID=bfe6ded8-21de-4a10-9a24-72bed094e682 +``` + +##### Improvement +Report error while parsing the public certificate + +#### Invalid key provided for inline provider | Juncheng +##### Current Error +Error log: +``` +time=2024-07-17T13:06:39.146768643Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-inline controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Original Error: (PEM decoding failed), Error: key invalid, Code: KEY_INVALID, Component Type: keyManagementProvider, Detail: error parsing public key name=keymanagementprovider-inline namespace= reconcileID=1a5d7e8f-5fc0-4b22-a371-fe871218ec1e +``` + +#### Wrong vaultURI for AKV provider | Juncheng +##### Current Error +Error log for valid URL: +``` +time=2024-07-16T07:56:11.14480023Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=Error fetching certificates in KMProvider keymanagementprovider-akv with azurekeyvault provider, error: failed to get secret objectName:wabbit-networks-io, objectVersion:, error: keyvault.BaseClient#GetSecret: Failure sending request: StatusCode=0 -- Original Error: Get "https://notroakv.vault.azure.net/secrets/wabbit-networks-io/?api-version=7.1": dial tcp: lookup notroakv.vault.azure.net on 10.0.0.10:53: no such host name=keymanagementprovider-akv namespace= reconcileID=ec30a479-d739-438f-a5a8-49e8394f4ae4`
Error log for invalid URL: `time=2024-07-16T08:05:33.552739275Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=Error fetching certificates in KMProvider keymanagementprovider-akv with azurekeyvault provider, error: failed to get secret objectName:wabbit-networks-io, objectVersion:, error: keyvault.BaseClient#GetSecret: Failure preparing request: StatusCode=0 -- Original Error: autorest: No scheme detected in URL .net/ name=keymanagementprovider-akv namespace= reconcileID=47f71684-2730-4545-979f-cc1598c6b88f +``` + +#### Wrong certificate name for AKV provider | Juncheng +##### Current Error +``` +time=2024-07-16T08:38:40.583971705Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=Error fetching certificates in KMProvider keymanagementprovider-akv with azurekeyvault provider, error: failed to get secret objectName:notwabbit-networks-io, objectVersion:, error: keyvault.BaseClient#GetSecret: Failure responding to request: StatusCode=404 -- Original Error: autorest/azure: Service returned an error. Status=404 Code="SecretNotFound" Message="A secret with (name/id) notwabbit-networks-io was not found in this key vault. If you recently deleted this secret you may be able to recover it using the correct recovery command. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125182" name=keymanagementprovider-akv namespace= reconcileID=358953da-30a6-419c-8628-5583637e17c3 +``` + +#### Invalid certificate version for AKV provider | Juncheng +##### Current Error +Error log: +``` +time=2024-07-15T23:52:33.66317963Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=Error fetching certificates in KMProvider keymanagementprovider-akv with azurekeyvault provider, error: failed to get secret objectName:wabbit-networks-io, objectVersion:invalidVersion, error: keyvault.BaseClient#GetSecret: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="BadParameter" Message="Method GET does not allow operation 'invalidVersion'" name=keymanagementprovider-akv namespace= reconcileID=481b14c6-0990-4413-9563-38d0ad0e180d +``` + +#### Wrong tenantID or clientID for AKV provider | Juncheng + +##### Current Error +Wrong tenantID Error log: +``` +time=2024-07-16T08:41:45.101552175Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Original Error: (Original Error: (failed to acquire token: failed to acquire AAD token: unable to resolve an endpoint: http call(https://login.microsoftonline.com//v2.0/.well-known/openid-configuration)(GET) error: reply status code was 400:{"error":"invalid_tenant","error_description":"AADSTS90002: Tenant '' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant. Trace ID: 5dd3d897-77ec-454b-907d-fca043f35600 Correlation ID: 2e2eacc0-4b67-43e0-8218-22de0a18a1eb Timestamp: 2024-07-16 08:41:45Z","error_codes":[90002],"timestamp":"2024-07-16 08:41:45Z","trace_id":"5dd3d897-77ec-454b-907d-fca043f35600","correlation_id":"2e2eacc0-4b67-43e0-8218-22de0a18a1eb","error_uri":"https://login.microsoftonline.com/error?code=90002"}), Error: auth denied, Code: AUTH_DENIED, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to get authorizer for keyvault client, Stack trace: goroutine 311 [running] ... Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to create keyvault client name=keymanagementprovider-akv namespace= reconcileID=8cb1dd6a-0e92-4f84-bd04-ccc32e98906d`
Wrong clientID Error log:`time=2024-07-16T08:50:37.655092495Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Original Error: (Original Error: (failed to acquire token: failed to acquire AAD token: FromAssertion(): http call(https://login.microsoftonline.com//oauth2/v2.0/token)(POST) error: reply status code was 400: {"error":"unauthorized_client","error_description":"AADSTS700016: Application with identifier '' was not found in the directory 'Microsoft'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant. Trace ID: bd8356e9-e3a1-4604-bee8-f11e6a4f4200 Correlation ID: 5daee285-5a95-4f61-85b4-2f5332996b62 Timestamp: 2024-07-16 08:50:22Z","error_codes":[700016],"timestamp":"2024-07-16 08:50:22Z","trace_id":"bd8356e9-e3a1-4604-bee8-f11e6a4f4200","correlation_id":"5daee285-5a95-4f61-85b4-2f5332996b62","error_uri":"https://login.microsoftonline.com/error?code=700016"}), Error: auth denied, Code: AUTH_DENIED, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to get authorizer for keyvault client, Stack trace: goroutine 311 [running] ..., Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to create keyvault client name=keymanagementprovider-akv namespace= reconcileID=0676c027-9dda-44c5-8769-c49f18f8d5b5 +``` + +### Store +#### useHttp is enabled for public registry | Binbin +##### Current Error +Mutation failure: +``` +Error from server: admission webhook "mutation.gatekeeper.sh" denied the request: failed to resolve external data placeholders: failed to validate external data response from provider ratify-mutation-provider: non-empty system error: operation failed with error operation timed out after duration 1.95s` and no error log. Validation failure: `Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] System error calling external data provider: operation failed with error operation timed out after duration 4.9s` error log: `"message": "verification failed: Original Error: (Get \"https://azure.microsoft.com/services/container-registry/\": context deadline exceeded), Error: list referrers failure, Code: LIST_REFERRERS_FAILURE, Plugin Name: oras, Component Type: referrerStore" +``` + +#### Invalid authProvider(we support multiple providers, probably docker/Azure/AWS are main use case), e.g. wrong clientID for azureWorkloadIdentity + +### Verifier +#### Notation verifier references invalid certStore | Susan +##### Current Error +``` +"verifierReports": [ + { + "isSuccess": false, + "name": "verifier-notation", + "type": "notation", + "message": "Original Error: (Original Error: (unable to fetch certificates for namedStore: certs), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://ratify.dev/docs/troubleshoot/verifier/notation, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", + "artifactType": "application/vnd.cncf.notary.signature" + } + ] +``` + +##### Improvement +Keep: +This error is useful unable to fetch certificates for namedStore: certs +ToImprove: + +1. Message maybe too verbose ( will get fixed if we remove nesting) +2. Message ideally should include action item like please validate your cert store name +3. Would a more specific error code help? + +#### Invalid artifactTypes in notation verifier | Susan + +##### Current Error +``` +"verifierReports": [ + { + "subject": "ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b", + "isSuccess": false, + "message": "verification failed: Error: no verifier report, Code: NO_VERIFIER_REPORT, Component Type: executor, Description: No verifier report was generated. This might be due to various factors, such as lack of artifacts attached to the image, a misconfiguration in the Referrer Store preventing access to the registry, or the absence of appropriate verifiers corresponding to the referenced image artifacts." + } + ] +``` + +##### Improvement +We should link to a TSG with potential root cause and fix. + +#### invalid(non-existing) trustStores | Susan + +##### Current Error +``` + "verifierReports": [ + { + "isSuccess": false, + "name": "verifier-notation", + "type": "notation", + "message": "Original Error: (Original Error: (valid certificates must be provided, only CA certificates or self-signed signing certificates are supported), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://ratify.dev/docs/troubleshoot/verifier/notation, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", + "artifactType": "application/vnd.cncf.notary.signature" + } + ] +``` + +##### Improvement +Wondering if we could have trust store missing error. + +#### Unsupported registryScope | Susan +##### Current Error +``` +"verifierReports": [ + { + "isSuccess": false, + "name": "verifier-notation", + "type": "notation", + "message": "Original Error: (Original Error: (artifact \"ghcr.io/deislabs/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b\" has no applicable trust policy. Trust policy applicability for a given artifact is determined by registryScopes. To create a trust policy, see: https://notaryproject.dev/docs/quickstart/#create-a-trust-policy), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://ratify.dev/docs/troubleshoot/verifier/notation, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", + "artifactType": "application/vnd.cncf.notary.signature" + } + ] +``` + +##### Improvement +The error has `no applicable trust policy.` is helpful. + +#### Invalid trustedIdentity | Susan + +##### Current Error +``` + time=2024-07-17T01:47:19.106388964Z level=error msg=Original Error: (trust policy statement "default" has trusted identity "ratifytest, test" missing separator), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: verifier-notation, Component Type: verifierunable to create verifier from verifier crd +``` + +#### Certificate has no matching trusted identity | Susan +##### Current Error + +``` + "verifierReports": [ + { + "isSuccess": false, + "name": "verifier-notation", + "type": "notation", + "message": "Original Error: (Original Error: (error while parsing the certificate subject from the digital signature. error : \"distinguished name (DN) \\\"CN=ratify.default\\\" has no mandatory RDN attribute for \\\"C\\\", it must contain 'C', 'ST', and 'O' RDN attributes at a minimum\"), Error: verify plugin failure, Code: VERIFY_PLUGIN_FAILURE, Plugin Name: verifier-notation, Component Type: verifier, Documentation: https://ratify.dev/docs/troubleshoot/verifier/notation, Detail: failed to verify signature of digest), Error: verify reference failure, Code: VERIFY_REFERENCE_FAILURE, Plugin Name: verifier-notation, Component Type: verifier", + "artifactType": "application/vnd.cncf.notary.signature" + } + ] +``` + +#### Cosign verifier references invalid key provider | Binbin + +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: huishwabbit1.azurecr.io/test8may24@sha256:c780036bc8a6f577910bf01151013aaa18e255057a1653c76d8f3572aa3f6ff6 +``` +Error log: +``` +cosign verification failed: Error: config invalid, Code: CONFIG_INVALID, Plugin Name: verifier-cosign, Component Type: verifier, Detail: trust policy [default] failed to access key management provider ratify-cosign-inline-key-1, err: failed to access non-existent key management provider: ratify-cosign-inline-key-1 +``` + +#### Invalid artifactTypes in cosign verifier(same question) | Binbin + +##### Current Error +Terminal reponse: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: huishwabbit1.azurecr.io/test8may24@sha256:c780036bc8a6f577910bf01151013aaa18e255057a1653c76d8f3572aa3f6ff6 +``` +Error log: +``` +verification failed: Error: no verifier report, Code: NO_VERIFIER_REPORT, Component Type: executor, Description: No verifier report was generated. This might be due to various factors, such as lack of artifacts attached to the image, a misconfiguration in the Referrer Store preventing access to the registry, or the absence of appropriate verifiers corresponding to the referenced image artifacts. +``` + +##### Improvement +Consider validating the artifactType when applying CR + +#### Unsupported scope is provided in cosign verifier | Binbin +##### Current Error +Failed to create cosign verifier. Error log: ![image](../img/ratify-errors/SJTOOXhDA.png) + + +#### Conflict scopes from multiple policies in cosign verifier | Binbin + +##### Current Error +Failed to create cosign verifier. Error log: ![image](../img/ratify-errors/H1KYhmnDA.png) + + +#### Invalid cert identity/cert OIDC issuer for keyless cosign verifier | Binbin + +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testcosign@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/r1vemTRwC.png) + +#### Configured keyless cosign verifier to verify a keyed signature | Binbin + +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/rkrEZvfd0.png) + +#### Cosign verifier configured a wrong public key | Binbin + +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/HJHWNwfuC.png) + +#### Cosign verifier references non-existent kmp | Binbin + +##### Current Error +Error log: +``` +cosign verification failed: Error: config invalid, Code: CONFIG_INVALID, Plugin Name: verifier-cosign, Component Type: verifier, Detail: trust policy [default] failed to access key management provider ratify-cosign-inline-key-1, err: failed to access non-existent key management provider: ratify-cosign-inline-key-1 +``` + +### Policy + +#### `metadata.name` is changed other than ratify-policy | Binbin + +##### Current Error +Error log while applying the CR: +``` +metadata.name must be ratify-policy, got ratify-policy2 +``` +It will keep using the previous policy. + +#### Invalid spec.type | Binbin + +##### Current Error +Error log after applying CR: +``` +time=2024-07-18T06:58:03.394537164Z level=error msg=unable to create policy from policy crd: failed to create policy enforcer: failed to create policy provider: Error: policy provider not found, Code: POLICY_PROVIDER_NOT_FOUND, Plugin Name: configpolicy2, Component Type: policyProvider, Documentation: https://ratify.dev/docs/reference/crds/policies, Detail: policy type: configpolicy2 is not registered policy provider +``` +And it will keep using the previous policy + +#### Invalid policy value except `all` and `or` in config policy | Binbin + +##### Current Error + +No error log upon applying CR. +Terminal response on deployment: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage2@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +No error log. + +#### Invalid policy string is provided in rego policy | Binbin + +##### Current Error +Error log during applying CR: +``` +time=2024-07-18T08:11:13.480990932Z level=error msg=unable to create policy from policy crd: failed to create policy enforcer: failed to create policy provider: Original Error: (Original Error: (failed to create policy engine: failed to create policy query, err: failed to prepare rego query, err: 1 error occurred: policy.rego:4: rego_parse_error: var cannot be used for rule name), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://ratify.dev/docs/reference/providers, Detail: failed to create OPA engine), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: regopolicy, Component Type: policyProvider, Documentation: https://ratify.dev/docs/reference/providers, Detail: failed to create policy provider +``` + +## Access Control +### ACR +#### `AcrPull` role is not assigned | Juncheng +##### Current Error +Error log: +``` +time=2024-07-17T16:28:16.939576441Z level=warning msg=Original Error: (Original Error: (HEAD "https://roacr.azurecr.io/v2/net-monitor/manifests/v2": GET "https://roacr.azurecr.io/oauth2/token?scope=repository%3Anet-monitor%3Apull&service=roacr.azurecr.io": response status code 401: unauthorized: authentication required, visit https://aka.ms/acr/authorization for more information.), Error: repository operation failure, Code: REPOSITORY_OPERATION_FAILURE, Plugin Name: oras), Error: get subject descriptor failure, Code: GET_SUBJECT_DESCRIPTOR_FAILURE, Plugin Name: oras, Component Type: referrerStore, Detail: failed to resolve the subject descriptor component-type=referrerStore go.version=go1.21.10 namespace= trace-id=34b27888-5402-443e-9836-77124c840561 +``` +![image](../img/ratify-errors/SkUQfOr_0.png) + +##### Improvement + +1. Users just need to know 401 error. +2. VerifierReports.Message should contain info including 401, repository operation failure and getSubject(optional) +3. probably different logs for each nested error + +### AKV +#### `Key Vault Secrets User` role is not assigned for notation signature | Juncheng + +##### Current Error +Error logs: +``` +time=2024-07-17T17:40:22.299403863Z level=error msg=authenticity validation failed. Failure reason: unable to fetch certificates for namedStore: certs component-type=verifier go.version=go1.21.10 namespace= trace-id=af227387-267c-4abc-b536-1ad423918726 +``` +![image](../img/ratify-errors/rym_MFHOC.png) + +##### Improvement +Reverse error msg and failure reason. Failure during akv fetching. Potential reason: no certs/invalid auth. List all possible reason. **Important: Add error field in KMP to return to verifier.** + +#### `Key Vault Crypto User` role is not assigned for cosign signature | Juncheng + +##### Current Error +Error log: +``` +time=2024-07-17T19:01:24.928790514Z level=error msg=Reconciler error KeyManagementProvider=keymanagementprovider-akv controller=keymanagementprovider controllerGroup=config.ratify.deislabs.io controllerKind=KeyManagementProvider error=failed to create key management provider provider: Original Error: (Original Error: (failed to acquire token: failed to acquire AAD token: FromAssertion(): http call(https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/v2.0/token)(POST) error: reply status code was 401:{"error":"invalid_client","error_description":"AADSTS70025: Client application has no configured federated identity credentials. Trace ID: 479893ff-f447-44d8-abbe-da02e9897300 Correlation ID: fa4c8a02-7d90-4858-9951-e485843c66e1 Timestamp: 2024-07-17 19:01:24Z","error_codes":[70025],"timestamp":"2024-07-17 19:01:24Z","trace_id":"479893ff-f447-44d8-abbe-da02e9897300","correlation_id":"fa4c8a02-7d90-4858-9951-e485843c66e1"}), Error: auth denied, Code: AUTH_DENIED, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to get authorizer for keyvault client, Stack trace:(omit_stack_trace)), Error: plugin init failure, Code: PLUGIN_INIT_FAILURE, Plugin Name: azurekeyvault, Component Type: keyManagementProvider, Documentation: https://learn.microsoft.com/en-us/azure/key-vault/general/overview, Detail: failed to create keyvault client name=keymanagementprovider-akv namespace= reconcileID=28f52896-543a-48de-8770-9820d9d6b063 +``` + +##### Improvement +KMP state is invalid/KMP cached certs/keys + +## Signature Verification + +#### No matching verifier for notation signature | Akash +##### Test Error +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b +``` +![image](../img/ratify-errors/Sy6OtPVuC.png) +Rego policy will not show any message simply shows:`{"verifierReports": []}` + +##### Improvment +1. Provide artifactType. +2. Message: failed to verify the artifact of type ... +3. Reason: no matching verifier configured for ... + +#### No matching verifier for cosign signature | Akash +##### Test Error +Same behavior as previous test case. +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: +``` +Rego policy will not show any message simply shows:`{"verifierReports": []}` + +#### Image is not signed | Akash + +##### Current Error +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdcec6 +``` +![image](../img/ratify-errors/SyfhDP4dC.png) +Rego policy will not show any message simply shows:`{"verifierReports": []}` + +##### Improvement +Reason: artifact is not signed or signature is found + +#### Image is signed with notation signature, but KMP provides a different certificate resulting to verification failure | Akash + +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b +``` +![image](../img/ratify-errors/rys__uEOA.png) + +##### Improvement +Probably need more accurate or descriptive msg from notation-go + +#### Image is signed with cosign signature, but KMP provides a different key resulting to verification failure | Akash +##### Current Error +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: generaltest.azurecr.io/cosign/hello-world@sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7 +``` +![image](../img/ratify-errors/B12iZKE_C.png) + +#### Images are signed by unknown identities so that verification should fail. (Notary Project signatures) | Akash + +##### Current Error +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: ghcr.io/ratify-project/ratify/notary-image@sha256:8e3d01113285a0e4aa574da8eb9c0f112a1eb979d72f73399d7175ba3cdb1c1b +``` + +![image](../img/ratify-errors/rJvv5_N_0.png) + +##### Improvement +same as prev case that uses different cert + +#### Images are signed by unknown keys so that verification should fail (Cosign key-pair signatures) | Akash +##### Current Error + +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: generaltest.azurecr.io/cosign/hello-world@sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7 +``` +![image](../img/ratify-errors/B12iZKE_C.png) + +#### Revocation: Images are signed with revoked certificates +#### Timestamp: Images are signed before certificate expired but cert is expired now (Time-stamp support is required and users specify TSA root certificate) +#### Images are signed with multiple signatures, but some signature is missing matching verifier | Binbin +##### Current Error +It passes using config-policy, but can fail using rego-policy. +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +![image](../img/ratify-errors/Byrwl2zuC.png) + +#### Images are signed with multiple signatures, and policy requires all signatures pass. | Binbin +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/SkubOnMuA.png) + +#### Images are signed with multiple signatures, and no matching verifiers configured | Binbin +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Error validating one or more images: ["libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556", "Error: config invalid, Code: CONFIG_INVALID, Component Type: verifier, Detail: verifiers config should have at least one verifier"] +``` + +#### Images are signed with multiple signatures, and policy requires at least one signature pass | Binbin +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/rJtithG_0.png) + +##### Improvement +print notation signature digest + +#### certificate is rotated, but Ratify didn't refresh it and previous cert is expired | Binbin +##### Current Error +Terminal Response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error Log: ![image](../img/ratify-errors/SJqHDNL_A.png) + +#### key is rotated, but Ratify didn't refresh it | Binbin +##### Current Error +Terminal response: +``` +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ratify-constraint] Subject failed verification: libinbinacr.azurecr.io/testimage@sha256:f2502800f0663995420b13214a0d20eae1ec9a3c072f99c462cef0132a684556 +``` +Error log: ![image](../img/ratify-errors/ryMGu8L_C.png) + +#### Timeout during keyless cosign verification +#### Timeout from requests to remote registry +#### Timeout during cert revocation evaluation for notation signature +Reference: https://github.com/notaryproject/specifications/blob/main/specs/trust-store-trust-policy.md#certificate-revocation-evaluation \ No newline at end of file diff --git a/docs/img/ratify-errors/B12iZKE_C.png b/docs/img/ratify-errors/B12iZKE_C.png new file mode 100644 index 000000000..cd8b317ef Binary files /dev/null and b/docs/img/ratify-errors/B12iZKE_C.png differ diff --git a/docs/img/ratify-errors/Byrwl2zuC.png b/docs/img/ratify-errors/Byrwl2zuC.png new file mode 100644 index 000000000..972cb5201 Binary files /dev/null and b/docs/img/ratify-errors/Byrwl2zuC.png differ diff --git a/docs/img/ratify-errors/H1KYhmnDA.png b/docs/img/ratify-errors/H1KYhmnDA.png new file mode 100644 index 000000000..78eb47cae Binary files /dev/null and b/docs/img/ratify-errors/H1KYhmnDA.png differ diff --git a/docs/img/ratify-errors/HJHWNwfuC.png b/docs/img/ratify-errors/HJHWNwfuC.png new file mode 100644 index 000000000..1d1ba7c9f Binary files /dev/null and b/docs/img/ratify-errors/HJHWNwfuC.png differ diff --git a/docs/img/ratify-errors/SJTOOXhDA.png b/docs/img/ratify-errors/SJTOOXhDA.png new file mode 100644 index 000000000..196746985 Binary files /dev/null and b/docs/img/ratify-errors/SJTOOXhDA.png differ diff --git a/docs/img/ratify-errors/SJqHDNL_A.png b/docs/img/ratify-errors/SJqHDNL_A.png new file mode 100644 index 000000000..c61490fd1 Binary files /dev/null and b/docs/img/ratify-errors/SJqHDNL_A.png differ diff --git a/docs/img/ratify-errors/SkUQfOr_0.png b/docs/img/ratify-errors/SkUQfOr_0.png new file mode 100644 index 000000000..0a621e6b2 Binary files /dev/null and b/docs/img/ratify-errors/SkUQfOr_0.png differ diff --git a/docs/img/ratify-errors/SkubOnMuA.png b/docs/img/ratify-errors/SkubOnMuA.png new file mode 100644 index 000000000..3ba9507b0 Binary files /dev/null and b/docs/img/ratify-errors/SkubOnMuA.png differ diff --git a/docs/img/ratify-errors/Sy6OtPVuC.png b/docs/img/ratify-errors/Sy6OtPVuC.png new file mode 100644 index 000000000..9a870767c Binary files /dev/null and b/docs/img/ratify-errors/Sy6OtPVuC.png differ diff --git a/docs/img/ratify-errors/SyfhDP4dC.png b/docs/img/ratify-errors/SyfhDP4dC.png new file mode 100644 index 000000000..c0ab26800 Binary files /dev/null and b/docs/img/ratify-errors/SyfhDP4dC.png differ diff --git a/docs/img/ratify-errors/r1vemTRwC.png b/docs/img/ratify-errors/r1vemTRwC.png new file mode 100644 index 000000000..88036620e Binary files /dev/null and b/docs/img/ratify-errors/r1vemTRwC.png differ diff --git a/docs/img/ratify-errors/rJtithG_0.png b/docs/img/ratify-errors/rJtithG_0.png new file mode 100644 index 000000000..29bc56ce9 Binary files /dev/null and b/docs/img/ratify-errors/rJtithG_0.png differ diff --git a/docs/img/ratify-errors/rJvv5_N_0.png b/docs/img/ratify-errors/rJvv5_N_0.png new file mode 100644 index 000000000..312dd41fc Binary files /dev/null and b/docs/img/ratify-errors/rJvv5_N_0.png differ diff --git a/docs/img/ratify-errors/rkrEZvfd0.png b/docs/img/ratify-errors/rkrEZvfd0.png new file mode 100644 index 000000000..5972e63f5 Binary files /dev/null and b/docs/img/ratify-errors/rkrEZvfd0.png differ diff --git a/docs/img/ratify-errors/ryMGu8L_C.png b/docs/img/ratify-errors/ryMGu8L_C.png new file mode 100644 index 000000000..fc968e65f Binary files /dev/null and b/docs/img/ratify-errors/ryMGu8L_C.png differ diff --git a/docs/img/ratify-errors/rym_MFHOC.png b/docs/img/ratify-errors/rym_MFHOC.png new file mode 100644 index 000000000..2cb1fd2ed Binary files /dev/null and b/docs/img/ratify-errors/rym_MFHOC.png differ diff --git a/docs/img/ratify-errors/rys__uEOA.png b/docs/img/ratify-errors/rys__uEOA.png new file mode 100644 index 000000000..9f4ae33b0 Binary files /dev/null and b/docs/img/ratify-errors/rys__uEOA.png differ diff --git a/docs/proposals/Automated-Certificate-and-Key-Updates.md b/docs/proposals/Automated-Certificate-and-Key-Updates.md new file mode 100644 index 000000000..17ce933f7 --- /dev/null +++ b/docs/proposals/Automated-Certificate-and-Key-Updates.md @@ -0,0 +1,237 @@ +# Automated Certificate and Key Updates + +## Problem/Motivation + +When ensuring the integrity and authenticity of images, you can sign images using Notation with code-signing certificates in Azure Key Vault (AKV) or Cosign with key pairs stored in AKV. To verify these signed images with Ratify in K8s, users typically configure the corresponding certificates or keys using a custom resource called `KeyManagementProvider` (referred to as `KMP` for short). This configuration allows Ratify to retrieve the correct certificates or keys for verification. + +In most cases, certificates and keys are securely managed within a Key Management System (KMS), such as AWS KMS, Azure Key Vault (AKV), HashiCorp Vault, or GCP KMS. As a proactive security best practice, both certificates and keys are regularly rotated within a KMS. This rotation can occur automatically or manually (On-demand) - For example, when certificates or keys are compromised. For most of KMS, after rotation, the cryptographic material associated with the key or certificate is updated while maintaining the same references (i.e., the key name or certificate name). This ensures compatibility with existing applications and services that rely on that certificate or key. For instance, if a certificate is rotated in AKV, the certificate name remains unchanged, but a new version of the certificate is created. The cryptographic key identifier in the new version differs from the previous version. Applications can continue referencing the certificate using the same name without breaking compatibility. + +In the current Ratify version (v1.2.0 or earlier), AKV is the only supported KMS provider. Consider AKV key configuration as an example: users can specify either the key name or a specific version of the key. If only the key name is configured, Ratify fetches the latest version of the configured key and caches it. When a specific version is configured, Ratify retrieves that precise version and caches it. However, unless users update the existing `KMP` resource to use a different key or key version, or delete the existing `KMP` and reapply it, the cached key versions remain unchanged. Consequently, when key rotation occurs or the key’s operational status changes, Ratify continues to rely on the cached version. This can lead to several issues: + +- **Signature Verification Failures** + - When images are signed with the latest version of the key, signature verification fails because the cached key is not updated. + - Images signed with previous versions may persist for an extended period, but Ratify only caches the latest version. Consequently, signature verification may fail for images signed using older versions. +- **Disabled Keys Should Not Be Used** + - If a key is disabled (due to compromise, for example), Ratify can still use the cached version for image verification. This poses significant security risks, as disabled keys should not be employed in any cryptographic operations. + +To address these challenges, users need to manually update or reapply the `KMP` resource to trigger Ratify to retrieve the latest/specific versions of keys or certificates. This step can be cumbersome, especially considering automated key rotation. Additionally, users must keep the previous versions of keys configured for some time, as not all images are signed with the latest/specific versions. In practice, keeping up with these changes manually can be challenging, and misconfigurations may lead to image verification failures and unnecessary service downtime. + +Certificate rotation in AKV follows a similar process to key rotation, as described earlier, but its impact is less significant. According to the Notary Project specification, only root CA certificates are required for trust stores. The root CA certificate typically has a long validity and is unlikely to change during certificate rotation unless users switch to a different CA for issuing code-signing certificates. As a result, the cached root CA certificate can be used for quite a long period normally. + +To address these problems, this document begins by comparing various key management providers. It then outlines scenarios, proposal and user experiences. + +## Key Management Providers comparison + +This section compares various KMPs that can be integrated with Ratify for signature verification scenarios: +- AWS KMS: https://docs.aws.amazon.com/kms/ +- Azure Key Vault: https://learn.microsoft.com/en-us/azure/key-vault/ (already supported by Ratify) +- HCP Vault: https://developer.hashicorp.com/vault/docs/what-is-vault +- GCP KMS: https://cloud.google.com/kms/docs/key-management-service +- AWS Signer: https://docs.aws.amazon.com/signer/latest/developerguide/Welcome.html +- Azure Trusted Signing: https://learn.microsoft.com/en-us/azure/trusted-signing/overview + +Here’s a comparison of various KMSs used for creating and managing keys/certificates for signing and verification: + +| | **AWS KMS** | **Azure Key Vault** | **HCP Vault** | **GCP KMS** | +|----------------------------|-------------|---------------------|---------------|-------------| +| **Key Operations** | Enable, Disable, Delete, Versioning, Rotate | Enable, Disable, Delete, Versioning, Rotate | Enable, Disable, Delete, Versioning, Rotate | Enable, Disable, Delete, Versioning, Rotate | +| **Key Rotation** | Manual for asymmetric keys | Automatic/Manual | Automatic/Manual | Manual for asymmetric keys | +| **Rotation Result** | New version created, old version remains valid | New version created, old version remains valid | New version created, old version remains valid | New version created, old version remains valid | +| **Events** | CloudWatch Events for key state changes | Azure Event Grid for key state changes | Custom implementation (e.g., using Vault's audit logs) | Cloud Pub/Sub for key state changes | +| **Certificate management** | Not supported | Supported | Supported | Not supported | + +Additionally, here’s a comparison of fully managed code-signing services related to Ratify signature verification scenarios. The difference from KMS in previous table is that fully managed code-signing services do not require users to create keys/certificates, instead they managed the key/certificates for users and normally these key/certificates have short validity. For fully managed code-signing, normally users set up a profile for signing purpose. The profile identifier is similar to the key identifier that used by KMS in previous table. Users can revoke the profile, so that it cannot be used for signing an verification. + +| | **AWS Signer** | **Azure Trusted Signing** | +|------------------------|--------------------------------------------------|---------------------------------------| +| **Configuration** | Signing profile | Certificate profile | +| **Revocation** | Signature revocation, signing profile revocation | Certificate profile revocation | +| **Signature Type** | Notary Project signature | Notary Project signature | + +Lastly, here’s `KMP` resource availability in Ratify v1.2.0: + +| | **AWS KMS** | **Azure KV** | **HCP Vault** | **GCP KMS** | **AWS Signer** | **Azure Trusted Signing** | +|---------------------------------------|-------------|---------------------|---------------|-------------|------------------|---------------------------| +| Notary Project signature verification | N/A | `azurekeyvault KMP` | N/A | N/A | N/A | N/A | +| Cosign signature verification | N/A | `azurekeyvault KMP` | N/A | N/A| N/A | N/A | + +If users have keys or certificates stored in a KMS, which does not have a corresponding `KMP` resource in Ratify, user can download the key or certificate from the KMS and configure a `inline KMP` for verification. + +## Scenarios + +### Certificate rotation + +Alice, an application engineer at Contoso LLC, focuses on securing containerized applications. She sets up the build pipeline to sign container images using certificates stored in a KMS. To ensure that only trusted images are deployed in Kubernetes clusters, she deploys Gatekeeper and Ratify. Ratify is configured to validate signatures using certificates in a KMS. Alice applies the security best practice that the certificate expires after certain periods, and automatic rotation is enabled before expiry. Alice wants Ratify to sync multiple versions of the certificate in AKV including latest version and previous versions. This way, Ratify can successfully verify images signed by different versions without requiring manual configuration adjustments. Additionally, she sets up the pipeline to update the signing certificate to the latest version for signing container images. By doing so, she ensures that container images are consistently signed with up-to-date certificates. The automatic handling of certificate updates enhances security and reduces the risk of service disruptions. + +### Certificate revocation + +A malicious user compromised specific versions of certificate. Alice promptly reported this incident to the Certificate Authority (CA). The CA added the compromised versions to the Certificate Revocation List (CRL) and responded to Online Certificate Status Protocol (OCSP) queries with the revoked status. As Alice expects, Ratify denies the deployment of images that were signed with revoked versions. Alice rotated the certificate manually resulting a new version of the certificate with new cryptographic materials. Alice wants Ratify to sync multiple versions of a certificate including latest version and previous versions, This way, it ensures successful verification of images signed with the latest version without requiring manual configuration adjustments. Additionally, the build pipeline was triggered to build and sign all the images with the new version of the certificate. By following these actions, Alice maintains security while seamlessly transitioning to the new certificate version. + +### Key rotation + +Bob, an application engineer at Wabbit Network LLC, focuses on securing containerized applications. He sets up the build pipeline to sign container images using key pairs in a KMS. To ensure that only trusted images are deployed in Kubernetes clusters, he deploys Gatekeeper and Ratify. Ratify is configured to validate signatures using public keys in the KMS. As a security best practice, Bob configured automated key rotation for keys in the KMS, resulting a new version of the key created before key expiry. Bob wants Ratify to sync multiple versions of a key including latest version and previous versions, allowing successful verification of images signed by different key versions without any manual configuration adjustments. Additionally, Bob sets up the pipeline to update the signing key to the latest version for signing container images. Bob knows that images are not allowed to be signed using keys that have expired in the KMS, but verification using expired key is allowed for verifying images that were signed at the time the key was valid. By following these actions, Bob maintains security while seamlessly supporting verification with the new version of key. + +### Key disabling + +A malicious user compromised the private key used for signing images. Bob promptly rotated the key manually, obtaining a new version, and disabled the compromised version. Bob wants Ratify to sync multiple versions of a key including latest version and previous versions, and excluding any disabled versions without requiring manual configuration adjustments, as a result, images signed with the disabled version fail signature verification and images signed with new version can be verified successfully. Additionally, the build pipeline was triggered to build and sign all the images with the new version of key. By following these actions, Bob maintains security while seamlessly transitioning to the new version of key. + +### Update certificates and keys manually + +In some scenarios, such as when the private key is compromised, both Alice and Bob prefer Ratify update cached keys or certificates immediately. They expect that images signed with compromised keys will fail validation immediately to avoid potential security attacks, and images signed with the rotated version can be validated successfully to prevent potential service downtime. To achieve this, they want to trigger Ratify to sync latest versions of the certificate and key promptly, ensuring that the necessary updates are applied. This way, images can be verified with the correct keys/certificates. + +## Proposed solutions + +There are two methods to update keys or certificate automatically: +- **Periodic retrieval of enabled keys or certificates**: Ratify periodically retrieve multiple enabled keys or certificates from the KMS. + - Pros: + - Simplicity (no need for real-time event handling). + - Predictable resource usage. + - Works well for less time-sensitive use cases + - Cons: + - May lead to delays of updates. + - Frequent queries can impact performance. +- **Event-Driven Notification**: Ratify subscribes to KMS events, when a relevant event occurs, Ratify receives an event notification. Then Ratify can retrieve the latest enabled keys or certificates from the KMS. + - Pros: + - Real-time responsiveness. + - Efficient use of resources (only fetches when needed). + - Minimizes downtime for key or certificates changes. + - Cons: + - Requires adapting to different event infrastructure (webhooks, message queues). + - Complexity in handling event delivery and retries. + +The proposed solution is **Periodic retrieval of enabled keys or certificates** because it is simpler and the update is not a time-sensitive action. Users can manual update keys or certificates as a complementary if required. + +As users may require previous versions for verification, so Ratify can allow users to specify how many previous versions to be synced up. Ratify's default setting is to sync two versions starting from the latest version and the previous enabled version. + +## User experiences + +This section describes the experience that users interact with Ratify using the proposed solution. In summary, the proposed solution maintains the existing user experience for configuring `KMP` resources. Automatic updates of keys or certificates occur seamlessly in the background. However, if users utilize inline `KMP` resources, they will still need to manually update the keys or certificates. Importantly, the automatic updates do not prevent users from making manual updates when necessary. + +### Automatically update keys and certificates + +If users specify key or certificate versions in the `KMP` resource, then only specific versions are updated automatically. For example, if specific versions are disabled or deleted in KMS, then they cannot be used for signature verification after updates. See an example of `KMP` configuration with the version `${KEY_VER}` specified for a key `${KEY_NAME}`: + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + version: ${KEY_VER1} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +``` + +If users configure key or certificate names or aliases or IDs, no version are specified, then Ratify will sync the latest version and multiple previous versions. Ratify's default setting is to sync the latest version and one previous version. You can specify a parameter named `previousVersionCount` for multiple previous versions. The default value is `1`. If you specify the parameter value `0`, which means only latest version is synced. Disabled versions are not synced. See an example of `KMP` configuration, no versions specified for the key `${KEY_NAME}`. Ratify will fetches two versions `${KEY_VER_LATEST}` and `${KEY_VER_1}`. + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +``` + +If your want to sync two previous versions, you can specify `previousVersionCount` to value `2`, for example, + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} + previousVersionCount: 2 +``` + +The following is an example of `KMP` configuration, no versions specified for the certificate `${CERT_NAME}`. Ratify will sync two certificate versions `${CERT_VER_LATEST}` and `${CERT_VER_1}`。 + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + certificates: + - name: ${CERT_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +``` + +### Configure update interval + +Users should have the ability to customize the time interval at which the retrieval process occurs, allowing them to override the default interval (e.g., 24 hours). A new parameter named `updateInterval` is introduced for a `KMP` resource using a KMS as provider, such as AKV. + +The default value for `updateInterval` determines how long the updated versions of keys or certificates will be available for verifying images. For Notary Project signatures, it is the root CA certificate retrieved and configured in trust store. In most cases, root CA certificates have a long validity period. Normally certificate rotation will not result in a change on root CA certificate. For Cosign signatures with key pairs in AKV, it depends on how quickly the pipeline switching to use the latest version for signing. The update interval should also consider the impact on normal verification traffic and rotation frequency in AKV. The current recommendation for default value is 24 hours, and users can configure a proper value based on their own situations. + +Below is an example to override the default retrieval interval: + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} + updateInterval: 4h +``` + +### Error handling during automatic update + +Some possible causes of the automated update failure are: Ratify cannot access the KMS due to permission changes, or there was a network issue during the update. If this happens, the cached keys and certificates will not be updated, and the `KMP` resource will remain the same as before. It is recommended generating warning logs for the failures and reasons, and producing failure metrics for monitoring, for example, `kmpAutoUpdateFailureCount`. The failure will not affect the next automated update. The cause of the failure could be a configuration problem on Ratify, a KMS issue, or something else. Once it is fixed, users have the option to manually update their keys and certificates instead of waiting for the next scheduled update. + +### Manually update certificates and keys + +You can always update existing `KMP` resources with new configuration without waiting for the automated update. To do it, you just update the existing resource file and then use `kubectl apply` command to apply the changes. For example, after you update the key name in the `KMP` resource file `my_akv_kmp.yaml`, you can execute the following command: + +```shell +kubectl apply -f my_akv_kmp.yaml +``` + +If you do not have any updates of keys/certificates parameters in existing `KMP` resource, but you want to trigger an immediate update of keys/certificates without waiting for the next round of automated update, you can update a specific annotation in the `KMP` resource to trigger an immediate update. For example, to trigger Ratify fetches the latest version right after key rotation, the annotation `metadata.annotations.forceUpdate` is added and set to `1`. The annotation name and value are for inspiring. We can choose a better name and value handling during design. + +```yaml +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: + name: keymanagementprovider-akv + annotations: + forceUpdate: 1 +spec: + type: azurekeyvault + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +``` + +> You can delete and recreate the resource to trigger an update. But this may cause service down time as the resource will be deleted first, then no keys or certificates can be used for verification before a new resource is created. diff --git a/docs/proposals/Error-Messages-Improvements.md b/docs/proposals/Error-Messages-Improvements.md new file mode 100644 index 000000000..d01b54e06 --- /dev/null +++ b/docs/proposals/Error-Messages-Improvements.md @@ -0,0 +1,135 @@ +# Error messages improvements + +## Problem/Motivation + +Error messages are crucial because they provide specific information about what went wrong, helping users quickly identify and resolve issues. Detailed error messages can pinpoint the exact part of the configuration or code that caused the problem. Error messages can include links or references to documentation, guiding users on how to fix the issue promptly. While testing the Ratify policy for signature verification, we noticed that some error messages were difficult to comprehend from the user perspective. + +Error message example 1: + +```text +time=2024-07-17T16:28:16.939576441Z level=warning msg=Original Error: (Original Error: (HEAD "https:/ +roacr.azurecr.io/v2/net-monitor/manifests/v2": GET "https://roacr.azurecr.io/oauth2/token? +scope=repository%3Anet-monitor%3Apull&service=roacr.azurecr.io": response status code 401: unauthorized: +authentication required, visit https://aka.ms/acr/authorization for more information.), Error: repository +operation failure, Code: REPOSITORY_OPERATION_FAILURE, Plugin Name: oras), Error: get subject descriptor +failure, Code: GET_SUBJECT_DESCRIPTOR_FAILURE, Plugin Name: oras, Component Type: referrerStore, Detail: +failed to resolve the subject descriptor component-type=referrerStore go.version=go1.21.10 namespace= +trace-id=34b27888-5402-443e-9836-77124c840561 +``` + +The above example indicated the an error happened, however, a warning level was set for the log. The error message was set to the field `msg`. It contained nested errors. The first original error message correctly described the source of the problem "401 unauthorized" and pointed to a document for resolution, however, errors following the original error were redundant and not well formatted, thus complicated the overall message. The overall message failed to describe the context of the error, although "401 unauthorized" explained the reason, but did this error happen during signature verification or else? What does the `subject` mean? + +Error message example 2: + +```text +"verifierReports": [ + { + "subject": "docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6", + "isSuccess": false, + "message": "verification failed: Error: no verifier report, Code: NO_VERIFIER _REPORT, Component Type: + executor, Description: No verifier report was generatec preventing access to the registry, or the absence + of appropriate verifiers corresponding to the referenced image artifacts." + } +] +``` + +When Ratify completes artifact verification, the result is returned to the policy engine in the format of the json object `verifierReports`. The `verifierReports` is also recorded in an INFO log of Ratify. If `isSuccess` field is set to `false`, the `message` field is set to error messages. In above example, the message is not well formatted and lacked clarity on the problem, its cause, and remediation methods. It's hard for users to understand in what context the error happened and what users need to do. For example, is it an error for signature verification? What does "no verifier report" mean? We also observed that the `verifierReports` contains different supported fields when compared with the Ratify Config policy and Ratify Rego policy, which is inconsistent. + +Error message example 3: + +```text +Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: +[ratify-constraint] Subject failed verification: huishwabbit1.azurecr.io/ +test8may24@sha256:c780036bc8a6f577910bf01151013aaa18e255057a1653c76d8f3572aa3f6ff6 +``` + +The policy engine, for instance, Gatekeeper, has produced the above error message using a constraint template supplied by Ratify. It is the responsibility of the policy engine to tailor the constraint template for proper error messages to their requirements, however, it requires Ratify to provided useful verification reports as data inputs. In this example, the error message is not clear to users regarding the meaning of the term `Subject`, and fails to specify the context of failure, such as whether it was related to signature verification or SBOM verification or other verifications. Additionally, reasons behind the error were not provided. Furthermore, users may not be able to locate this error in the complete K8s logs to view more logs during error happened, because only artifact digest was shown and it is not enough to pinpoint the exact error in K8s logs. + +Further findings covering a range of cases such as Key Management Provider (KMP), Store, Verifier, Policy configuration, access control, and signature verification issues are recorded at [Ratify Error Handling Scenarios.md](../discussion/Ratify%20Error%20Handling%20Scenarios.md) + +In summary, the areas that need enhancement include: + +- Error messages similar to the first example will appear in Ratify logs. These may be found in the logs of Ratify Pods if Ratify is set up as a Kubernetes service, or they might be output by the Ratify CLI. The primary concerns include excessive nested errors, lacking error context, and not user-friendly error descriptions. +- Error messages contained within `verifierReports` that are sent back to the policy engine, as seen in the second example. These error messages share issues similar to those mentioned for the first example. The policy engine can customize their Rego policies using the messages inside the `verifierReports` to return to users in the logs or UI of the policy engine. +- Ratify failed to provide sufficient information for the policy engine to generate error messages, which is demonstrated in the third example. + +The document aims to provide solutions and guidelines to improve error messages. + +## Scenarios + +### Error messages displayed in the Ratify logs + +Alice works as a DevOps engineer at Contoso. She set up tasks to deploy containerized apps into Kubernetes clusters. The cluster is assigned with the policy to deny the deployment of images that don't pass policy evaluation including verification of signature, SBOM, vulnerability reports and other image metadata. Alice knows that behind the scene, it is the Ratify conduct the verification and returned results as reports to the policy engine. When policy evaluation fails, Alice sees clear and actionable error messages in Ratify logs. The error messages contain concise error descriptions, error reasons and error recommendations, allowing her to act on errors promptly. + +### Error messages displayed in verification reports used by the policy engine + +Bob is a software engineer on Contoso's Policy team, writing policies used during admissions in Kubernetes clusters. These policies evaluate images based on verifier reports generated by Ratify. If policy evaluation fails, Ratify sends back the reports with error messages to the policy engine. The reports, in JSON format, provide structured error messages that Bob utilizes to create clear and actionable error messages for the policy engine. These messages include concise error descriptions, error reasons, and error recommendations, allowing policy users to act on errors promptly. + +### Error messages returned by Ratify CLI commands + +Gina is a software engineer on the CI/CD team at Contoso, where she creates pipeline tasks incorporating Ratify CLI commands to assess artifacts according to policies. Should a policy check not pass, the corresponding artifacts are prevented from progressing in the pipeline. When policy evaluation fails, Gina sees concise, clear and actionable error messages returned by Ratify CLI commands. The error messages contain concise error descriptions, error reasons and error recommendations, allowing her to act on errors promptly. + +## Proposed solutions + +We won’t create new error message guidelines; instead, we’ll refer to the existing ones. [Azure CLI Error Handling Guidelines](https://github.com/Azure/azure-cli/blob/dev/doc/error_handling_guidelines.md#error-message) outlined a general pattern for error messages, consisting of: + +1. __What the error is.__ +2. __Why it happens.__ +3. __What users need to do to fix it.__ + +The proposed improvements for Ratify error messages adhere to this general pattern and the detailed DOs and DON'Ts provided in the guidelines. The error message will also include an error code. Since Ratify already supports a list of error codes, these can be used to search for remediation in the troubleshooting guide. For example, search error code `CERT_INVALID` in [the troubleshooting guide](https://ratify.dev/docs/troubleshoot/key-management-provider/kmp-tsg#cert_invalid). + +The recommended format for an error message in the Ratify log is as following. + +```text +": : : " +``` + +For the error messages displayed in `verifierReports`, it is recommended to add two new optional fields `errorReason` and `remediation`, which will be used when the field `isSuccess` is set to `false`: + +```text +"verifierReports": [ + { + "subject": "" + "isSuccess": false, + "message": "", + "errorReason": "", + "remediation": "" + } +] +``` + +## Examples + +### Error messages displayed in the Ratify logs or returned by Ratify CLI commands + +For the above first example, the error message in the Ratify log can be improved to: + +```text +REPOSITORY_OPERATION_FAILURE: Failed to resolve the artifact descriptor: HEAD "https://roacr.azurecr.io/v2/net-monitor/manifests/v2": GET "https://roacr.azurecr.io/oauth2/token? +scope=repository%3Anet-monitor%3Apull&service=roacr.azurecr.io": response status code 401: unauthorized: +authentication required, visit https://aka.ms/acr/authorization for more information. +``` + +### Error messages displayed in `verifierReports` + +For the second example, the error message can be improved to: + +```text +"verifierReports": [ + { + "subject": "docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6", + "isSuccess": false, + "message": "NO_VERIFIER_REPORT: Failed to verify artifact docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6: + "errorReason": "No signature is found or wrong configuration" + "remediation": "Please either sign the artifact or configure verifiers for signature verification. Learn more at https://ratify.dev/docs/plugins/verifier/notation." + } +] +``` + +> This link https://ratify.dev/docs/plugins/verifier/notation is used as an example to illustrate the improvements. The link should vary depending on the particular error encountered. + +## References + +- [Azure CLI Error Handling Guidelines](https://github.com/Azure/azure-cli/blob/dev/doc/error_handling_guidelines.md) +- [ORAS CLI Error Handling and Message Guideline](https://github.com/oras-project/oras/blob/v1.2.0/docs/proposals/error-handling-guideline.md) diff --git a/docs/proposals/Release-Supply-Chain-Metadata.md b/docs/proposals/Release-Supply-Chain-Metadata.md new file mode 100644 index 000000000..e50806de1 --- /dev/null +++ b/docs/proposals/Release-Supply-Chain-Metadata.md @@ -0,0 +1,192 @@ +# Supply Chain Metadata for Ratify Assets + +Ratify currently publishes multiple forms of release assets both for production and development uses. Currently, these assets are not published with accompanying supply chain metadata including signatures, SBOMs, and provenance. Shipping each of these forms of metadata with all binaries and container images produced by Ratify will provide consumers a verifiable way to guarantee integrity of Ratify assets. Furthermore, this will improve Ratify's OSSF scorecard. In general, Ratify will prioritize addressing HIGH, MEDIUM risks surfaced by OSSF scorecard. Learn more about the OSSF checks performed [here](https://github.com/ossf/scorecard/blob/main/docs/checks.md) + +## What does Ratify currently publish? + +Ratify publishes two types: release and development. Release assets accompany official Ratify Github releases. Development assets are published weekly (or adhoc as needed). + +Each publish type includes the following group of assets: + +* CRD container image to `ghcr.io/ratify-project` +* Base container image to `ghcr.io/ratify-project` +* Base + plugins container image to `ghcr.io/ratify-project` +* Ratify binaries as a single bundle per OS/arch which includes: + * ratify binary + * sbom-verifier plugin binary + * vulernability-report verifier plugin binary +* `checksums.txt` which contains list of checksum for each binary bundle +* Helm chart + * Release ONLY: published packaged helm chart to `ratify-project.github.io` + * Dev ONLY: published packaged helm chart to `ghcr.io/ratify-project` +* Source code (zip + tar ball) + +## Signatures + +Ratify should support signing all container images and binaries with both Sigstore Cosign and Notary Project. + +### Notary Project Image Signatures + +Ratify can utilize Notary Project's install and sign github actions published. +Notation requires a KMS to store the signing certificate. There is no support for Github Encrypted Secrets for storing the certificate. + +1. Configure Notation action to sign using certificate's associated key stored in AKV. +2. Sign CRD, base, plugin images + +#### Certificate Lifecycle Management + +Ratify will use a self-signed certificate stored in Azure Key Vault. This certificate is valid for 1 year. A new version is auto-generated after 9.5 months. +The notation signing action will be configured to always use the latest certificate version. + +The verification certificate will be published in 2 different directories: + +* Ratify website @ `ratify.dev/.well-known/pki-validation/...` +* Root of Ratify github repository @ `https://github.com/ratify-project/ratify/main/.well-known/pki-validation/...` + +The verification steps in the security section of Ratify website will recommend downloading certificates from the ratify website. + +The latest certificate can always be found at `ratify.dev/.well-known/pki-validation/ratify-verification.crt`. When a new version of the certificate is generated, the `./ratify-verification.crt` content MUST be updated to contain the new certificate. The previous version will be preserved as a separate file following convention: `./ratify-verification_.crt` where `` is the last date where that particular certificate version was valid. Ratify will NOT re-sign any dev release assets so older versions of certificates must be published so users can continue to validate those. + +NOTE: certificate update operations will be a MANUAL process and maintainers must track certificate regeneration date and make updates accordingly as specified by convention above. + +### Cosign Image Signatures + +Ratify can utilize Cosign's installer github action to install cosign on the runner. There is no accompanying sign action so the CLI must be directly used. Cosign will be used to generate keyless signatures. Signature will be uploaded to the Sigstore public-good transparency log. Cosign will use Ratify's workflow OIDC identity for signing. This will guarantee that the trusted identity is the Ratify workflow that published the images. + +1. Sign CRD, base, plugin images with cosign keyless + +### Sign binaries + +Notary Project is about to release support for blob signing which allows for all binaries to be signed using the same signing certificate used for container image signing. The public certificate can be appended to the release assets. + +Cosign has support for binary signing and Ratify can leverage the built-in GoReleaser support to achieve this. The keyless support will automatically append the `.sig` files as well as `.pem` to use for verification to the release. + +## SBOM + +There are multiple tools for generating SBOMS such as Syft, Trivy, Microsoft SBOM tool, etc. + +### Docker Buildx Attestations + +Recently, Ratify added support for generating SBOM in-toto attestations via buildx. Buildx supports generating SBOM via Syft and attaching the attestation as an OCI Image part of the top level image index. Read more [here](https://ratify.dev/docs/next/troubleshoot/security#build-attestations). + +Other OSS projects such as Gatekeeper publish attestations in a similar fashion. It's very simple to add such support making adoption more widespread. + +These attestations produced via buildx adhere to a docker-specific standard for discoverability and verification. + +Here's an example image index with an SBOM attestation: + +```bash +$ docker buildx imagetools inspect ghcr.io/ratify-project/ratify:v1.3.0 +Name: ghcr.io/ratify-project/ratify:latest +MediaType: application/vnd.oci.image.index.v1+json +Digest: sha256:f261f5076b8a1fd3f53cfbd10f647899d5875e4fcd40b1854598a18f580b422d + +Manifests: + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:c99c9b5edfe005e0454c4160388a70520844d1856c1fcc3f8557532d6a034f32 + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: linux/amd64 + + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:f1b520af44d5e22b9b8702cbbcd651092df8672ed7822851266b17947c2a0962 + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: linux/arm64 + + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:6105d973c1c672379abfdb63486a0327d612c4fe67bb62e4d20cb910c0008aa9 + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: linux/arm/v7 + + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:836450813252daf7854b0aec1ccafe486bbb1352ec234b9adf105ddc24b0cb37 + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: unknown/unknown + Annotations: + vnd.docker.reference.digest: sha256:c99c9b5edfe005e0454c4160388a70520844d1856c1fcc3f8557532d6a034f32 + vnd.docker.reference.type: attestation-manifest + + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:dcfa5faf20c916c9a41dd4636939594d8164f467ebb00d73570ae13cbcbf59ad + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: unknown/unknown + Annotations: + vnd.docker.reference.digest: sha256:f1b520af44d5e22b9b8702cbbcd651092df8672ed7822851266b17947c2a0962 + vnd.docker.reference.type: attestation-manifest + + Name: ghcr.io/ratify-project/ratify:v1.3.0@sha256:c936d0ed115975ee7fc8196fbc5baff8100e92bff3d401c60df6396b9451e773 + MediaType: application/vnd.oci.image.manifest.v1+json + Platform: unknown/unknown + Annotations: + vnd.docker.reference.type: attestation-manifest + vnd.docker.reference.digest: sha256:6105d973c1c672379abfdb63486a0327d612c4fe67bb62e4d20cb910c0008aa9 +``` + +You can see the `attestation-manifest` reference-type. + +To inspect, it's recommended to use docker's image inspection tool: + +```shell +docker buildx imagetools inspect ghcr.io/ratify-project/ratify:v1.3.0 --format '{{ json .SBOM }}' +``` + +### Referrer Artifacts + +Ratify should support generating SBOMs via an OSS tool and attach to the published container images via ORAS. This would be in line with the Ratify verification capabilities supported by the project. + +### SBOM Binary Artifacts + +Ratify should support publishing an SBOM in a common format like SPDX as a release artifact published next to each OS/arch specific binary. + +GoRelease already support automatic SBOM generation and Ratify should update GoReleaser to take advantage of this support. Example [here](https://github.com/goreleaser/goreleaser-example-supply-chain/blob/d6d60f6320dbe97bda24b6351d9afa2035b3a23a/.goreleaser.yaml#L48). + +Ratify should also sign the SBOM using both notation and cosign. + +## Provenance + +### Docker Buildx Attestation + +Please refer to SBOM section for format [details](#docker-buildx-attestations). + +Ratify also added SLSA provenance attestation generation support as part of docker buildx. Similar to SBOM support, this adds a SLSA provenance attestation to the image index during image build. + +### Referrer Artifacts (TBD) + +As a future improvement, Ratify can look into attaching the SLSA build provenance metadata as a referrer artifact attached to the image. This might be in the form of a standalone artifact or packaged in an in-toto attestation. + +### Provenance Binary Artifacts + +Ratify should support publishing an provenance file for each OS/arch binary. The OSSF scorecard also recommends (and looks for) a provenance file in the release. + +This would involve adding an extra workflow to generate the SLSA provenance for each output binary after GoReleaser has finished. Example [here](https://github.com/slsa-framework/slsa-github-generator/blob/main/.github/workflows/generator_generic_slsa3.yml). + +Ratify should also sign each Provenance release file with notation and cosign. + +## Proposed Dev Implementation + +### Questions + +1. Is it ok to use a self-signed certificate for Ratify's signing purposes? Yes, we are ok with this. +2. How do we handle certificate revocation scenarios? Is it Ratify's responsibility to resign all the release and dev images? Ratify will follow the supportability promise and only resign the **latest** minor release assets. +3. For binary signing, should Ratify only sign the `checksums.txt` or should Ratify sign all the assets individually? All assets should be signed. +4. Do we need to publish the same artifacts as referrers as well or is it sufficient to use docker buildx attestations? Ratify will consider this in the future as need arises. Right now, other OSS projects, like OPA Gatekeeper. have adopted buildx attestations. + +### Stage 1 + +* Generate SBOM buildx attestations and publish for release and dev images +* Generate SLSA provenance buildx attestation and publish for release and dev images +* Add verification guidance to Ratify website under `Security` section + +### Stage 2 + +* Container image signing using Notation for dev images and chart + * Generate and store self signed certificate in Azure Key vault + * AKV is in same subscription used for azure e2e tests + * Publish verification public cert in the root of the Ratify repo +* Container image signing using Cosign for dev images + * Keyless support only + * Publish to rekor public good transparency log +* Signing support only introduced for dev images to test if workflow and approach is what Ratify should adopt long term +* Add SBOM generation to GoReleaser +* Add verification guidance to Ratify website under `Security` section + +### Future (TBD) + +* Attach SBOMs to release and dev images using Syft (not as in-toto attestations) +* Add Provenance generation after GoReleaser +* Introduce binary signing using Notation and Cosign +* Attach SLSA provenance to release and dev images as referrers diff --git a/errors/errors.go b/errors/errors.go index 75ccbc784..9305f795c 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -119,10 +119,17 @@ var ( Description: "Failed to encode data. Please verify the encoding data.", }) - // ErrorCodeKeyManagementConflict is returned when key management provider and certificate store are configured together. - ErrorCodeKeyManagementConflict = Register("errcode", ErrorDescriptor{ - Value: "KEY_MANAGEMENT_CONFLICT", - Message: "key management provider and certificate store should not be configured together", - Description: "Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.", + // ErrorCodeNotFound is returned when the requested resource is not found. + ErrorCodeNotFound = Register("errcode", ErrorDescriptor{ + Value: "RESOURCE_NOT_FOUND", + Message: "resource not found", + Description: "The requested resource is not found. Please verify the resource exists.", + }) + + // ErrorCodeForbidden is returned when the requested operation is forbidden. + ErrorCodeForbidden = Register("errcode", ErrorDescriptor{ + Value: "OPERATION_FORBIDDEN", + Message: "operation forbidden", + Description: "The requested operation is forbidden. Please verify the permission to the requested resource.", }) ) diff --git a/errors/pluginerrors.go b/errors/pluginerrors.go index 81944f588..1e44d19d6 100644 --- a/errors/pluginerrors.go +++ b/errors/pluginerrors.go @@ -105,11 +105,11 @@ var ( Description: `The manifest is invalid. Please validate the manifest is correctly formatted.`, }) - // ErrorCodeReferrersNotFound is returned if there is no ReferrerStore set. - ErrorCodeReferrersNotFound = Register("errcode", ErrorDescriptor{ - Value: "REFERRERS_NOT_FOUND", - Message: "referrers not found", - Description: "No referrers are found. Please verify the subject has attached expected artifacts and refer to https://ratify.dev/docs/reference/store/ to investigate Referrer Store configuration.", + // ErrorCodeNoVerifierReport is returned if there is no ReferrerStore set. + ErrorCodeNoVerifierReport = Register("errcode", ErrorDescriptor{ + Value: "NO_VERIFIER_REPORT", + Message: "no verifier report", + Description: "No verifier report was generated. This might be due to various factors, such as lack of artifacts attached to the image, a misconfiguration in the Referrer Store preventing access to the registry, or the absence of appropriate verifiers corresponding to the referenced image artifacts.", }) // Generic errors happen in plugins @@ -145,6 +145,14 @@ var ( Description: "The certificate is invalid. Please verify the provided inline certificates or certificates fetched from key vault are in valid format. Refer to https://ratify.dev/docs/reference/crds/certificate-stores for more information.", }) + // ErrorCodeKeyInvalid is returned when provided key is invalid. + // TODO: add website docs for this error code and update URL for error description + ErrorCodeKeyInvalid = Register("errcode", ErrorDescriptor{ + Value: "KEY_INVALID", + Message: "key invalid", + Description: "The key is invalid. Please verify the provided inline key or key fetched from key vault is in valid format. Refer to [INPUT URL] for more information.", + }) + // ErrorCodePolicyProviderNotFound is returned when a policy provider cannot // be found. ErrorCodePolicyProviderNotFound = Register("errcode", ErrorDescriptor{ @@ -160,4 +168,11 @@ var ( Message: "Key vault operation failed", Description: "Key vault operation failed. Please validate correct key vault configuration is provided or check the error details for further investigation.", }) + + // ErrorCodeKeyManagementProviderFailure is returned when a key management provider operation fails. + ErrorCodeKeyManagementProviderFailure = Register("errcode", ErrorDescriptor{ + Value: "KEY_MANAGEMENT_PROVIDER_FAILURE", + Message: "Key management provider failure", + Description: "Generic failure in key management provider. Please validate correct key management provider configuration is provided or check the error details for further investigation.", + }) ) diff --git a/errors/types.go b/errors/types.go index 8b4529e83..0f8f6ea24 100644 --- a/errors/types.go +++ b/errors/types.go @@ -60,14 +60,16 @@ type ErrorCode int // Error provides a wrapper around ErrorCode with extra Details provided. type Error struct { - OriginalError error `json:"originalError,omitempty"` - Code ErrorCode `json:"code"` - Message string `json:"message"` - Detail interface{} `json:"detail,omitempty"` - ComponentType ComponentType `json:"componentType,omitempty"` - PluginName string `json:"pluginName,omitempty"` - LinkToDoc string `json:"linkToDoc,omitempty"` - Stack string `json:"stack,omitempty"` + originalError error + code ErrorCode + message string + detail interface{} + componentType ComponentType + remediation string + pluginName string + stack string + description string + isRootError bool // isRootError is true if the originalError is either nil or not an Error type } // ErrorDescriptor provides relevant information about a given error code. @@ -124,6 +126,11 @@ func (ec ErrorCode) Message() string { return ec.Descriptor().Message } +// Description returned the description of this error code. +func (ec ErrorCode) Description() string { + return ec.Descriptor().Description +} + // String returns the canonical identifier for this error code. func (ec ErrorCode) String() string { return ec.Descriptor().Value @@ -144,9 +151,13 @@ func (ec ErrorCode) WithComponentType(componentType ComponentType) Error { return newError(ec, ec.Message()).WithComponentType(componentType) } -// WithLinkToDoc returns a new Error object with attached link to the documentation. -func (ec ErrorCode) WithLinkToDoc(link string) Error { - return newError(ec, ec.Message()).WithLinkToDoc(link) +// WithRemediation returns a new Error object with remediation. +func (ec ErrorCode) WithRemediation(link string) Error { + return newError(ec, ec.Message()).WithRemediation(link) +} + +func (ec ErrorCode) WithDescription() Error { + return newError(ec, ec.Message()).WithDescription() } // WithPluginName returns a new Error object with pluginName set. @@ -155,27 +166,29 @@ func (ec ErrorCode) WithPluginName(pluginName string) Error { } // NewError returns a new Error object. -func (ec ErrorCode) NewError(componentType ComponentType, pluginName, link string, err error, detail interface{}, printStackTrace bool) Error { +func (ec ErrorCode) NewError(componentType ComponentType, pluginName, remediation string, err error, detail interface{}, printStackTrace bool) Error { stack := "" if printStackTrace { stack = getStackTrace() } return Error{ - Code: ec, - Message: ec.Message(), - OriginalError: err, - Detail: detail, - ComponentType: componentType, - PluginName: pluginName, - LinkToDoc: link, - Stack: stack, + code: ec, + message: ec.Message(), + originalError: err, + detail: detail, + componentType: componentType, + pluginName: pluginName, + remediation: remediation, + stack: stack, + isRootError: err == nil || !errors.As(err, &Error{}), } } func newError(code ErrorCode, message string) Error { return Error{ - Code: code, - Message: message, + code: code, + message: message, + isRootError: true, } } @@ -183,81 +196,126 @@ func newError(code ErrorCode, message string) Error { func (e Error) Is(target error) bool { t := &Error{} if errors.As(target, t) { - return e.Code.ErrorCode() == t.Code.ErrorCode() + return e.code.ErrorCode() == t.code.ErrorCode() } return false } // ErrorCode returns the ID/Value of this Error func (e Error) ErrorCode() ErrorCode { - return e.Code + return e.code } // Unwrap returns the original error func (e Error) Unwrap() error { - return e.OriginalError + return e.originalError } // Error returns a human readable representation of the error. +// An Error message includes the error code, detail from nested errors, root cause and remediation, all separated by ": ". func (e Error) Error() string { - var errStr string - if e.OriginalError != nil { - errStr += fmt.Sprintf("Original Error: (%s), ", e.OriginalError.Error()) + err, details := e.getRootError() + if err.detail != nil { + details = append(details, fmt.Sprintf("%s", err.detail)) } - - errStr += fmt.Sprintf("Error: %s, Code: %s", e.Message, e.Code.String()) - - if e.PluginName != "" { - errStr += fmt.Sprintf(", Plugin Name: %s", e.PluginName) + if err.originalError != nil { + details = append(details, err.originalError.Error()) } - if e.ComponentType != "" { - errStr += fmt.Sprintf(", Component Type: %s", e.ComponentType) + if err.remediation != "" { + details = append(details, err.remediation) } + return fmt.Sprintf("%s: %s", err.ErrorCode().Descriptor().Value, strings.Join(details, ": ")) +} - if e.LinkToDoc != "" { - errStr += fmt.Sprintf(", Documentation: %s", e.LinkToDoc) +// GetDetail returns details from all nested errors. +func (e Error) GetDetail() string { + err, details := e.getRootError() + if err.originalError != nil && err.detail != nil { + details = append(details, fmt.Sprintf("%s", err.detail)) } - if e.Detail != nil { - errStr += fmt.Sprintf(", Detail: %v", e.Detail) + return strings.Join(details, ": ") +} + +// GetErrorReason returns the root cause of the error. +func (e Error) GetErrorReason() string { + err, _ := e.getRootError() + if err.originalError != nil { + return err.originalError.Error() } + return fmt.Sprintf("%s", err.detail) +} + +// GetRemiation returns the remediation of the root error. +func (e Error) GetRemediation() string { + err, _ := e.getRootError() + return err.remediation +} - if e.Stack != "" { - errStr += fmt.Sprintf(", Stack trace: %s", e.Stack) +// GetConciseError returns a formatted error message consisting of the error code and reason. +// If the generated error message exceeds the specified maxLength, it truncates the message and appends an ellipsis ("..."). +// The function ensures that the returned error message is concise and within the length limit. +func (e Error) GetConciseError(maxLength int) string { + err, _ := e.getRootError() + errMsg := fmt.Sprintf("%s: %s", err.ErrorCode().Descriptor().Value, e.GetErrorReason()) + if len(errMsg) > maxLength { + return fmt.Sprintf("%s...", errMsg[:maxLength-3]) } + return errMsg +} - return errStr +func (e Error) getRootError() (err Error, details []string) { + err = e + for !err.isRootError { + if err.detail != nil { + details = append(details, fmt.Sprintf("%s", err.detail)) + } + var ratifyError Error + if errors.As(err.originalError, &ratifyError) { + err = ratifyError + } else { + // break is unnecessary, but added for safety + break + } + } + return err, details } // WithDetail will return a new Error, based on the current one, but with // some Detail info added func (e Error) WithDetail(detail interface{}) Error { - e.Detail = detail + e.detail = detail return e } // WithPluginName returns a new Error object with pluginName set. func (e Error) WithPluginName(pluginName string) Error { - e.PluginName = pluginName + e.pluginName = pluginName return e } // WithComponentType returns a new Error object with ComponentType set. func (e Error) WithComponentType(componentType ComponentType) Error { - e.ComponentType = componentType + e.componentType = componentType return e } // WithError returns a new Error object with original error. func (e Error) WithError(err error) Error { - e.OriginalError = err + e.originalError = err + e.isRootError = err == nil || !errors.As(err, &Error{}) + return e +} + +// WithRemediation returns a new Error object attached with remediation. +func (e Error) WithRemediation(remediation string) Error { + e.remediation = remediation return e } -// WithLinkToDoc returns a new Error object attached with link to documentation. -func (e Error) WithLinkToDoc(link string) Error { - e.LinkToDoc = link +func (e Error) WithDescription() Error { + e.description = e.code.Description() return e } diff --git a/errors/types_test.go b/errors/types_test.go index 4dc5dd6ce..93a96d962 100644 --- a/errors/types_test.go +++ b/errors/types_test.go @@ -22,26 +22,31 @@ import ( ) const ( - testGroup = "test-group" - testValue = "test-value" - testMessage = "test-message" - testDescription = "test-description" - testDetail = "test-detail" - testComponentType = "test-component-type" - testLink = "test-link" - testPluginName = "test-plugin-name" - testErrorString = "Original Error: (Error: , Code: UNKNOWN), Error: test-message, Code: test-value, Plugin Name: test-plugin-name, Component Type: test-component-type, Documentation: test-link, Detail: test-detail" - nonexistentEC = 2000 + testGroup = "test-group" + testErrCode1 = "TEST_ERROR_CODE_1" + testErrCode2 = "TEST_ERROR_CODE_2" + testMessage = "test-message" + testDescription = "test-description" + testDetail1 = "test-detail-1" + testDetail2 = "test-detail-2" + testComponentType1 = "test-component-type-1" + testComponentType2 = "test-component-type-2" + testLink1 = "test-link-1" + testLink2 = "test-link-2" + testPluginName = "test-plugin-name" + nonexistentEC = 2000 ) var ( testEC = Register(testGroup, ErrorDescriptor{ - Value: testValue, + Value: testErrCode1, Message: testMessage, Description: testDescription, }) - testEC2 = Register(testGroup, ErrorDescriptor{}) + testEC2 = Register(testGroup, ErrorDescriptor{ + Value: testErrCode2, + }) ) func TestErrorCode(t *testing.T) { @@ -52,8 +57,9 @@ func TestErrorCode(t *testing.T) { } func TestError(t *testing.T) { - if testEC.Error() != testValue { - t.Fatalf("expected: %s, got: %s", testValue, testEC.Error()) + expectedStr := "test error code 1" + if testEC.Error() != expectedStr { + t.Fatalf("expected: %s, got: %s", expectedStr, testEC.Error()) } } @@ -66,7 +72,7 @@ func TestDescriptor(t *testing.T) { { name: "existing error code", ec: testEC, - expectedValue: testValue, + expectedValue: testErrCode1, }, { name: "nonexistent error code", @@ -92,9 +98,9 @@ func TestMessage(t *testing.T) { } func TestWithDetail(t *testing.T) { - err := testEC.WithDetail(testDetail) - if err.Detail != testDetail { - t.Fatalf("expected detail: %s, got: %s", testDetail, err.Detail) + err := testEC.WithDetail(testDetail1) + if err.detail != testDetail1 { + t.Fatalf("expected detail: %s, got: %s", testDetail1, err.detail) } } @@ -106,28 +112,47 @@ func TestWithError(t *testing.T) { } func TestWithComponentType(t *testing.T) { - err := testEC.WithComponentType(testComponentType) - if err.ComponentType != testComponentType { - t.Fatalf("expected component type: %s, got: %s", testComponentType, err.ComponentType) + err := testEC.WithComponentType(testComponentType1) + if err.componentType != testComponentType1 { + t.Fatalf("expected component type: %s, got: %s", testComponentType1, err.componentType) } } -func TestWithLinkToDoc(t *testing.T) { - err := testEC.WithLinkToDoc(testLink) - if err.LinkToDoc != testLink { - t.Fatalf("expected link to doc: %s, got: %s", testLink, err.LinkToDoc) +func TestWithRemediation(t *testing.T) { + err := testEC.WithRemediation(testLink1) + if err.remediation != testLink1 { + t.Fatalf("expected remediation: %s, got: %s", testLink1, err.remediation) } } func TestWithPluginName(t *testing.T) { err := testEC.WithPluginName(testPluginName) - if err.PluginName != testPluginName { - t.Fatalf("expected plugin name: %s, got: %s", testPluginName, err.PluginName) + if err.pluginName != testPluginName { + t.Fatalf("expected plugin name: %s, got: %s", testPluginName, err.pluginName) + } +} + +func TestWithDescription(t *testing.T) { + err := testEC.WithDescription() + if err.description != testDescription { + t.Fatalf("expected description: %s, got: %s", testDescription, err.description) + } +} + +func TestGetConciseError(t *testing.T) { + err := testEC.WithDetail("long message, long message, long message") + if err.GetConciseError(30) != "TEST_ERROR_CODE_1: long mes..." { + t.Fatalf("expected: TEST_ERROR_CODE_1: long mes..., got: %s", err.GetConciseError(30)) + } + + err = testEC.WithDetail("short message") + if err.GetConciseError(100) != "TEST_ERROR_CODE_1: short message" { + t.Fatalf("expected: TEST_ERROR_CODE_1: short message, got: %s", err.GetConciseError(100)) } } func TestIs(t *testing.T) { - err := testEC.WithDetail(testDetail) + err := testEC.WithDetail(testDetail1) result := err.Is(err) if !result { t.Fatalf("expected true, got: %v", result) @@ -142,7 +167,7 @@ func TestIs(t *testing.T) { func TestError_ErrorCode(t *testing.T) { err := Error{ - Code: 1, + code: 1, } if err.ErrorCode() != 1 { t.Fatalf("expected 1, got: %d", err.ErrorCode()) @@ -161,17 +186,64 @@ func TestIsEmpty(t *testing.T) { } func TestError_Error(t *testing.T) { - err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType).WithLinkToDoc(testLink).WithDetail(testDetail).WithError(Error{}) - result := err.Error() - if !strings.HasPrefix(result, testErrorString) { - t.Fatalf("expected string starts with: %s, but got: %s", testErrorString, result) + // Nested errors. + rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false) + err := testEC2.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr) + + expectedErrStr := strings.Join([]string{testErrCode1, testDetail2, testDetail1, testMessage, testLink1}, ": ") + if err.Error() != expectedErrStr { + t.Fatalf("expected string: %s, but got: %s", expectedErrStr, err.Error()) + } + + // Single error. + err = testEC.WithDetail(testDetail1) + expectedErrStr = "TEST_ERROR_CODE_1: test-detail-1" + if err.Error() != expectedErrStr { + t.Fatalf("expected string: %s, but got: %s", expectedErrStr, err.Error()) + } +} + +func TestError_GetRootCause(t *testing.T) { + // rootErr contains original error. + rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false) + err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr) + + if err.GetErrorReason() != testMessage { + t.Fatalf("expected root cause: %v, but got: %v", err.GetErrorReason(), testMessage) + } + + // rootErr does not contain original error. + rootErr = testEC.NewError(testComponentType1, "", testLink1, nil, testDetail1, false) + err = testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr) + + if err.GetErrorReason() != testDetail1 { + t.Fatalf("expected root cause: %v, but got: %v", err.GetErrorReason(), testDetail1) + } +} + +func TestError_GetFullDetails(t *testing.T) { + rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false) + err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr) + + expectedDetails := strings.Join([]string{testDetail2, testDetail1}, ": ") + if err.GetDetail() != expectedDetails { + t.Fatalf("expected full details: %v, but got: %v", expectedDetails, err.GetDetail()) + } +} + +func TestError_GetRootRemediation(t *testing.T) { + rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false) + err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr) + + if err.GetRemediation() != testLink1 { + t.Fatalf("expected root remediation: %v, but got: %v", err.GetRemediation(), testLink1) } } func TestNewError(t *testing.T) { - err := testEC.NewError(testComponentType, testPluginName, testLink, Error{}, testDetail, false) + err := testEC.NewError(testComponentType1, testPluginName, testLink1, Error{}, testDetail1, false) - if err.ComponentType != testComponentType || err.PluginName != testPluginName || err.LinkToDoc != testLink || err.Detail != testDetail { - t.Fatalf("expected component type: %s, plugin name: %s, link to doc: %s, detail: %s, but got: %s, %s, %s, %s", testComponentType, testPluginName, testLink, testDetail, err.ComponentType, err.PluginName, err.LinkToDoc, err.Detail) + if err.componentType != testComponentType1 || err.pluginName != testPluginName || err.remediation != testLink1 || err.detail != testDetail1 { + t.Fatalf("expected component type: %s, plugin name: %s, link to doc: %s, detail: %s, but got: %s, %s, %s, %s", testComponentType1, testPluginName, testLink1, testDetail1, err.componentType, err.pluginName, err.remediation, err.detail) } } diff --git a/experimental/generate-protos.sh b/experimental/generate-protos.sh index 92938cf6a..98c4645cd 100644 --- a/experimental/generate-protos.sh +++ b/experimental/generate-protos.sh @@ -20,7 +20,7 @@ protoc \ --proto_path=./ratify/proto/v1 \ --go_out=. \ ---go_opt=module=github.com/deislabs/ratify/experimental \ +--go_opt=module=github.com/ratify-project/ratify/experimental \ --go-grpc_out=. \ ---go-grpc_opt=module=github.com/deislabs/ratify/experimental \ +--go-grpc_opt=module=github.com/ratify-project/ratify/experimental \ ./ratify/proto/v1/*.proto diff --git a/experimental/proto/v1/orchestrator/orchestrator.pb.go b/experimental/proto/v1/orchestrator/orchestrator.pb.go index b1176df88..2372472d0 100644 --- a/experimental/proto/v1/orchestrator/orchestrator.pb.go +++ b/experimental/proto/v1/orchestrator/orchestrator.pb.go @@ -7,8 +7,8 @@ package orchestrator import ( - common "github.com/deislabs/ratify/experimental/proto/v1/common" _struct "github.com/golang/protobuf/ptypes/struct" + common "github.com/ratify-project/ratify/experimental/proto/v1/common" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" diff --git a/experimental/proto/v1/referrerstore/referrerstore.pb.go b/experimental/proto/v1/referrerstore/referrerstore.pb.go index 82400ac96..18c595ae2 100644 --- a/experimental/proto/v1/referrerstore/referrerstore.pb.go +++ b/experimental/proto/v1/referrerstore/referrerstore.pb.go @@ -7,8 +7,8 @@ package referrerstore import ( - common "github.com/deislabs/ratify/experimental/proto/v1/common" _struct "github.com/golang/protobuf/ptypes/struct" + common "github.com/ratify-project/ratify/experimental/proto/v1/common" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" diff --git a/experimental/proto/v1/verifier/verifier.pb.go b/experimental/proto/v1/verifier/verifier.pb.go index b5cab617d..26c9eec34 100644 --- a/experimental/proto/v1/verifier/verifier.pb.go +++ b/experimental/proto/v1/verifier/verifier.pb.go @@ -7,8 +7,8 @@ package verifier import ( - common "github.com/deislabs/ratify/experimental/proto/v1/common" _struct "github.com/golang/protobuf/ptypes/struct" + common "github.com/ratify-project/ratify/experimental/proto/v1/common" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" diff --git a/experimental/ratify/proto/README.md b/experimental/ratify/proto/README.md index 568c6ee86..f060aa28c 100644 --- a/experimental/ratify/proto/README.md +++ b/experimental/ratify/proto/README.md @@ -30,4 +30,4 @@ cd ./experimental ```sh go mod tidy ``` -4. _**Depending on the value of "go_package" for each proto file, you may have to move the generated code to an alternate directory.**_ _e.g. setting the `go_package` to "github.com/deislabs/ratify/experimental/proto/v1/referrerstore" will create such a directory structure. Moving the generated code to {root}/experimental/proto/v1/referrerstore will resolve errors since the project's module is "github.com/deislabs/ratify"_ +4. _**Depending on the value of "go_package" for each proto file, you may have to move the generated code to an alternate directory.**_ _e.g. setting the `go_package` to "github.com/ratify-project/ratify/experimental/proto/v1/referrerstore" will create such a directory structure. Moving the generated code to {root}/experimental/proto/v1/referrerstore will resolve errors since the project's module is "github.com/ratify-project/ratify"_ diff --git a/experimental/ratify/proto/v1/common.proto b/experimental/ratify/proto/v1/common.proto index 311bfd1c3..48d55ea5f 100644 --- a/experimental/ratify/proto/v1/common.proto +++ b/experimental/ratify/proto/v1/common.proto @@ -2,7 +2,7 @@ syntax="proto3"; package common; -option go_package = "github.com/deislabs/ratify/experimental/proto/v1/common"; +option go_package = "github.com/ratify-project/ratify/experimental/proto/v1/common"; /* Descriptor holds various properties of an artifact. diff --git a/experimental/ratify/proto/v1/orchestrator.proto b/experimental/ratify/proto/v1/orchestrator.proto index 6b3e6283f..94ddcca73 100644 --- a/experimental/ratify/proto/v1/orchestrator.proto +++ b/experimental/ratify/proto/v1/orchestrator.proto @@ -2,7 +2,7 @@ syntax="proto3"; package orchestrator; -option go_package = "github.com/deislabs/ratify/experimental/proto/v1/orchestrator"; +option go_package = "github.com/ratify-project/ratify/experimental/proto/v1/orchestrator"; import "common.proto"; import "google/protobuf/struct.proto"; diff --git a/experimental/ratify/proto/v1/referrerstore.proto b/experimental/ratify/proto/v1/referrerstore.proto index 61d5f246d..15e191121 100644 --- a/experimental/ratify/proto/v1/referrerstore.proto +++ b/experimental/ratify/proto/v1/referrerstore.proto @@ -2,7 +2,7 @@ syntax="proto3"; package referrerstore; -option go_package = "github.com/deislabs/ratify/experimental/proto/v1/referrerstore"; +option go_package = "github.com/ratify-project/ratify/experimental/proto/v1/referrerstore"; import "common.proto"; import "google/protobuf/struct.proto"; diff --git a/experimental/ratify/proto/v1/verifier.proto b/experimental/ratify/proto/v1/verifier.proto index 19615674f..c9049dd83 100644 --- a/experimental/ratify/proto/v1/verifier.proto +++ b/experimental/ratify/proto/v1/verifier.proto @@ -2,7 +2,7 @@ syntax="proto3"; package verifier; -option go_package = "github.com/deislabs/ratify/experimental/proto/v1/verifier"; +option go_package = "github.com/ratify-project/ratify/experimental/proto/v1/verifier"; import "common.proto"; import "google/protobuf/struct.proto"; diff --git a/go.mod b/go.mod index a421ac098..40c95d81c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module github.com/deislabs/ratify +module github.com/ratify-project/ratify -go 1.21 +go 1.22.5 // Accidentally published prior to 1.0.0 release retract ( @@ -10,52 +10,54 @@ retract ( require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 + 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.24.1 - github.com/aws/aws-sdk-go-v2/config v1.26.6 - github.com/aws/aws-sdk-go-v2/credentials v1.16.16 - github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 - github.com/cespare/xxhash/v2 v2.2.0 + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/config v1.27.33 + github.com/aws/aws-sdk-go-v2/credentials v1.17.32 + 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 github.com/dgraph-io/ristretto v0.1.1 github.com/distribution/reference v0.5.0 - github.com/docker/cli v24.0.9+incompatible + github.com/docker/cli v27.1.2+incompatible github.com/docker/distribution v2.8.3+incompatible github.com/fsnotify/fsnotify v1.7.0 + github.com/go-jose/go-jose/v3 v3.0.3 github.com/golang/protobuf v1.5.4 - github.com/google/go-containerregistry v0.19.1 + github.com/google/go-containerregistry v0.20.2 github.com/gorilla/mux v1.8.1 - github.com/notaryproject/notation-core-go v1.0.2 - github.com/notaryproject/notation-go v1.0.1 + github.com/notaryproject/notation-core-go v1.1.0 + github.com/notaryproject/notation-go v1.2.1 + github.com/notaryproject/notation-plugin-framework-go v1.0.0 github.com/open-policy-agent/cert-controller v0.8.0 github.com/open-policy-agent/frameworks/constraint v0.0.0-20230411224310-3f237e2710fa - github.com/open-policy-agent/opa v0.61.0 + github.com/open-policy-agent/opa v0.63.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 - github.com/owenrumney/go-sarif/v2 v2.3.1 + github.com/owenrumney/go-sarif/v2 v2.3.3 github.com/pkg/errors v0.9.1 - github.com/sigstore/cosign/v2 v2.2.3 - github.com/sigstore/sigstore v1.8.3 + github.com/sigstore/cosign/v2 v2.2.4 + github.com/sigstore/sigstore v1.8.9 github.com/sirupsen/logrus v1.9.3 - github.com/spdx/tools-golang v0.5.3 - github.com/spf13/cobra v1.8.0 + github.com/spdx/tools-golang v0.5.5 + github.com/spf13/cobra v1.8.1 github.com/xlab/treeprint v1.1.0 - go.opentelemetry.io/otel/exporters/prometheus v0.39.0 - go.opentelemetry.io/otel/metric v1.22.0 - go.opentelemetry.io/otel/sdk/metric v0.39.0 - golang.org/x/sync v0.6.0 - google.golang.org/grpc v1.61.2 - google.golang.org/protobuf v1.33.0 - k8s.io/api v0.28.8 - k8s.io/apimachinery v0.28.8 - k8s.io/client-go v0.28.8 - oras.land/oras-go/v2 v2.3.1 + go.opentelemetry.io/otel/exporters/prometheus v0.49.0 + go.opentelemetry.io/otel/metric v1.27.0 + go.opentelemetry.io/otel/sdk/metric v1.27.0 + golang.org/x/sync v0.8.0 + google.golang.org/grpc v1.64.1 + google.golang.org/protobuf v1.34.2 + k8s.io/api v0.28.14 + k8s.io/apimachinery v0.28.14 + k8s.io/client-go v0.28.14 + oras.land/oras-go/v2 v2.5.0 ) require ( - cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect @@ -78,14 +80,15 @@ require ( github.com/alibabacloud-go/tea-xml v1.1.3 // indirect 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.18.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // 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.4 // 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 github.com/clbanning/mxj/v2 v2.7.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect - github.com/coreos/go-oidc/v3 v3.9.0 // indirect + github.com/coreos/go-oidc/v3 v3.11.0 // indirect github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect @@ -94,86 +97,84 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-ldap/ldap/v3 v3.4.6 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect + github.com/go-ldap/ldap/v3 v3.4.8 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/google/go-github/v55 v55.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect github.com/mozillazg/docker-credential-acr-helper v0.3.0 // indirect + github.com/notaryproject/tspclient-go v0.2.0 // indirect github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/segmentio/ksuid v1.0.4 // indirect - github.com/sigstore/fulcio v1.4.3 // indirect - github.com/sigstore/timestamp-authority v1.2.1 // indirect + github.com/sigstore/fulcio v1.4.5 // indirect + 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/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 - github.com/xanzy/go-gitlab v0.96.0 // indirect + github.com/xanzy/go-gitlab v0.102.0 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - go.step.sm/crypto v0.42.1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect - gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect + go.step.sm/crypto v0.44.2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect + gotest.tools/v3 v3.1.0 // indirect sigs.k8s.io/release-utils v0.7.7 // indirect ) require ( - cloud.google.com/go/compute v1.23.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.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.23 + github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect 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.14.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect - github.com/aws/smithy-go v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // 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.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect + github.com/aws/smithy-go v1.20.4 // 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 github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fxamacker/cbor/v2 v2.5.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-chi/chi v4.1.2+incompatible // indirect - github.com/go-logr/logr v1.4.1 + github.com/go-logr/logr v1.4.2 github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.22.0 // indirect - github.com/go-openapi/errors v0.21.0 // indirect - github.com/go-openapi/jsonpointer v0.20.2 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/loads v0.21.5 // indirect - github.com/go-openapi/runtime v0.27.1 // indirect - github.com/go-openapi/spec v0.20.13 // indirect - github.com/go-openapi/strfmt v0.22.0 // indirect - github.com/go-openapi/swag v0.22.9 // indirect - github.com/go-openapi/validate v0.22.4 // indirect + github.com/go-openapi/analysis v0.23.0 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/loads v0.22.0 // indirect + github.com/go-openapi/runtime v0.28.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/validate v0.24.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.1.2 // indirect + github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/certificate-transparency-go v1.1.7 // indirect + github.com/google/certificate-transparency-go v1.1.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 @@ -185,9 +186,9 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect + github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -200,56 +201,55 @@ 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.18.0 - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.15.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect - github.com/sigstore/rekor v1.3.4 // indirect + github.com/sigstore/rekor v1.3.6 github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.18.2 // indirect - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.5 // indirect - github.com/veraison/go-cose v1.2.0 // indirect + github.com/veraison/go-cose v1.2.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 - go.mongodb.org/mongo-driver v1.13.1 // indirect - go.opentelemetry.io/otel v1.22.0 - go.opentelemetry.io/otel/sdk v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.mongodb.org/mongo-driver v1.14.0 // indirect + go.opentelemetry.io/otel v1.27.0 + go.opentelemetry.io/otel/sdk v1.27.0 + go.opentelemetry.io/otel/trace v1.27.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.20.0 - golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect - golang.org/x/text v0.14.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.27.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/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/appengine v1.6.8 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apiextensions-apiserver v0.27.7 // indirect k8s.io/component-base v0.27.7 // indirect - k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/klog/v2 v2.120.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/controller-runtime v0.15.3 @@ -257,8 +257,3 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) - -replace ( - github.com/prometheus/client_golang => github.com/prometheus/client_golang v1.12.2 - k8s.io/apiserver => k8s.io/apiserver v0.22.5 -) diff --git a/go.sum b/go.sum index 460662d2e..bdc1f1bf3 100644 --- a/go.sum +++ b/go.sum @@ -1,61 +1,31 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM= -cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs= +cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e h1:GwCVItFUPxwdsEYnlUcJ6PJxOjTeFFCKOh6QWg4oAzQ= +cuelabs.dev/go/oci/ociregistry v0.0.0-20240314152124-224736b49f2e/go.mod h1:ApHceQLLwcOkCEXM1+DyCXTHEJhNGDpJ2kmV6axsx24= +cuelang.org/go v0.8.1 h1:VFYsxIFSPY5KgSaH1jQ2GxHOrbu6Ga3kEI70yCZwnOg= +cuelang.org/go v0.8.1/go.mod h1:CoDbYolfMms4BhWUlhD+t5ORnihR7wvjcfgyO9lL5FI= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18 h1:rd389Q26LMy03gG4anandGFC2LW/xvjga5GezeeaxQk= -github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230618160516-e936619f9f18/go.mod h1:fgJuSBrJP5qZtKqaMJE0hmhS2tmRH+44IkfZvjtaf1M= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= +github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d/go.mod h1:XNqJ7hv2kY++g8XEHREpi+JqZo3+0l+CH2egBVN4yqM= github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 h1:8+4G8JaejP8Xa6W46PzJEwisNgBXMvFcz78N6zG/ARw= 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.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +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/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= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= @@ -65,8 +35,8 @@ github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/ github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= -github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= -github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= +github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= +github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= @@ -90,7 +60,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzS github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= @@ -101,8 +72,8 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRB github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= -github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= +github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= @@ -152,44 +123,40 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= -github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= -github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= -github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= -github.com/aws/aws-sdk-go-v2/config v1.26.6 h1:Z/7w9bUqlRI0FFQpetVuFYEsjzE3h7fpU6HuGmfPL/o= -github.com/aws/aws-sdk-go-v2/config v1.26.6/go.mod h1:uKU6cnDmYCvJ+pxO9S4cWDb2yWWIH5hra+32hVh1MI4= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8= -github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3 h1:n3GDfwqF2tzEkXlv5cuy4iy7LpKDtqDMcNLfZDu9rls= -github.com/aws/aws-sdk-go-v2/internal/ini v1.7.3/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 h1:y6LX9GUoEA3mO0qpFl1ZQHj1rFyPWVphlzebiSt2tKE= -github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2/go.mod h1:Q0LcmaN/Qr8+4aSBrdrXXePqoX0eOuYpJLbYpilmWnA= -github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 h1:PpbXaecV3sLAS6rjQiaKw4/jyq3Z8gNzmoJupHAoBp0= -github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2/go.mod h1:fUHpGXr4DrXkEDpGAjClPsviWf+Bszeb0daKE0blxv8= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.9 h1:W9PbZAZAEcelhhjb7KuwUtf+Lbc+i7ByYJRuWLlnxyQ= -github.com/aws/aws-sdk-go-v2/service/kms v1.27.9/go.mod h1:2tFmR7fQnOdQlM2ZCEPpFnBIQD1U8wmXmduBgZbOag0= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow= -github.com/aws/aws-sdk-go-v2/service/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= -github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= -github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= -github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= -github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= +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.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= +github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +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.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +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.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/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= @@ -198,20 +165,25 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= +github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70= +github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM= +github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM= +github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd9x/v/OH98qyUA= +github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE= +github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 h1:krfRl01rzPzxSxyLyrChD+U+MzsBXbm0OwYYB67uF+4= github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589/go.mod h1:OuDyvmLnMCwa2ep4Jkm6nyA0ocJuZlGyk2gGseVzERM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -225,15 +197,17 @@ github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUK github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= +github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= -github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= -github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46 h1:2Dx4IHfC1yHWI12AxQDJM1QbRCDfk6M+blLzlZCXdrc= github.com/cyberphone/json-canonicalization v0.0.0-20231011164504-785e29786b46/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= @@ -264,14 +238,10 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= -github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI= +github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -279,22 +249,23 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/proto v1.12.1 h1:6n/Z2pZAnBwuhU66Gs8160B8rrrYKo7h2F2sCOnNceE= +github.com/emicklei/proto v1.12.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= -github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -302,59 +273,56 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= -github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= -github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ= +github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-openapi/analysis v0.22.0 h1:wQ/d07nf78HNj4u+KiSY0sT234IAyePPbMgpUjUJQR0= -github.com/go-openapi/analysis v0.22.0/go.mod h1:acDnkkCI2QxIo8sSIPgmp1wUlRohV7vfGtAIVae73b0= -github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= -github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= -github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= -github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= -github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/loads v0.21.5 h1:jDzF4dSoHw6ZFADCGltDb2lE4F6De7aWSpe+IcsRzT0= -github.com/go-openapi/loads v0.21.5/go.mod h1:PxTsnFBoBe+z89riT+wYt3prmSBP6GDAQh2l9H1Flz8= -github.com/go-openapi/runtime v0.27.1 h1:ae53yaOoh+fx/X5Eaq8cRmavHgDma65XPZuvBqvJYto= -github.com/go-openapi/runtime v0.27.1/go.mod h1:fijeJEiEclyS8BRurYE1DE5TLb9/KZl6eAdbzjsrlLU= -github.com/go-openapi/spec v0.20.13 h1:XJDIN+dLH6vqXgafnl5SUIMnzaChQ6QTo0/UPMbkIaE= -github.com/go-openapi/spec v0.20.13/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= -github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= -github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= -github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= -github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= -github.com/go-openapi/validate v0.22.4 h1:5v3jmMyIPKTR8Lv9syBAIRxG6lY0RqeBPB1LKEijzk8= -github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A= -github.com/go-rod/rod v0.114.7 h1:h4pimzSOUnw7Eo41zdJA788XsawzHjJMyzCE3BrBww0= -github.com/go-rod/rod v0.114.7/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= +github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= +github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= +github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= +github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= +github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg= +github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM= +github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= +github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -365,48 +333,33 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/certificate-transparency-go v1.1.7 h1:IASD+NtgSTJLPdzkthwvAG1ZVbF2WtFg4IvoA68XGSw= -github.com/google/certificate-transparency-go v1.1.7/go.mod h1:FSSBo8fyMVgqptbfF6j5p/XNdgQftAhSmXcIxV9iphE= +github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to= +github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8= github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= @@ -415,18 +368,13 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.19.1 h1:yMQ62Al6/V0Z7CqIrrS1iYoA5/oQCm88DeNujc7C1KY= -github.com/google/go-containerregistry v0.19.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= +github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo= +github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8= github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -434,52 +382,40 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= -github.com/google/trillian v1.5.3 h1:3ioA5p09qz+U9/t2riklZtaQdZclaStp0/eQNfewNRg= -github.com/google/trillian v1.5.3/go.mod h1:p4tcg7eBr7aT6DxrAoILpc3uXNfcuAvZSnQKonVg+Eo= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/trillian v1.6.0 h1:jMBeDBIkINFvS2n6oV5maDqfRlxREAc6CW9QYWQ0qT4= +github.com/google/trillian v1.6.0/go.mod h1:Yu3nIMITzNhhMJEHjAtp6xKiu+H/iHu2Oq5FjV2mCWI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= @@ -488,16 +424,16 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9 github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= -github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api v1.12.2 h1:7YkCTE5Ni90TcmYHDBExdt4WGJxhpzaHqR6uGbQb/rE= +github.com/hashicorp/vault/api v1.12.2/go.mod h1:LSGf1NGT1BnvFFnKVtnvcaLBM2Lz+gJdpL6HUYed8KE= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= @@ -505,10 +441,22 @@ github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= -github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= -github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= +github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -521,15 +469,11 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -539,8 +483,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 h1:WGrKdjHtWC67RX96eTkYD2f53NDHhrq/7robWTAfk4s= -github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491/go.mod h1:o158RFmdEbYyIZmXAbrvmJWesbyxlLKee6X64VPVuOc= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= +github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -549,16 +493,15 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -568,24 +511,29 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mozillazg/docker-credential-acr-helper v0.3.0 h1:DVWFZ3/O8BP6Ue3iS/Olw+G07u1hCq1EOVCDZZjCIBI= github.com/mozillazg/docker-credential-acr-helper v0.3.0/go.mod h1:cZlu3tof523ujmLuiNUb6JsjtHcNA70u1jitrrdnuyA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/notaryproject/notation-core-go v1.0.2 h1:VEt+mbsgdANd9b4jqgmx2C7U0DmwynOuD2Nhxh3bANw= -github.com/notaryproject/notation-core-go v1.0.2/go.mod h1:2HkQzUwg08B3x9oVIztHsEh7Vil2Rj+tYgxH+JObLX4= -github.com/notaryproject/notation-go v1.0.1 h1:D3fqG3eaBKVESRySV/Tg//MyTg2Q1nTKPh/t2q9LpSw= -github.com/notaryproject/notation-go v1.0.1/go.mod h1:VonyZsbocRQQNIDq/VPV5jKJOQwDH3gvfK4cXNpUA0U= +github.com/notaryproject/notation-core-go v1.1.0 h1:xCybcONOKcCyPNihJUSa+jRNsyQFNkrk0eJVVs1kWeg= +github.com/notaryproject/notation-core-go v1.1.0/go.mod h1:+6AOh41JPrnVLbW/19SJqdhVHwKgIINBO/np0e7nXJA= +github.com/notaryproject/notation-go v1.2.1 h1:fbCMBcvg1xttrisd5CyM60QDectGYYF701Us0M3cKN8= +github.com/notaryproject/notation-go v1.2.1/go.mod h1:re9V+TfuNRaUq5e3NuNcCJN53++sL2KbnJrjGyOUpgE= +github.com/notaryproject/notation-plugin-framework-go v1.0.0 h1:6Qzr7DGXoCgXEQN+1gTZWuJAZvxh3p8Lryjn5FaLzi4= +github.com/notaryproject/notation-plugin-framework-go v1.0.0/go.mod h1:RqWSrTOtEASCrGOEffq0n8pSg2KOgKYiWqFWczRSics= +github.com/notaryproject/tspclient-go v0.2.0 h1:g/KpQGmyk/h7j60irIRG1mfWnibNOzJ8WhLqAzuiQAQ= +github.com/notaryproject/tspclient-go v0.2.0/go.mod h1:LGyA/6Kwd2FlM0uk8Vc5il3j0CddbWSHBj/4kxQDbjs= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM= +github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -604,8 +552,8 @@ github.com/open-policy-agent/cert-controller v0.8.0 h1:pao3WCLsKGz5dSWSlNUFrNFQd github.com/open-policy-agent/cert-controller v0.8.0/go.mod h1:alotCQRwX4M6VEwEgO53FB6nGLSlvah6L0pWxSRslIk= github.com/open-policy-agent/frameworks/constraint v0.0.0-20230411224310-3f237e2710fa h1:1r6gnPhbsswSIem/Fa11fKo/MhjijzvqSxWIu+3HQeY= github.com/open-policy-agent/frameworks/constraint v0.0.0-20230411224310-3f237e2710fa/go.mod h1:nrGEsNJ9LyQa68eqwV6snwCc7pbkvwUJLPZlq6zz6Fs= -github.com/open-policy-agent/opa v0.61.0 h1:nhncQ2CAYtQTV/SMBhDDPsCpCQsUW+zO/1j+T5V7oZg= -github.com/open-policy-agent/opa v0.61.0/go.mod h1:7OUuzJnsS9yHf8lw0ApfcbrnaRG1EkN3J2fuuqi4G/E= +github.com/open-policy-agent/opa v0.63.0 h1:ztNNste1v8kH0/vJMJNquE45lRvqwrM5mY9Ctr9xIXw= +github.com/open-policy-agent/opa v0.63.0/go.mod h1:9VQPqEfoB2N//AToTxzZ1pVTVPUoF2Mhd64szzjWPpU= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -613,8 +561,10 @@ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2sz github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/owenrumney/go-sarif/v2 v2.3.1 h1:77opmuqxQZE1UF6TylFz5XllVEI72WijgwpwNw4JTmY= -github.com/owenrumney/go-sarif/v2 v2.3.1/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU= +github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w= +github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= +github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -625,23 +575,21 @@ 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.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +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= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/procfs v0.15.0 h1:A82kmvXJq2jTu5YUhSGNlYoxh85zLnKgPz4bMZgI5Ek= +github.com/prometheus/procfs v0.15.0/go.mod h1:Y0RJ/Y5g5wJpkTisOtqwDSo4HwhGmLB4VQSw2sQJLHk= +github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf h1:014O62zIzQwvoD7Ekj3ePDF5bv9Xxy0w6AZk0qYbjUk= +github.com/protocolbuffers/txtpbfmt v0.0.0-20231025115547-084445ff1adf/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -651,32 +599,32 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A= github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk= -github.com/sassoftware/relic/v7 v7.6.1 h1:O5s8ewCgq5QYNpv45dK4u6IpBmDM9RIcsbf/G1uXepQ= -github.com/sassoftware/relic/v7 v7.6.1/go.mod h1:NxwtWxWxlUa9as2qZi635Ye6bBT/tGnMALLq7dSfOOU= +github.com/sassoftware/relic/v7 v7.6.2 h1:rS44Lbv9G9eXsukknS4mSjIAuuX+lMq/FnStgmZlUv4= +github.com/sassoftware/relic/v7 v7.6.2/go.mod h1:kjmP0IBVkJZ6gXeAu35/KCEfca//+PKM6vTAsyDPY+k= github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbmfHkLguCE9laoZCUzEEpIZXA= github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= -github.com/sigstore/cosign/v2 v2.2.3 h1:WX7yawI+EXu9h7S5bZsfYCbB9XW6Jc43ctKy/NoOSiA= -github.com/sigstore/cosign/v2 v2.2.3/go.mod h1:WpMn4MBt0cI23GdHsePwO4NxhX1FOz1ITGB3ALUjFaI= -github.com/sigstore/fulcio v1.4.3 h1:9JcUCZjjVhRF9fmhVuz6i1RyhCc/EGCD7MOl+iqCJLQ= -github.com/sigstore/fulcio v1.4.3/go.mod h1:BQPWo7cfxmJwgaHlphUHUpFkp5+YxeJes82oo39m5og= -github.com/sigstore/rekor v1.3.4 h1:RGIia1iOZU7fOiiP2UY/WFYhhp50S5aUm7YrM8aiA6E= -github.com/sigstore/rekor v1.3.4/go.mod h1:1GubPVO2yO+K0m0wt/3SHFqnilr/hWbsjSOe7Vzxrlg= -github.com/sigstore/sigstore v1.8.3 h1:G7LVXqL+ekgYtYdksBks9B38dPoIsbscjQJX/MGWkA4= -github.com/sigstore/sigstore v1.8.3/go.mod h1:mqbTEariiGA94cn6G3xnDiV6BD8eSLdL/eA7bvJ0fVs= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1 h1:rEDdUefulkIQaMJyzLwtgPDLNXBIltBABiFYfb0YmgQ= -github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1/go.mod h1:RCdYCc1IxCYWzh2IdzdA6Yf7JIY0cMRqH08fpQYechw= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1 h1:DvRWG99QGWZC5mp42SEde2Xke/Q384Idnj2da7yB+Mk= -github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.1/go.mod h1:s13mo3a0UCQS3+PAUUZfvKe48sMDMsHk2GE1b2YfPcU= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1 h1:lwdRsJv1UbBemuk7w5YfXAQilQxMoFevrzamdPbG0wY= -github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.1/go.mod h1:2OaSQ80EcdyVRSQ3T4d1lsc6Scopblsiq8U2AEk5K1A= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1 h1:9Ki0qudKpc1FQdef7xHO2bkLyTuw+qNUpWRzjBEmF4c= -github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.1/go.mod h1:nhIgyu4YwwNgalIwTGsoAzam16jjAn3ADRSWKbWPwGI= -github.com/sigstore/timestamp-authority v1.2.1 h1:j9RmqSAdvKgSofeltPO4x7d+1M3AXaROBzUJ+AA7L5Q= -github.com/sigstore/timestamp-authority v1.2.1/go.mod h1:Ce+vWWEf0QaKLY2u6mpwEJbmYXEVeOfUk4fQ69kE6ck= +github.com/sigstore/cosign/v2 v2.2.4 h1:iY4vtEacmu2hkNj1Fh+8EBqBwKs2DHM27/lbNWDFJro= +github.com/sigstore/cosign/v2 v2.2.4/go.mod h1:JZlRD2uaEjVAvZ1XJ3QkkZJhTqSDVtLaet+C/TMR81Y= +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/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= +github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.3/go.mod h1:G4+I83FILPX6MtnoaUdmv/bRGEVtR3JdLeJa/kXdk/0= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3 h1:vDl2fqPT0h3D/k6NZPlqnKFd1tz3335wm39qjvpZNJc= +github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.3/go.mod h1:9uOJXbXEXj+M6QjMKH5PaL5WDMu43rHfbIMgXzA8eKI= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3 h1:h9G8j+Ds21zqqulDbA/R/ft64oQQIyp8S7wJYABYSlg= +github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.3/go.mod h1:zgCeHOuqF6k7A7TTEvftcA9V3FRzB7mrPtHOhXAQBnc= +github.com/sigstore/timestamp-authority v1.2.2 h1:X4qyutnCQqJ0apMewFyx+3t7Tws00JQ/JonBiu3QvLE= +github.com/sigstore/timestamp-authority v1.2.2/go.mod h1:nEah4Eq4wpliDjlY342rXclGSO7Kb9hoRrl9tqLW13A= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -690,22 +638,26 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= -github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQraY= -github.com/spdx/tools-golang v0.5.3/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spiffe/go-spiffe/v2 v2.2.0 h1:9Vf06UsvsDbLYK/zJ4sYsIsHmMFknUD+feA7IYoWMQY= +github.com/spiffe/go-spiffe/v2 v2.2.0/go.mod h1:Urzb779b3+IwDJD2ZbN8fVl3Aa8G4N/PiUe6iXC0XxU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/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= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -714,9 +666,11 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= @@ -736,17 +690,14 @@ github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/veraison/go-cose v1.2.0 h1:Ok0Hr3GMAf8K/1NB4sV65QGgCiukG1w1QD+H5tmt0Ow= -github.com/veraison/go-cose v1.2.0/go.mod h1:7ziE85vSq4ScFTg6wyoMXjucIGOf4JkFEZi/an96Ct4= +github.com/veraison/go-cose v1.2.1 h1:Gj4x20D0YP79J2+cK3anjGEMwIkg2xX+TKVVGUXwNAc= +github.com/veraison/go-cose v1.2.1/go.mod h1:t6V8WJzHm1PD5HNsuDjW3KLv577uWb6UTzbZGvdQHD8= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xanzy/go-gitlab v0.96.0 h1:LGkZ+wSNMRtHIBaYE4Hq3dZVjprwHv3Y1+rhKU3WETs= -github.com/xanzy/go-gitlab v0.96.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4= +github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -758,70 +709,62 @@ github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= -github.com/ysmood/got v0.34.1 h1:IrV2uWLs45VXNvZqhJ6g2nIhY+pgIG1CUoOcqfXFl1s= -github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM= +github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= +github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= -github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= -github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= +github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zalando/go-keyring v0.2.2 h1:f0xmpYiSrHtSNAVgwip93Cg8tuF45HJM6rHq/A5RI/4= -github.com/zalando/go-keyring v0.2.2/go.mod h1:sI3evg9Wvpw3+n4SqplGSJUMwtDeROfD4nsFz4z9PG0= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= -go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= +go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= -go.opentelemetry.io/otel/exporters/prometheus v0.39.0 h1:whAaiHxOatgtKd+w0dOi//1KUxj3KoPINZdtDaDj3IA= -go.opentelemetry.io/otel/exporters/prometheus v0.39.0/go.mod h1:4jo5Q4CROlCpSPsXLhymi+LYrDXd2ObU5wbKayfZs7Y= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= -go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.step.sm/crypto v0.42.1 h1:OmwHm3GJO8S4VGWL3k4+I+Q4P/F2s+j8msvTyGnh1Vg= -go.step.sm/crypto v0.42.1/go.mod h1:yNcTLFQBnYCA75fC5bklBoTAT7y0dRZsB1TkinB8JMs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/exporters/prometheus v0.49.0 h1:Er5I1g/YhfYv9Affk9nJLfH/+qCCVVg1f2R9AbJfqDQ= +go.opentelemetry.io/otel/exporters/prometheus v0.49.0/go.mod h1:KfQ1wpjf3zsHjzP149P4LyAwWRupc6c7t1ZJ9eXpKQM= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.step.sm/crypto v0.44.2 h1:t3p3uQ7raP2jp2ha9P6xkQF85TJZh+87xmjSLaib+jk= +go.step.sm/crypto v0.44.2/go.mod h1:x1439EnFhadzhkuaGX7sz03LEMQ+jV4gRamf5LCZJQQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -829,85 +772,45 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +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.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w= -golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -918,69 +821,41 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 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.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -993,9 +868,11 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +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/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= @@ -1003,80 +880,43 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1084,105 +924,41 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.159.0 h1:fVTj+7HHiUYz4JEZCHHoRIeQX7h5FMzrA2RF/DzDdbs= -google.golang.org/api v0.159.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= +google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= +google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7 h1:ImUcDPHjTrAqNhlOkSocDLfG9rrNHH7w7uoKWPaWZ8s= +google.golang.org/genproto v0.0.0-20240311173647-c811ad7063a7/go.mod h1:/3XmxOjePkvmKrHuBy4zNFw7IzxJXtAgdpXi8Ll990U= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.61.2 h1:TzJay21lXCf7BiNFKl7mSskt5DlkKAumAYTs52SpJeo= -google.golang.org/grpc v1.61.2/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 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= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -1199,38 +975,30 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= +gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.28.8 h1:G0/G7yX1puRAcon/+XPLsKXZ9A5L7Ds6oKbDIe027xw= -k8s.io/api v0.28.8/go.mod h1:rU8f1t9CNUAXlk/1j/wMJ7XnaxkR1g1AlZGQAOOL+sw= +k8s.io/api v0.28.14 h1:7DXeMrQq+BJI6H7WtSMC8l1gM4QZWtWN65UbN+qZ9Uc= +k8s.io/api v0.28.14/go.mod h1:ROk/G6/7IZf14AL1WkpZdq//5khE1EtLNxkcEpSXNFM= k8s.io/apiextensions-apiserver v0.27.7 h1:YqIOwZAUokzxJIjunmUd4zS1v3JhK34EPXn+pP0/bsU= k8s.io/apiextensions-apiserver v0.27.7/go.mod h1:x0p+b5a955lfPz9gaDeBy43obM12s+N9dNHK6+dUL+g= -k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ= -k8s.io/apimachinery v0.28.8/go.mod h1:cBnwIM3fXoRo28SqbV/Ihxf/iviw85KyXOrzxvZQ83U= -k8s.io/client-go v0.28.8 h1:TE59Tjd87WKvS2FPBTfIKLFX0nQJ4SSHsnDo5IHjgOw= -k8s.io/client-go v0.28.8/go.mod h1:uDVQ/rPzWpWIy40c6lZ4mUwaEvRWGnpoqSO4FM65P3o= +k8s.io/apimachinery v0.28.14 h1:n2l8jNNOmUUDXpa8ljHCEUSeIChby1BKyqoL0AtpmGw= +k8s.io/apimachinery v0.28.14/go.mod h1:zUG757HaKs6Dc3iGtKjzIpBfqTM4yiRsEe3/E7NX15o= +k8s.io/client-go v0.28.14 h1:wfPRgz07MvLMxcHfN8kAc4Qcwduc4My25A3CBU7OqBQ= +k8s.io/client-go v0.28.14/go.mod h1:HGfdb7BqkX4hRpNyVLHNQKWDU03W6a38LfIHD7QGJpI= k8s.io/component-base v0.27.7 h1:kngM58HR9W9Nqpv7e4rpdRyWnKl/ABpUhLAZ+HoliMs= k8s.io/component-base v0.27.7/go.mod h1:YGjlCVL1oeKvG3HSciyPHFh+LCjIEqsxz4BDR3cfHRs= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-aggregator v0.27.2 h1:jfHoPip+qN/fn3OcrYs8/xMuVYvkJHKo0H0DYciqdns= k8s.io/kube-aggregator v0.27.2/go.mod h1:mwrTt4ESjQ7A6847biwohgZWn8P/KzSFHegEScbSGY4= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go/v2 v2.3.1 h1:lUC6q8RkeRReANEERLfH86iwGn55lbSWP20egdFHVec= -oras.land/oras-go/v2 v2.3.1/go.mod h1:5AQXVEu1X/FKp1F9DMOb5ZItZBOa0y5dha0yCm4NR9c= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= +oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= sigs.k8s.io/controller-runtime v0.15.3 h1:L+t5heIaI3zeejoIyyvLQs5vTVu/67IU2FfisVzFlBc= sigs.k8s.io/controller-runtime v0.15.3/go.mod h1:kp4jckA4vTx281S/0Yk2LFEEQe67mjg+ev/yknv47Ds= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= @@ -1239,8 +1007,7 @@ sigs.k8s.io/release-utils v0.7.7 h1:JKDOvhCk6zW8ipEOkpTGDH/mW3TI+XqtPp16aaQ79FU= sigs.k8s.io/release-utils v0.7.7/go.mod h1:iU7DGVNi3umZJ8q6aHyUFzsDUIaYwNnNKGHo3YE5E3s= sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= -software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/helmfile.yaml b/helmfile.yaml index e5eb0b660..e216de2ef 100644 --- a/helmfile.yaml +++ b/helmfile.yaml @@ -2,14 +2,14 @@ repositories: - name: gatekeeper url: https://open-policy-agent.github.io/gatekeeper/charts - name: ratify - url: https://deislabs.github.io/ratify - + url: https://ratify-project.github.io/ratify + releases: - name: gatekeeper namespace: gatekeeper-system createNamespace: true chart: gatekeeper/gatekeeper - version: 3.14.0 + version: 3.17.0 wait: true set: - name: enableExternalData @@ -23,7 +23,7 @@ releases: - name: ratify namespace: gatekeeper-system chart: ratify/ratify - version: 1.12.0 # Make sure this matches Chart.yaml + version: 1.14.0 # Make sure this matches Chart.yaml wait: true needs: - gatekeeper @@ -33,14 +33,14 @@ releases: command: "bash" args: - "-c" - - "kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml && kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "kubectl apply -f https://ratify-project.github.io/ratify/library/default/template.yaml && kubectl apply -f https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - events: ["postuninstall"] showlogs: true command: "kubectl" args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/template.yaml" + - "https://ratify-project.github.io/ratify/library/default/template.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -48,7 +48,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -60,6 +60,11 @@ releases: - "verifiers.config.ratify.deislabs.io" - "certificatestores.config.ratify.deislabs.io" - "policies.config.ratify.deislabs.io" + - "keymanagementproviders.config.ratify.deislabs.io" + - "namespacedkeymanagementproviders.config.ratify.deislabs.io" + - "namespacedpolicies.config.ratify.deislabs.io" + - "namespacedstores.config.ratify.deislabs.io" + - "namespacedverifiers.config.ratify.deislabs.io" - events: ["postuninstall"] showlogs: true command: "kubectl" @@ -70,7 +75,7 @@ releases: - "-n" - "gatekeeper-system" set: - - name: notationCert + - name: notationCerts[0] value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt") | quote }} - name: featureFlags.RATIFY_CERT_ROTATION value: true diff --git a/high-availability.helmfile.yaml b/high-availability.helmfile.yaml index 8eb927afa..5698e2281 100644 --- a/high-availability.helmfile.yaml +++ b/high-availability.helmfile.yaml @@ -1,20 +1,38 @@ repositories: + - name: gatekeeper + url: https://open-policy-agent.github.io/gatekeeper/charts - name: dapr url: https://dapr.github.io/helm-charts/ - name: bitnami url: https://charts.bitnami.com/bitnami - name: ratify - url: https://deislabs.github.io/ratify + url: https://ratify-project.github.io/ratify releases: - name: dapr namespace: dapr-system createNamespace: true chart: dapr/dapr - version: 1.11.1 + version: 1.13.2 wait: true + - name: gatekeeper + namespace: gatekeeper-system + createNamespace: true + chart: gatekeeper/gatekeeper + version: 3.17.0 + wait: true + set: + - name: enableExternalData + value: true + - name: validatingWebhookTimeoutSeconds + value: 5 + - name: mutatingWebhookTimeoutSeconds + value: 2 + - name: externaldataProviderResponseCacheTTL + value: 10s - name: redis namespace: gatekeeper-system + createNamespace: true chart: bitnami/redis version: 17.11.6 wait: true @@ -32,11 +50,12 @@ releases: - name: ratify namespace: gatekeeper-system chart: ratify/ratify - version: 1.12.0 # Make sure this matches Chart.yaml + version: 1.14.0 # Make sure this matches Chart.yaml wait: true needs: - dapr-system/dapr - gatekeeper-system/redis + - gatekeeper-system/gatekeeper hooks: - events: ["presync"] showlogs: true @@ -53,6 +72,12 @@ releases: - "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/dapr/dapr-redis.yaml" - "-n" - "gatekeeper-system" + - events: ["presync"] + showlogs: true + command: "bash" + args: + - "-c" + - "kubectl apply -f https://ratify-project.github.io/ratify/library/default/template.yaml && kubectl apply -f https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - events: ["postuninstall"] showlogs: true command: "kubectl" @@ -79,7 +104,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/template.yaml" + - "https://ratify-project.github.io/ratify/library/default/template.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -87,7 +112,7 @@ releases: args: - "delete" - "-f" - - "https://deislabs.github.io/ratify/library/default/samples/constraint.yaml" + - "https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml" - "--ignore-not-found=true" - events: ["postuninstall"] showlogs: true @@ -99,6 +124,10 @@ releases: - "verifiers.config.ratify.deislabs.io" - "certificatestores.config.ratify.deislabs.io" - "policies.config.ratify.deislabs.io" + - "namespacedkeymanagementproviders.config.ratify.deislabs.io" + - "namespacedpolicies.config.ratify.deislabs.io" + - "namespacedstores.config.ratify.deislabs.io" + - "namespacedverifiers.config.ratify.deislabs.io" - events: ["postuninstall"] showlogs: true command: "kubectl" @@ -115,7 +144,7 @@ releases: value: true - name: logger.level value: debug - - name: notationCert + - name: notationCerts[0] value: {{ exec "curl" (list "-sSL" "https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt") | quote }} - name: replicaCount value: 2 diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile index 678b52351..eba9625c0 100644 --- a/httpserver/Dockerfile +++ b/httpserver/Dockerfile @@ -11,10 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -ARG BUILDERIMAGE="golang:1.21" -ARG BASEIMAGE="gcr.io/distroless/static:nonroot" - -FROM --platform=$BUILDPLATFORM $BUILDERIMAGE as builder +FROM --platform=$BUILDPLATFORM golang:1.22@sha256:4594271250150c1a322ed749abfd218e1a8c6eb1ade90872e325a664412e2037 as builder ARG TARGETPLATFORM ARG TARGETOS @@ -44,8 +41,8 @@ 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 $BASEIMAGE -LABEL org.opencontainers.image.source https://github.com/deislabs/ratify +FROM gcr.io/distroless/static:nonroot@sha256:42d15c647a762d3ce3a67eab394220f5268915d6ddba9006871e16e4698c3a24 +LABEL org.opencontainers.image.source https://github.com/ratify-project/ratify ARG RATIFY_FOLDER=$HOME/.ratify/ diff --git a/httpserver/context.go b/httpserver/context.go index d7e2f5068..a0c5222b3 100644 --- a/httpserver/context.go +++ b/httpserver/context.go @@ -19,8 +19,8 @@ import ( "context" "net/http" - "github.com/deislabs/ratify/utils" "github.com/docker/distribution/registry/api/errcode" + "github.com/ratify-project/ratify/utils" "github.com/sirupsen/logrus" ) diff --git a/httpserver/handlers.go b/httpserver/handlers.go index 57c999bad..7c4596fb1 100644 --- a/httpserver/handlers.go +++ b/httpserver/handlers.go @@ -24,16 +24,16 @@ import ( "sync" "time" - "github.com/deislabs/ratify/errors" - ctxUtils "github.com/deislabs/ratify/internal/context" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/executor" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/metrics" - "github.com/deislabs/ratify/pkg/referrerstore" - pkgUtils "github.com/deislabs/ratify/pkg/utils" - "github.com/deislabs/ratify/utils" + "github.com/ratify-project/ratify/errors" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/executor" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/metrics" + "github.com/ratify-project/ratify/pkg/referrerstore" + pkgUtils "github.com/ratify-project/ratify/pkg/utils" + "github.com/ratify-project/ratify/utils" "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" ) @@ -83,11 +83,6 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http results = append(results, returnItem) mu.Unlock() }() - if err := server.validateComponents(verifyComponents); err != nil { - logger.GetLogger(ctx, server.LogOption).Error(err) - returnItem.Error = err.Error() - return - } requestKey, err := pkgUtils.ParseRequestKey(key) if err != nil { returnItem.Error = err.Error() @@ -100,6 +95,12 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http } ctx = ctxUtils.SetContextWithNamespace(ctx, requestKey.Namespace) + if err := server.validateComponents(ctx, verifyComponents); err != nil { + logger.GetLogger(ctx, server.LogOption).Error(err) + returnItem.Error = err.Error() + return + } + if subjectReference.Digest.String() == "" { logger.GetLogger(ctx, server.LogOption).Warn("Digest should be used instead of tagged reference. The resolved digest may not point to the same signed artifact, since tags are mutable.") } @@ -129,7 +130,7 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http verifyParameters := executor.VerifyParameters{ Subject: resolvedSubjectReference, } - if result, err = server.GetExecutor().VerifySubject(ctx, verifyParameters); err != nil { + if result, err = server.GetExecutor(ctx).VerifySubject(ctx, verifyParameters); err != nil { returnItem.Error = errors.ErrorCodeExecutorFailure.WithError(err).WithComponentType(errors.Executor).Error() return } @@ -140,12 +141,12 @@ func (server *Server) verify(ctx context.Context, w http.ResponseWriter, r *http logger.GetLogger(ctx, server.LogOption).Warnf("unable to insert cache entry for subject %v", resolvedSubjectReference) } } - - if res, err := json.MarshalIndent(result, "", " "); err == nil { - logger.GetLogger(ctx, server.LogOption).Infof("verify result for subject %s: %s", resolvedSubjectReference, string(res)) - } } - returnItem.Value = fromVerifyResult(result, server.GetExecutor().PolicyEnforcer.GetPolicyType(ctx)) + verificationResponse := fromVerifyResult(ctx, result, server.GetExecutor(ctx).PolicyEnforcer.GetPolicyType(ctx)) + returnItem.Value = verificationResponse + if res, err := json.MarshalIndent(verificationResponse, "", " "); err == nil { + logger.GetLogger(ctx, server.LogOption).Infof("verification response for subject %s: \n%s", resolvedSubjectReference, string(res)) + } logger.GetLogger(ctx, server.LogOption).Debugf("verification: execution time for image %s: %dms", resolvedSubjectReference, time.Since(routineStartTime).Milliseconds()) }(utils.SanitizeString(key), ctx) } @@ -192,11 +193,6 @@ func (server *Server) mutate(ctx context.Context, w http.ResponseWriter, r *http results = append(results, returnItem) mu.Unlock() }() - if err := server.validateComponents(mutateComponents); err != nil { - logger.GetLogger(ctx, server.LogOption).Error(err) - returnItem.Error = err.Error() - return - } requestKey, err := pkgUtils.ParseRequestKey(image) if err != nil { returnItem.Error = err.Error() @@ -209,11 +205,18 @@ func (server *Server) mutate(ctx context.Context, w http.ResponseWriter, r *http returnItem.Error = err.Error() return } + ctx = ctxUtils.SetContextWithNamespace(ctx, requestKey.Namespace) + if err := server.validateComponents(ctx, mutateComponents); err != nil { + logger.GetLogger(ctx, server.LogOption).Error(err) + returnItem.Error = err.Error() + return + } + if parsedReference.Digest == "" { var selectedStore referrerstore.ReferrerStore - for _, store := range server.GetExecutor().ReferrerStores { + for _, store := range server.GetExecutor(ctx).ReferrerStores { if store.Name() == server.MutationStoreName { selectedStore = store break @@ -243,20 +246,20 @@ func (server *Server) mutate(ctx context.Context, w http.ResponseWriter, r *http return sendResponse(&results, "", w, http.StatusOK, true) } -func (server *Server) validateComponents(handlerComponents string) error { +func (server *Server) validateComponents(ctx context.Context, handlerComponents string) error { if handlerComponents == mutateComponents { - if len(server.GetExecutor().ReferrerStores) == 0 { + if len(server.GetExecutor(ctx).ReferrerStores) == 0 { return errors.ErrorCodeConfigInvalid.WithComponentType(errors.ReferrerStore).WithDetail("referrer store config should have at least one store") } } if handlerComponents == verifyComponents { - if len(server.GetExecutor().ReferrerStores) == 0 { + if len(server.GetExecutor(ctx).ReferrerStores) == 0 { return errors.ErrorCodeConfigInvalid.WithComponentType(errors.ReferrerStore).WithDetail("referrer store config should have at least one store") } - if server.GetExecutor().PolicyEnforcer == nil { - return errors.ErrorCodeConfigInvalid.WithComponentType(errors.PolicyProvider).WithDetail("policy provider config must be specified") + if server.GetExecutor(ctx).PolicyEnforcer == nil { + return errors.ErrorCodeConfigInvalid.WithComponentType(errors.PolicyProvider).WithDetail("policy provider config is not provided") } - if len(server.GetExecutor().Verifiers) == 0 { + if len(server.GetExecutor(ctx).Verifiers) == 0 { return errors.ErrorCodeConfigInvalid.WithComponentType(errors.Verifier).WithDetail("verifiers config should have at least one verifier") } } @@ -284,7 +287,7 @@ func sendResponse(results *[]externaldata.Item, systemErr string, w http.Respons } func processTimeout(h ContextHandler, duration time.Duration, isMutation bool) ContextHandler { - return func(handlerContext context.Context, w http.ResponseWriter, r *http.Request) error { + return func(_ context.Context, w http.ResponseWriter, r *http.Request) error { ctx, cancel := context.WithTimeout(r.Context(), duration) defer cancel() diff --git a/httpserver/server.go b/httpserver/server.go index c52daf19d..782e3c83d 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -29,9 +29,9 @@ import ( "syscall" "time" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/metrics" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/metrics" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -42,6 +42,7 @@ const ( certName = "tls.crt" keyName = "tls.key" readHeaderTimeout = 5 * time.Second + idleTimeout = 90 * time.Second defaultMutationReferrerStoreName = "oras" DefaultMetricsType = "prometheus" @@ -135,6 +136,7 @@ func (server *Server) Run(certRotatorReady chan struct{}) error { Addr: server.Address, Handler: server.Router, ReadHeaderTimeout: readHeaderTimeout, + IdleTimeout: idleTimeout, } if server.CertDirectory != "" { @@ -178,13 +180,13 @@ func (server *Server) registerHandlers() error { if err != nil { return err } - server.register(http.MethodPost, verifyPath, processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false)) + server.register(http.MethodPost, verifyPath, processTimeout(server.verify, server.GetExecutor(server.Context).GetVerifyRequestTimeout(), false)) mutatePath, err := url.JoinPath(ServerRootURL, "mutate") if err != nil { return err } - server.register(http.MethodPost, mutatePath, processTimeout(server.mutate, server.GetExecutor().GetMutationRequestTimeout(), true)) + server.register(http.MethodPost, mutatePath, processTimeout(server.mutate, server.GetExecutor(server.Context).GetMutationRequestTimeout(), true)) return nil } diff --git a/httpserver/server_test.go b/httpserver/server_test.go index 9a4587287..5c84cfa98 100644 --- a/httpserver/server_test.go +++ b/httpserver/server_test.go @@ -28,25 +28,25 @@ import ( "testing" "time" - ratifyerrors "github.com/deislabs/ratify/errors" - exconfig "github.com/deislabs/ratify/pkg/executor/config" - "github.com/deislabs/ratify/pkg/executor/core" - "github.com/deislabs/ratify/pkg/ocispecs" - config "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" + ratifyerrors "github.com/ratify-project/ratify/errors" + exconfig "github.com/ratify-project/ratify/pkg/executor/config" + "github.com/ratify-project/ratify/pkg/executor/core" + "github.com/ratify-project/ratify/pkg/ocispecs" + config "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" "github.com/sirupsen/logrus" - "github.com/deislabs/ratify/pkg/policyprovider/types" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/mocks" - "github.com/deislabs/ratify/pkg/verifier" "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/policyprovider/types" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier" ) const testArtifactType string = "test-type1" const testImageNameTagged string = "localhost:5000/net-monitor:v1" -func testGetExecutor() *core.Executor { +func testGetExecutor(context.Context) *core.Executor { return &core.Executor{ Verifiers: []verifier.ReferenceVerifier{}, ReferrerStores: []referrerstore.ReferrerStore{}, @@ -126,7 +126,7 @@ func TestServer_Timeout_Failed(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { time.Sleep(time.Duration(timeoutDuration) * time.Second) return true }, @@ -138,7 +138,7 @@ func TestServer_Timeout_Failed(t *testing.T) { Verifiers: []verifier.ReferenceVerifier{ver}, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -151,7 +151,7 @@ func TestServer_Timeout_Failed(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -194,7 +194,7 @@ func TestServer_MultipleSubjects_Success(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -209,7 +209,7 @@ func TestServer_MultipleSubjects_Success(t *testing.T) { }, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -222,7 +222,7 @@ func TestServer_MultipleSubjects_Success(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -268,7 +268,7 @@ func TestServer_Mutation_Success(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { time.Sleep(time.Duration(timeoutDuration) * time.Second) return true }, @@ -280,7 +280,7 @@ func TestServer_Mutation_Success(t *testing.T) { Verifiers: []verifier.ReferenceVerifier{ver}, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -294,7 +294,7 @@ func TestServer_Mutation_Success(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.mutate, server.GetExecutor().GetMutationRequestTimeout(), true), + handler: processTimeout(server.mutate, server.GetExecutor(nil).GetMutationRequestTimeout(), true), } handler.ServeHTTP(responseRecorder, request) @@ -344,7 +344,7 @@ func TestServer_Mutation_ReferrerStoreConfigInvalid_Failure(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { time.Sleep(time.Duration(timeoutDuration) * time.Second) return true }, @@ -356,7 +356,7 @@ func TestServer_Mutation_ReferrerStoreConfigInvalid_Failure(t *testing.T) { Verifiers: []verifier.ReferenceVerifier{ver}, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -370,7 +370,7 @@ func TestServer_Mutation_ReferrerStoreConfigInvalid_Failure(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.mutate, server.GetExecutor().GetMutationRequestTimeout(), true), + handler: processTimeout(server.mutate, server.GetExecutor(nil).GetMutationRequestTimeout(), true), } handler.ServeHTTP(responseRecorder, request) @@ -423,7 +423,7 @@ func TestServer_MultipleRequestsForSameSubject_Success(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -439,7 +439,7 @@ func TestServer_MultipleRequestsForSameSubject_Success(t *testing.T) { }, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -452,7 +452,7 @@ func TestServer_MultipleRequestsForSameSubject_Success(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -491,7 +491,7 @@ func TestServer_Verify_ParseReference_Failure(t *testing.T) { }, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -504,7 +504,7 @@ func TestServer_Verify_ParseReference_Failure(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -552,7 +552,7 @@ func TestServer_Verify_PolicyEnforcerConfigInvalid_Failure(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { time.Sleep(time.Duration(timeoutDuration) * time.Second) return true }, @@ -564,7 +564,7 @@ func TestServer_Verify_PolicyEnforcerConfigInvalid_Failure(t *testing.T) { Verifiers: []verifier.ReferenceVerifier{ver}, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -578,7 +578,7 @@ func TestServer_Verify_PolicyEnforcerConfigInvalid_Failure(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -592,7 +592,7 @@ func TestServer_Verify_PolicyEnforcerConfigInvalid_Failure(t *testing.T) { t.Fatalf("failed to decode response body: %v", err) } retFirstErr := respBody.Response.Items[0].Error - expectedErr := ratifyerrors.ErrorCodeConfigInvalid.WithComponentType(ratifyerrors.PolicyProvider).WithDetail("policy provider config must be specified").Error() + expectedErr := ratifyerrors.ErrorCodeConfigInvalid.WithComponentType(ratifyerrors.PolicyProvider).WithDetail("policy provider config is not provided").Error() if retFirstErr != expectedErr { t.Fatalf("Expected first subject error to be %s but got %s", expectedErr, retFirstErr) } @@ -633,7 +633,7 @@ func TestServer_Verify_VerifierConfigInvalid_Failure(t *testing.T) { Verifiers: []verifier.ReferenceVerifier{}, } - getExecutor := func() *core.Executor { + getExecutor := func(context.Context) *core.Executor { return ex } @@ -647,7 +647,7 @@ func TestServer_Verify_VerifierConfigInvalid_Failure(t *testing.T) { handler := contextHandler{ context: server.Context, - handler: processTimeout(server.verify, server.GetExecutor().GetVerifyRequestTimeout(), false), + handler: processTimeout(server.verify, server.GetExecutor(nil).GetVerifyRequestTimeout(), false), } handler.ServeHTTP(responseRecorder, request) @@ -671,7 +671,7 @@ func TestServer_Verify_VerifierConfigInvalid_Failure(t *testing.T) { // TestServe_serverGracefulShutdown tests the case where the server is shutdown gracefully func TestServer_serverGracefulShutdown(t *testing.T) { // create a server that sleeps for 5 seconds before responding - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(5 * time.Second) fmt.Fprintln(w, "request succeeded") })) diff --git a/httpserver/tlsManager.go b/httpserver/tlsManager.go index 3764e3d7b..442bd2598 100644 --- a/httpserver/tlsManager.go +++ b/httpserver/tlsManager.go @@ -73,7 +73,7 @@ func (t *TLSCertWatcher) Start() error { var watchErr error pollInterval := 1 * time.Second pollTimeout := 10 * time.Second - if err := wait.PollUntilContextTimeout(context.TODO(), pollInterval, pollTimeout, false, func(ctx context.Context) (done bool, err error) { + if err := wait.PollUntilContextTimeout(context.TODO(), pollInterval, pollTimeout, false, func(_ context.Context) (done bool, err error) { for f := range files { if err := t.watcher.Add(f); err != nil { watchErr = err diff --git a/httpserver/types.go b/httpserver/types.go index 02ad6e744..dafad6547 100644 --- a/httpserver/types.go +++ b/httpserver/types.go @@ -16,31 +16,41 @@ limitations under the License. package httpserver import ( - "github.com/deislabs/ratify/pkg/executor/types" - pt "github.com/deislabs/ratify/pkg/policyprovider/types" + "context" + "time" + + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/executor/types" + pt "github.com/ratify-project/ratify/pkg/policyprovider/types" ) const ( VerificationResultVersion = "0.1.0" + ResultVersion0_2_0 = "0.2.0" // Starting from this version, the verification result can be // evaluated by Ratify embedded OPA engine. ResultVersionSupportingRego = "1.0.0" + ResultVersion1_1_0 = "1.1.0" ) type VerificationResponse struct { Version string `json:"version"` IsSuccess bool `json:"isSuccess"` + TraceID string `json:"traceID,omitempty"` + Timestamp string `json:"timestamp,omitempty"` VerifierReports []interface{} `json:"verifierReports,omitempty"` } -func fromVerifyResult(res types.VerifyResult, policyType string) VerificationResponse { - version := VerificationResultVersion +func fromVerifyResult(ctx context.Context, res types.VerifyResult, policyType string) VerificationResponse { + version := ResultVersion0_2_0 if policyType == pt.RegoPolicy { - version = ResultVersionSupportingRego + version = ResultVersion1_1_0 } return VerificationResponse{ Version: version, IsSuccess: res.IsSuccess, + Timestamp: time.Now().Format(time.RFC3339Nano), + TraceID: logger.GetTraceID(ctx), VerifierReports: res.VerifierReports, } } diff --git a/httpserver/types_test.go b/httpserver/types_test.go index 51be42151..c03d694df 100644 --- a/httpserver/types_test.go +++ b/httpserver/types_test.go @@ -16,10 +16,11 @@ limitations under the License. package httpserver import ( + "context" "testing" - "github.com/deislabs/ratify/pkg/executor/types" - pt "github.com/deislabs/ratify/pkg/policyprovider/types" + "github.com/ratify-project/ratify/pkg/executor/types" + pt "github.com/ratify-project/ratify/pkg/policyprovider/types" ) func TestFromVerifyResult(t *testing.T) { @@ -32,18 +33,18 @@ func TestFromVerifyResult(t *testing.T) { { name: "Rego policy", policyType: pt.RegoPolicy, - expectedVersion: "1.0.0", + expectedVersion: "1.1.0", }, { name: "Config policy", policyType: pt.ConfigPolicy, - expectedVersion: "0.1.0", + expectedVersion: "0.2.0", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if res := fromVerifyResult(result, tc.policyType); res.Version != tc.expectedVersion { + if res := fromVerifyResult(context.Background(), result, tc.policyType); res.Version != tc.expectedVersion { t.Fatalf("Expected version to be %s, got %s", tc.expectedVersion, res.Version) } }) diff --git a/instrumentation/grafana_namespaced_configMap.yaml b/instrumentation/grafana_namespaced_configMap.yaml new file mode 100644 index 000000000..ea65b96aa --- /dev/null +++ b/instrumentation/grafana_namespaced_configMap.yaml @@ -0,0 +1,1482 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: ratify-grafana-dashboard + labels: + grafana_dashboard: "1" + namespace: monitoring +data: + ratify-dashboard.json: |- + { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.8", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "sum(rate(ratify_verification_request_count[$__rate_interval]))", + "instant": true, + "key": "Q-806348e4-5c52-4af8-87f1-0676999cd43c-0", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Verification RPS", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "Count of Verifier Ops", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "verifier= isSuccess= isError=" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "sum by(error, success, verifier) (increase(ratify_verifier_duration_count{workload_namespace=~\"$workload_namespace\"}[$__rate_interval]))", + "instant": false, + "key": "Q-e20b12bd-6bd1-4c51-a1f5-4b1582183dc6-0", + "legendFormat": "verifier={{verifier}} isSuccess={{success}} isError={{error}}", + "range": true, + "refId": "A" + } + ], + "title": "Count of Verifier Operations Per Interval", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (ratify_mutation_request_bucket))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "P95 Mutation Request Duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (ratify_verification_request_bucket))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "P95 Verification Request Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 110, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 17, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(ratify_blob_cache_count_total{hit=\"true\", workload_namespace=~\"$workload_namespace\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "key": "Q-e087804a-f777-4009-b91d-0207684b5d52-0", + "legendFormat": "{{hit}}", + "range": true, + "refId": "A", + "useBackend": false + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "builder", + "expr": "increase(ratify_blob_cache_count_total{workload_namespace=~\"$workload_namespace\"}[$__rate_interval])", + "fullMetaSearch": false, + "hide": true, + "includeNullMetadata": true, + "instant": false, + "key": "Q-e087804a-f777-4009-b91d-0207684b5d52-0", + "legendFormat": "{{hit}}", + "range": true, + "refId": "B", + "useBackend": false + }, + { + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$A/$B * 100", + "hide": false, + "refId": "C", + "type": "math" + } + ], + "title": "Blob Cache Hit", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "sum(increase(ratify_registry_request_count_total{workload_namespace=~\"$workload_namespace\"}[$__rate_interval]))", + "instant": false, + "key": "Q-03cacf42-7553-4d7a-9bce-8bea83f95a09-0", + "legendFormat": "non 429", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "sum(increase(ratify_registry_request_count_total{status_code=\"429\", workload_namespace=~\"$workload_namespace\"}[$__rate_interval]))", + "hide": false, + "instant": false, + "key": "Q-03cacf42-7553-4d7a-9bce-8bea83f95a09-0", + "legendFormat": "429", + "range": true, + "refId": "B" + } + ], + "title": "Registry Request Count", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "limit" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(container_memory_working_set_bytes{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", cluster=\"\", namespace=\"$namespace\", pod=\"$pod\", container!=\"\", image!=\"\"}) by (container)", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "key": "Q-e556d2e4-096e-4e25-8524-17875553f26c-0", + "legendFormat": "{{label_name}}", + "range": true, + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(\n kube_pod_container_resource_limits{job=\"kube-state-metrics\", cluster=\"\", namespace=\"$namespace\", pod=\"$pod\", resource=\"memory\"}\n)\n", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "key": "Q-2810b9b8-6d65-402c-a8dd-e33651478da8-2", + "legendFormat": "limit", + "range": true, + "refId": "C", + "step": 10 + } + ], + "title": "Memory Usage (WSS)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "request" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "limit" + }, + "properties": [ + { + "id": "custom.lineStyle", + "value": { + "dash": [ + 10, + 10 + ], + "fill": "dash" + } + }, + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=\"$namespace\", pod=\"$pod\", container=\"ratify\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "key": "Q-255c079c-fc4f-462b-a7e6-675a987fb424-0", + "legendFormat": "ratify", + "range": true, + "refId": "A", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(\n kube_pod_container_resource_requests{namespace=\"$namespace\", pod=\"$pod\", resource=\"cpu\"}\n)\n", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "key": "Q-a2ac92c1-8a95-49aa-96fc-01b51a7e7773-1", + "legendFormat": "request", + "range": true, + "refId": "B", + "step": 10 + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(\n kube_pod_container_resource_limits{namespace=\"$namespace\", pod=\"$pod\", resource=\"cpu\"}\n)\n", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "key": "Q-fa5c0a68-ea62-4328-8f34-c389bd402946-2", + "legendFormat": "limit", + "range": true, + "refId": "C", + "step": 10 + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "ratify_system_error_count", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Error Count", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 19, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 41 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (ratify_akv_certificate_duration_bucket))", + "instant": false, + "key": "Q-fcdaeb64-9bf0-4d47-bd25-dbe3022be5f9-0", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "P95 AKV Certificate Fetch Duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 41 + }, + "id": 15, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (ratify_aad_exchange_duration_bucket))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "P95 AAD Exchange Duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 49 + }, + "id": 13, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "histogram_quantile(0.95, sum by(le) (ratify_acr_exchange_duration_bucket))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "P95 ACR Exchange Duration", + "type": "timeseries" + } + ], + "title": "Azure", + "type": "row" + } + ], + "refresh": false, + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "gatekeeper-system", + "value": "gatekeeper-system" + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(kube_namespace_status_phase{job=\"kube-state-metrics\"}, namespace)", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(kube_namespace_status_phase{job=\"kube-state-metrics\"}, namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(kube_pod_info{namespace=\"$namespace\"}, pod)", + "hide": 0, + "includeAll": false, + "label": "ratify pod", + "multi": false, + "name": "pod", + "options": [], + "query": { + "query": "label_values(kube_pod_info{namespace=\"$namespace\"}, pod)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "/^ratify.*/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": { + "selected": false, + "text": [ + "default" + ], + "value": [ + "default" + ] + }, + "definition": "label_values(ratify_verifier_duration_count, workload_namespace)", + "hide": 0, + "includeAll": true, + "label": "workload namespace", + "multi": true, + "name": "workload_namespace", + "options": [], + "query": { + "qryType": 5, + "query": "label_values(ratify_verifier_duration_count, workload_namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "nowDelay": "" + }, + "timezone": "", + "title": "Ratify", + "uid": "mBWgLNBVk", + "version": 7, + "weekStart": "" + } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index cc6866ec4..0b47690ac 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -19,3 +19,4 @@ package constants const RatifyPolicy = "ratify-policy" const EmptyNamespace = "" const NamespaceSeperator = "/" +const MaxBriefErrLength = 100 diff --git a/internal/context/utils.go b/internal/context/utils.go index 39c8ae5b7..1f77a87c0 100644 --- a/internal/context/utils.go +++ b/internal/context/utils.go @@ -22,16 +22,16 @@ import ( type contextKey string -const contextKeyNamespace = contextKey("namespace") +const ContextKeyNamespace = contextKey("namespace") // SetContextWithNamespace embeds namespace to the context. func SetContextWithNamespace(ctx context.Context, namespace string) context.Context { - return context.WithValue(ctx, contextKeyNamespace, namespace) + return context.WithValue(ctx, ContextKeyNamespace, namespace) } // GetNamespace returns the embedded namespace from the context. func GetNamespace(ctx context.Context) string { - namespace := ctx.Value(contextKeyNamespace) + namespace := ctx.Value(ContextKeyNamespace) if namespace == nil { return "" } @@ -40,7 +40,7 @@ func GetNamespace(ctx context.Context) string { // CreateCacheKey creates a new cache key prefixed with embedded namespace. func CreateCacheKey(ctx context.Context, key string) string { - namespace := ctx.Value(contextKeyNamespace) + namespace := ctx.Value(ContextKeyNamespace) if namespace == nil { return key } diff --git a/internal/context/utils_test.go b/internal/context/utils_test.go index 1b2d94a0a..8a52fafab 100644 --- a/internal/context/utils_test.go +++ b/internal/context/utils_test.go @@ -28,7 +28,7 @@ const ( func TestSetContext(t *testing.T) { ctx := context.Background() ctx = SetContextWithNamespace(ctx, testNamespace) - namespace := ctx.Value(contextKeyNamespace).(string) + namespace := ctx.Value(ContextKeyNamespace).(string) if namespace != testNamespace { t.Fatalf("expected namespace %s, got %s", testNamespace, namespace) } diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 15a5a78d7..3a65de01c 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -22,9 +22,10 @@ import ( "time" logstash "github.com/bshuster-repo/logrus-logstash-hook" - re "github.com/deislabs/ratify/errors" dcontext "github.com/docker/distribution/context" "github.com/google/uuid" + re "github.com/ratify-project/ratify/errors" + icontext "github.com/ratify-project/ratify/internal/context" "github.com/sirupsen/logrus" ) @@ -60,12 +61,16 @@ const ( Executor componentType = "executor" // Server is the component type for the Ratify http server. Server componentType = "server" + // CommandLine is the component type for the Ratify command line. + CommandLine componentType = "commandLine" // ReferrerStore is the component type for the referrer store. ReferrerStore componentType = "referrerStore" // Cache is the component type for the cache. Cache componentType = "cache" // CertProvider is the component type for certificate provider. CertProvider componentType = "certificateProvider" + // KeyManagementProvider is the component type for key management provider. + KeyManagementProvider componentType = "keyManagementProvider" // AuthProvider is the component type for auth provider. AuthProvider componentType = "authProvider" // PolicyProvider is the component type for policy provider. @@ -91,10 +96,20 @@ func InitContext(ctx context.Context, r *http.Request) context.Context { // GetLogger returns a logger with provided values. func GetLogger(ctx context.Context, opt Option) dcontext.Logger { + ctx = dcontext.WithLogger(ctx, dcontext.GetLogger(ctx, icontext.ContextKeyNamespace)) ctx = context.WithValue(ctx, ContextKeyComponentType, opt.ComponentType) return dcontext.GetLogger(ctx, ContextKeyComponentType) } +// GetTraceID returns the trace ID from the context. +func GetTraceID(ctx context.Context) string { + traceID := ctx.Value(ContextKeyTraceID) + if traceID == nil { + return "" + } + return traceID.(string) +} + // setTraceID sets the trace ID in the context. If the trace ID is not present in the request headers, a new one is generated. func setTraceID(ctx context.Context, r *http.Request) context.Context { traceID := "" diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index 6024134e7..9aa95d3a6 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -23,7 +23,6 @@ import ( "testing" logstash "github.com/bshuster-repo/logrus-logstash-hook" - dcontext "github.com/docker/distribution/context" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -75,7 +74,7 @@ func TestInitContext(t *testing.T) { t.Run(tc.name, func(t *testing.T) { traceIDHeaderNames = tc.headerNames ctx := InitContext(context.Background(), tc.r) - traceID := dcontext.GetStringValue(ctx, ContextKeyTraceID) + traceID := GetTraceID(ctx) if traceID == "" { t.Fatalf("expected non-empty traceID, but got empty one") } diff --git a/library/default/customazurepolicy.json b/library/default/customazurepolicy.json index 52b16c5c4..6572fffe8 100644 --- a/library/default/customazurepolicy.json +++ b/library/default/customazurepolicy.json @@ -106,7 +106,7 @@ "details": { "templateInfo": { "sourceType": "PublicURL", - "url": "https://deislabs.github.io/ratify/library/default/template.yaml" + "url": "https://ratify-project.github.io/ratify/library/default/template.yaml" }, "apiGroups": [ "" diff --git a/library/default/template.yaml b/library/default/template.yaml index 50d0077a2..6cc46ec3f 100644 --- a/library/default/template.yaml +++ b/library/default/template.yaml @@ -11,7 +11,7 @@ spec: - target: admission.k8s.gatekeeper.sh rego: | package ratifyverification - + # Get data from Ratify remote_data := response { images := [img | img = input.review.object.spec.containers[_].image] @@ -26,23 +26,23 @@ spec: violation[{"msg": msg}] { general_violation[{"result": msg}] } - + # Check if there are any system errors general_violation[{"result": result}] { err := remote_data.system_error err != "" result := sprintf("System error calling external data provider: %s", [err]) } - + # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 result := sprintf("Error validating one or more images: %s", remote_data.errors) } - + # Check if the success criteria is true general_violation[{"result": result}] { subject_validation := remote_data.responses[_] subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Time=%s, failed to verify the artifact: %s, trace-id: %s", [subject_validation[1].timestamp, subject_validation[0], subject_validation[1].traceID]) } diff --git a/library/multi-tenancy-validation/samples/constraint.yaml b/library/multi-tenancy-validation/samples/constraint.yaml new file mode 100644 index 000000000..cb483c322 --- /dev/null +++ b/library/multi-tenancy-validation/samples/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: RatifyVerification +metadata: + name: ratify-constraint +spec: + enforcementAction: deny + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + namespaces: ["default", "new-namespace"] diff --git a/library/multi-tenancy-validation/template.yaml b/library/multi-tenancy-validation/template.yaml new file mode 100644 index 000000000..5037c6565 --- /dev/null +++ b/library/multi-tenancy-validation/template.yaml @@ -0,0 +1,48 @@ +apiVersion: templates.gatekeeper.sh/v1beta1 +kind: ConstraintTemplate +metadata: + name: ratifyverification +spec: + crd: + spec: + names: + kind: RatifyVerification + targets: + - target: admission.k8s.gatekeeper.sh + rego: | + package ratifyverification + + # Get data from Ratify + remote_data := response { + images := [img | img = concat("", ["[",input.review.object.metadata.namespace,"]",input.review.object.spec.containers[_].image])] + images_init := [img | img = concat("", ["[",input.review.object.metadata.namespace,"]",input.review.object.spec.initContainers[_].image])] + images_ephemeral := [img | img = concat("", ["[",input.review.object.metadata.namespace,"]",input.review.object.spec.ephemeralContainers[_].image])] + other_images := array.concat(images_init, images_ephemeral) + all_images := array.concat(other_images, images) + response := external_data({"provider": "ratify-provider", "keys": all_images}) + } + + # Base Gatekeeper violation + violation[{"msg": msg}] { + general_violation[{"result": msg}] + } + + # Check if there are any system errors + general_violation[{"result": result}] { + err := remote_data.system_error + err != "" + result := sprintf("System error calling external data provider: %s", [err]) + } + + # Check if there are errors for any of the images + general_violation[{"result": result}] { + count(remote_data.errors) > 0 + result := sprintf("Error validating one or more images: %s", remote_data.errors) + } + + # Check if the success criteria is true + general_violation[{"result": result}] { + subject_validation := remote_data.responses[_] + subject_validation[1].isSuccess == false + result := sprintf("Time=%s, failed to verify the artifact: %s, trace-id: %s", [subject_validation[1].timestamp, subject_validation[0], subject_validation[1].traceID]) + } diff --git a/library/notation-issuer-validation/template.yaml b/library/notation-issuer-validation/template.yaml index 44caf1cb7..d0f04dac6 100644 --- a/library/notation-issuer-validation/template.yaml +++ b/library/notation-issuer-validation/template.yaml @@ -17,7 +17,7 @@ spec: - target: admission.k8s.gatekeeper.sh rego: | package notationissuervalidation - + # Get data from Ratify remote_data := response { images := [img | img = input.review.object.spec.containers[_].image] @@ -32,25 +32,25 @@ spec: violation[{"msg": msg}] { general_violation[{"result": msg}] } - + # Check if there are any system errors general_violation[{"result": result}] { err := remote_data.system_error err != "" result := sprintf("System error calling external data provider: %s", [err]) } - + # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 result := sprintf("Error validating one or more images: %s", remote_data.errors) } - + # Check if the success criteria is true general_violation[{"result": result}] { subject_validation := remote_data.responses[_] subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } # Check that signature result for Issuer exists @@ -62,7 +62,7 @@ spec: count(issuer_results) == 0 result := sprintf("Subject %s has no signatures for certificate with Issuer: %s", [subject_results[0], input.parameters.issuer]) } - + # Check for valid signature general_violation[{"result": result}] { subject_results := remote_data.responses[_] diff --git a/library/notation-nested-validation/template.yaml b/library/notation-nested-validation/template.yaml index 39db14e82..97d1553ae 100644 --- a/library/notation-nested-validation/template.yaml +++ b/library/notation-nested-validation/template.yaml @@ -57,7 +57,7 @@ spec: subject_validation := remote_data.responses[_] subject_result := subject_validation[1] failed_verify(subject_result) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } failed_verify(reports) if { @@ -85,4 +85,3 @@ spec: ] number := count(sigs) } - \ No newline at end of file diff --git a/pkg/cache/dapr/dapr.go b/pkg/cache/dapr/dapr.go index a0d9a9414..1230a9158 100644 --- a/pkg/cache/dapr/dapr.go +++ b/pkg/cache/dapr/dapr.go @@ -23,10 +23,10 @@ import ( "time" "github.com/dapr/go-sdk/client" - ctxUtils "github.com/deislabs/ratify/internal/context" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/featureflag" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/featureflag" ) const DaprCacheType = "dapr" diff --git a/pkg/cache/ristretto/ristretto.go b/pkg/cache/ristretto/ristretto.go index 581b98a47..67210d7b9 100644 --- a/pkg/cache/ristretto/ristretto.go +++ b/pkg/cache/ristretto/ristretto.go @@ -22,11 +22,11 @@ import ( "time" "github.com/cespare/xxhash/v2" - ctxUtils "github.com/deislabs/ratify/internal/context" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/cache" "github.com/dgraph-io/ristretto" "github.com/dgraph-io/ristretto/z" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/cache" ) const RistrettoCacheType = "ristretto" diff --git a/pkg/cache/ristretto/ristretto_test.go b/pkg/cache/ristretto/ristretto_test.go index afb67600b..e2c8f0525 100644 --- a/pkg/cache/ristretto/ristretto_test.go +++ b/pkg/cache/ristretto/ristretto_test.go @@ -22,8 +22,8 @@ import ( "time" "github.com/cespare/xxhash/v2" - "github.com/deislabs/ratify/pkg/cache" "github.com/dgraph-io/ristretto/z" + "github.com/ratify-project/ratify/pkg/cache" ) // TestKeytoHash_Expected tests the keyToHash function diff --git a/pkg/certificateprovider/azurekeyvault/auth.go b/pkg/certificateprovider/azurekeyvault/auth.go index 6dde6afee..b347000e7 100644 --- a/pkg/certificateprovider/azurekeyvault/auth.go +++ b/pkg/certificateprovider/azurekeyvault/auth.go @@ -25,10 +25,9 @@ import ( "strings" "time" - "github.com/deislabs/ratify/pkg/utils/azureauth" + "github.com/ratify-project/ratify/pkg/utils/azureauth" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" ) const ( @@ -37,7 +36,6 @@ const ( // the format for expires_on in UTC without AM/PM expiresOnDateFormat = "1/2/2006 15:04:05 +00:00" - tokenTypeBearer = "Bearer" // For Azure AD Workload Identity, the audience recommended for use is // "api://AzureADTokenExchange" DefaultTokenAudience = "api://AzureADTokenExchange" //nolint @@ -64,13 +62,7 @@ func getAuthorizerForWorkloadIdentity(ctx context.Context, tenantID, clientID, r return nil, fmt.Errorf("failed to acquire token: %w", err) } - token := adal.Token{ - AccessToken: result.AccessToken, - Resource: resource, - Type: tokenTypeBearer, - } - token.ExpiresOn, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)) - if err != nil { + if _, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)); err != nil { return nil, fmt.Errorf("failed to parse expires_on: %w", err) } diff --git a/pkg/certificateprovider/azurekeyvault/provider.go b/pkg/certificateprovider/azurekeyvault/provider.go index 473272c1c..6565bca07 100644 --- a/pkg/certificateprovider/azurekeyvault/provider.go +++ b/pkg/certificateprovider/azurekeyvault/provider.go @@ -27,11 +27,11 @@ import ( "strings" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/certificateprovider" - "github.com/deislabs/ratify/pkg/certificateprovider/azurekeyvault/types" - "github.com/deislabs/ratify/pkg/metrics" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/certificateprovider" + "github.com/ratify-project/ratify/pkg/certificateprovider/azurekeyvault/types" + "github.com/ratify-project/ratify/pkg/metrics" "golang.org/x/crypto/pkcs12" kv "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" @@ -90,7 +90,7 @@ func (s *akvCertProvider) GetCertificates(ctx context.Context, attrib map[string } if len(keyVaultCerts) == 0 { - return nil, nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "no keyvault certificate configured", re.PrintStackTrace) + return nil, nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "no keyvault certificate configured", re.HideStackTrace) } logger.GetLogger(ctx, logOpt).Debugf("vaultURI %s", keyvaultURI) @@ -106,7 +106,7 @@ func (s *akvCertProvider) GetCertificates(ctx context.Context, attrib map[string logger.GetLogger(ctx, logOpt).Debugf("fetching secret from key vault, certName %v, keyvault %v", keyVaultCert.CertificateName, keyvaultURI) // fetch the object from Key Vault - // GetSecret is required so we can fetch the entire cert chain. See issue https://github.com/deislabs/ratify/issues/695 for details + // GetSecret is required so we can fetch the entire cert chain. See issue https://github.com/ratify-project/ratify/issues/695 for details startTime := time.Now() secretBundle, err := kvClient.GetSecret(ctx, keyvaultURI, keyVaultCert.CertificateName, keyVaultCert.CertificateVersion) @@ -155,7 +155,7 @@ func getKeyvaultRequestObj(ctx context.Context, attrib map[string]string) ([]typ for i, object := range objects.Array { var keyVaultCert types.KeyVaultCertificate if err = yaml.Unmarshal([]byte(object), &keyVaultCert); err != nil { - return nil, re.ErrorCodeDataDecodingFailure.NewError(re.CertProvider, providerName, re.EmptyLink, err, fmt.Sprintf("unmarshal failed for keyVaultCerts at index: %d", i), re.PrintStackTrace) + return nil, re.ErrorCodeDataDecodingFailure.NewError(re.CertProvider, providerName, re.EmptyLink, err, fmt.Sprintf("unmarshal failed for keyVaultCerts at index: %d", i), re.HideStackTrace) } // remove whitespace from all fields in keyVaultCert formatKeyVaultCertificate(&keyVaultCert) @@ -213,12 +213,12 @@ func initializeKvClient(ctx context.Context, keyVaultEndpoint, tenantID, clientI err := kvClient.AddToUserAgent("ratify") if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to add user agent to keyvault client", re.PrintStackTrace) + return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to add user agent to keyvault client", re.HideStackTrace) } kvClient.Authorizer, err = getAuthorizerForWorkloadIdentity(ctx, tenantID, clientID, kvEndpoint) if err != nil { - return nil, re.ErrorCodeAuthDenied.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to get authorizer for keyvault client", re.PrintStackTrace) + return nil, re.ErrorCodeAuthDenied.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to get authorizer for keyvault client", re.HideStackTrace) } return &kvClient, nil } diff --git a/pkg/certificateprovider/azurekeyvault/provider_test.go b/pkg/certificateprovider/azurekeyvault/provider_test.go index efadc599f..f11f31eed 100644 --- a/pkg/certificateprovider/azurekeyvault/provider_test.go +++ b/pkg/certificateprovider/azurekeyvault/provider_test.go @@ -26,7 +26,8 @@ import ( kv "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest/azure" - "github.com/deislabs/ratify/pkg/certificateprovider/azurekeyvault/types" + "github.com/ratify-project/ratify/internal/version" + "github.com/ratify-project/ratify/pkg/certificateprovider/azurekeyvault/types" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -105,7 +106,7 @@ func SkipTestInitializeKVClient(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, kvBaseClient) assert.NotNil(t, kvBaseClient.Authorizer) - assert.Contains(t, kvBaseClient.UserAgent, "ratify") + assert.Contains(t, kvBaseClient.UserAgent, version.UserAgent) } } diff --git a/pkg/certificateprovider/certificate_provider.go b/pkg/certificateprovider/certificate_provider.go index 80f7f0631..cf517f606 100644 --- a/pkg/certificateprovider/certificate_provider.go +++ b/pkg/certificateprovider/certificate_provider.go @@ -21,7 +21,7 @@ import ( "encoding/pem" "fmt" - "github.com/deislabs/ratify/errors" + "github.com/ratify-project/ratify/errors" ) // This is a map containing Cert store configuration including name, tenantID, and cert object information @@ -75,5 +75,8 @@ func DecodeCertificates(value []byte) ([]*x509.Certificate, error) { } } + if len(certs) == 0 { + return nil, errors.ErrorCodeCertInvalid.WithComponentType(errors.CertProvider).WithDetail("no certificates found in the pem block") + } return certs, nil } diff --git a/pkg/certificateprovider/certificate_provider_test.go b/pkg/certificateprovider/certificate_provider_test.go index a86e31a95..78d70c439 100644 --- a/pkg/certificateprovider/certificate_provider_test.go +++ b/pkg/certificateprovider/certificate_provider_test.go @@ -19,7 +19,7 @@ import ( "errors" "testing" - ratifyerrors "github.com/deislabs/ratify/errors" + ratifyerrors "github.com/ratify-project/ratify/errors" "github.com/stretchr/testify/assert" ) @@ -32,13 +32,18 @@ func TestDecodeCertificates(t *testing.T) { }{ { desc: "empty string", - expectedErr: false, + expectedErr: true, }, { desc: "invalid certificate", pemString: "-----BEGIN CERTIFICATE-----\nbaddata\n-----END CERTIFICATE-----\n", expectedErr: true, }, + { + desc: "invalid certificate", + pemString: "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAweAc4xikYT4ZszXVdF5mrgP0zKVYi4Ces0py9dw8XZfh/Hlxb5xWMs4DzTcKwmLatgKNSrvNyOaxkBD90PvcYNaTCwzwQ09kZ5dYtVOV4sdzeyOj8UDtf4MF5eJgJj/wWCQJnWrX/4n6nSdNTXSJEFAZkDv0BKVkZekJHn3fh+pOuv8UtvOrY1NjNK/TLWxB+8xpwugeB9oZ+VgV/gHZBLprxYkmUDsfngYy3+r6RZ+hInalZc5uAbtRUoB8+nVhXXOe3iVcVWFoWPMJ2fuPHz/8cDjv02MNWa/MeAt+ItW3N+VFZNkwbu5en3FepsxzRl04rhZzr1DSX6V6CVX43wIDAQAB-----END PUBLIC KEY-----", + expectedErr: true, + }, { desc: "single certificate", pemString: "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n", diff --git a/pkg/certificateprovider/inline/provider.go b/pkg/certificateprovider/inline/provider.go index d52a07e9e..effdf4d0b 100644 --- a/pkg/certificateprovider/inline/provider.go +++ b/pkg/certificateprovider/inline/provider.go @@ -19,8 +19,8 @@ import ( "context" "crypto/x509" - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/certificateprovider" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/certificateprovider" ) const ( diff --git a/pkg/common/oras/authprovider/authprovider.go b/pkg/common/oras/authprovider/authprovider.go index 6d08941ae..c266920da 100644 --- a/pkg/common/oras/authprovider/authprovider.go +++ b/pkg/common/oras/authprovider/authprovider.go @@ -24,11 +24,11 @@ import ( "strings" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" ) // This config represents the credentials that should be used diff --git a/pkg/common/oras/authprovider/authprovider_test.go b/pkg/common/oras/authprovider/authprovider_test.go index 98da91d51..1001c319e 100644 --- a/pkg/common/oras/authprovider/authprovider_test.go +++ b/pkg/common/oras/authprovider/authprovider_test.go @@ -22,7 +22,7 @@ import ( "testing" "time" - re "github.com/deislabs/ratify/errors" + re "github.com/ratify-project/ratify/errors" ) const ( diff --git a/pkg/common/oras/authprovider/authproviderfactory.go b/pkg/common/oras/authprovider/authproviderfactory.go index 4536b83e1..08bde422c 100644 --- a/pkg/common/oras/authprovider/authproviderfactory.go +++ b/pkg/common/oras/authprovider/authproviderfactory.go @@ -18,7 +18,7 @@ package authprovider import ( "fmt" - "github.com/deislabs/ratify/errors" + "github.com/ratify-project/ratify/errors" "github.com/sirupsen/logrus" ) diff --git a/pkg/common/oras/authprovider/aws/awsecrbasic.go b/pkg/common/oras/authprovider/aws/awsecrbasic.go index 6605b6407..3d8866259 100644 --- a/pkg/common/oras/authprovider/aws/awsecrbasic.go +++ b/pkg/common/oras/authprovider/aws/awsecrbasic.go @@ -27,9 +27,9 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/ecr" "github.com/aws/aws-sdk-go-v2/service/ecr/types" - provider "github.com/deislabs/ratify/pkg/common/oras/authprovider" - "github.com/deislabs/ratify/pkg/utils/awsauth" "github.com/pkg/errors" + provider "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + "github.com/ratify-project/ratify/pkg/utils/awsauth" "github.com/sirupsen/logrus" ) @@ -68,13 +68,16 @@ func (d *awsEcrBasicAuthProvider) getEcrAuthToken(artifact string) (EcrAuthToken } ctx := context.Background() - - resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) { + // TODO: Update to use regional endpoint + // nolint:staticcheck + resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, _ ...interface{}) (aws.Endpoint, error) { if service == ecr.ServiceID && region == apiOverrideRegion { logrus.Info("AWS ECR basic auth using custom endpoint resolver...") logrus.Infof("AWS ECR basic auth API override endpoint: %s", apiOverrideEndpoint) logrus.Infof("AWS ECR basic auth API override partition: %s", apiOverridePartition) logrus.Infof("AWS ECR basic auth API override region: %s", apiOverrideRegion) + // TODO: Update to use regional endpoint + // nolint:staticcheck return aws.Endpoint{ URL: apiOverrideEndpoint, PartitionID: apiOverridePartition, @@ -82,9 +85,12 @@ func (d *awsEcrBasicAuthProvider) getEcrAuthToken(artifact string) (EcrAuthToken }, nil } // returning EndpointNotFoundError will allow the service to fall back to its default resolution + // TODO: Update to use regional endpoint + // nolint:staticcheck return aws.Endpoint{}, &aws.EndpointNotFoundError{} }) - + // TODO: Update to use regional endpoint + // nolint:staticcheck cfg, err := config.LoadDefaultConfig(ctx, config.WithEndpointResolverWithOptions(resolver), config.WithWebIdentityRoleCredentialOptions(func(options *stscreds.WebIdentityRoleOptions) { options.RoleSessionName = awsSessionName diff --git a/pkg/common/oras/authprovider/azure/azureidentity.go b/pkg/common/oras/authprovider/azure/azureidentity.go index 9e5ee0111..0a5a00e5c 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity.go +++ b/pkg/common/oras/authprovider/azure/azureidentity.go @@ -22,9 +22,9 @@ import ( "os" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - provider "github.com/deislabs/ratify/pkg/common/oras/authprovider" + 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/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" diff --git a/pkg/common/oras/authprovider/azure/azureidentity_test.go b/pkg/common/oras/authprovider/azure/azureidentity_test.go index 3bf0230f1..472e704b9 100644 --- a/pkg/common/oras/authprovider/azure/azureidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureidentity_test.go @@ -22,8 +22,8 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" - ratifyerrors "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common/oras/authprovider" + ratifyerrors "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common/oras/authprovider" ) // Verifies that Enabled checks if tenantID is empty or AAD token is empty diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go index 5541940fa..a40ce4436 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go @@ -21,11 +21,11 @@ import ( "os" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - provider "github.com/deislabs/ratify/pkg/common/oras/authprovider" - "github.com/deislabs/ratify/pkg/metrics" - "github.com/deislabs/ratify/pkg/utils/azureauth" + 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" diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go index 0ac3ce81d..3695ef65a 100644 --- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go +++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go @@ -23,8 +23,8 @@ import ( "time" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" - ratifyerrors "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common/oras/authprovider" + ratifyerrors "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common/oras/authprovider" ) // Verifies that Enabled checks if tenantID is empty or AAD token is empty diff --git a/pkg/common/oras/authprovider/azure/const.go b/pkg/common/oras/authprovider/azure/const.go index e0014ea84..b311c02a6 100644 --- a/pkg/common/oras/authprovider/azure/const.go +++ b/pkg/common/oras/authprovider/azure/const.go @@ -18,7 +18,7 @@ package azure import ( "time" - "github.com/deislabs/ratify/internal/logger" + "github.com/ratify-project/ratify/internal/logger" ) const ( diff --git a/pkg/common/oras/authprovider/k8secret_authprovider.go b/pkg/common/oras/authprovider/k8secret_authprovider.go index 10fd74631..50fa03416 100644 --- a/pkg/common/oras/authprovider/k8secret_authprovider.go +++ b/pkg/common/oras/authprovider/k8secret_authprovider.go @@ -24,9 +24,9 @@ import ( "os" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/utils" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/utils" "github.com/docker/cli/cli/config" core "k8s.io/api/core/v1" diff --git a/pkg/common/oras/authprovider/k8secret_authprovider_test.go b/pkg/common/oras/authprovider/k8secret_authprovider_test.go index 52be7957f..8dcc3f142 100644 --- a/pkg/common/oras/authprovider/k8secret_authprovider_test.go +++ b/pkg/common/oras/authprovider/k8secret_authprovider_test.go @@ -20,7 +20,7 @@ import ( "errors" "testing" - ratifyerrors "github.com/deislabs/ratify/errors" + ratifyerrors "github.com/ratify-project/ratify/errors" core "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" diff --git a/pkg/common/plugin/download.go b/pkg/common/plugin/download.go index 075931c4b..357e47f10 100644 --- a/pkg/common/plugin/download.go +++ b/pkg/common/plugin/download.go @@ -24,11 +24,11 @@ import ( "os" "time" - "github.com/deislabs/ratify/internal/version" - "github.com/deislabs/ratify/pkg/common/oras/authprovider" - commonutils "github.com/deislabs/ratify/pkg/common/utils" - "github.com/deislabs/ratify/pkg/ocispecs" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/internal/version" + "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + commonutils "github.com/ratify-project/ratify/pkg/common/utils" + "github.com/ratify-project/ratify/pkg/ocispecs" "github.com/sirupsen/logrus" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" diff --git a/pkg/common/plugin/download_test.go b/pkg/common/plugin/download_test.go index d5dbba0b9..04ae4a767 100644 --- a/pkg/common/plugin/download_test.go +++ b/pkg/common/plugin/download_test.go @@ -19,7 +19,7 @@ import ( "encoding/json" "testing" - "github.com/deislabs/ratify/api/v1beta1" + "github.com/ratify-project/ratify/api/v1beta1" ) func TestParsePluginSource_HandlesJSON(t *testing.T) { diff --git a/pkg/common/plugin/exec.go b/pkg/common/plugin/exec.go index 9cbfdd065..f9f5e08b5 100644 --- a/pkg/common/plugin/exec.go +++ b/pkg/common/plugin/exec.go @@ -27,7 +27,7 @@ import ( "strings" "time" - "github.com/deislabs/ratify/internal/logger" + "github.com/ratify-project/ratify/internal/logger" "github.com/sirupsen/logrus" ) diff --git a/pkg/common/utils/utils.go b/pkg/common/utils/utils.go index 8efffb1cd..9b1d67f1f 100644 --- a/pkg/common/utils/utils.go +++ b/pkg/common/utils/utils.go @@ -16,8 +16,8 @@ limitations under the License. package utils import ( - "github.com/deislabs/ratify/pkg/ocispecs" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/ocispecs" ) func OciManifestToReferenceManifest(ociManifest oci.Manifest) ocispecs.ReferenceManifest { diff --git a/pkg/common/utils/utils_test.go b/pkg/common/utils/utils_test.go index 7a35fe980..70931d5dd 100644 --- a/pkg/common/utils/utils_test.go +++ b/pkg/common/utils/utils_test.go @@ -19,8 +19,8 @@ import ( "reflect" "testing" - "github.com/deislabs/ratify/pkg/ocispecs" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/ocispecs" ) const TestArtifactType = "application/vnd.test.artifacttype" diff --git a/pkg/controllers/keymanagementprovider_controller.go b/pkg/controllers/clusterresource/keymanagementprovider_controller.go similarity index 51% rename from pkg/controllers/keymanagementprovider_controller.go rename to pkg/controllers/clusterresource/keymanagementprovider_controller.go index 6d2c6f15e..afb53a904 100644 --- a/pkg/controllers/keymanagementprovider_controller.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller.go @@ -14,15 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" "encoding/json" "fmt" - _ "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider - _ "github.com/deislabs/ratify/pkg/keymanagementprovider/inline" // register inline key management provider + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" // register inline key management provider + "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -30,14 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - c "github.com/deislabs/ratify/config" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" - "github.com/deislabs/ratify/pkg/keymanagementprovider/types" - "github.com/sirupsen/logrus" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" ) // KeyManagementProviderReconciler reconciles a KeyManagementProvider object @@ -46,21 +45,19 @@ type KeyManagementProviderReconciler struct { Scheme *runtime.Scheme } -// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/finalizers,verbs=update -func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *KeyManagementProviderReconciler) ReconcileWithType(ctx context.Context, req ctrl.Request, refresherType string) (ctrl.Result, error) { logger := logrus.WithContext(ctx) - var resource = req.NamespacedName.String() var keyManagementProvider configv1beta1.KeyManagementProvider - logger.Infof("reconciling key management provider '%v'", resource) + resource := req.Name + + logger.Infof("reconciling cluster key management provider '%v'", resource) if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil { if apierrors.IsNotFound(err) { logger.Infof("deletion detected, removing key management provider %v", resource) - keymanagementprovider.DeleteCertificatesFromMap(resource) + kmp.DeleteResourceFromMap(resource) } else { logger.Error(err, "unable to fetch key management provider") } @@ -69,7 +66,6 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr } lastFetchedTime := metav1.Now() - isFetchSuccessful := false // get certificate store list to check if certificate store is configured // TODO: remove check in v2.0.0+ @@ -78,33 +74,70 @@ func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctr logger.Error(err, "unable to list certificate stores") return ctrl.Result{}, err } - // if certificate store is configured, return error. Only one of certificate store and key management provider can be configured + if len(certificateStoreList.Items) > 0 { - err := re.ErrorCodeKeyManagementConflict.WithComponentType(re.KeyManagementProvider).WithPluginName(resource).WithDetail("certificate store already exists") - // Note: for backwards compatibility in upgrade scenarios, Ratify will only log an error. - logger.Error(err) + // Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement. + logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.") } - provider, err := specToKeyManagementProvider(keyManagementProvider.Spec) + provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type) if err != nil { - writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) - return ctrl.Result{}, err + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to create key management provider from CR") + + kmp.SetCertificateError(resource, kmpErr) + kmp.SetKeyError(resource, kmpErr) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr } - certificates, certAttributes, err := provider.GetCertificates(ctx) + refresherConfig := refresh.RefresherConfig{ + RefresherType: refresherType, + Provider: provider, + ProviderType: keyManagementProvider.Spec.Type, + ProviderRefreshInterval: keyManagementProvider.Spec.RefreshInterval, + Resource: resource, + } + + refresher, err := refresh.CreateRefresherFromConfig(refresherConfig) if err != nil { - writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil) - return ctrl.Result{}, fmt.Errorf("Error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err) + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to create refresher from config") + kmp.SetCertificateError(resource, kmpErr) + kmp.SetKeyError(resource, kmpErr) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + err = refresher.Refresh(ctx) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to refresh key management provider") + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + result, ok := refresher.GetResult().(ctrl.Result) + if !ok { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to get result from refresher") + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult()) + } + + status, ok := refresher.GetStatus().(kmp.KeyManagementProviderStatus) + if !ok { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to get status from refresher") + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetStatus: %T", refresher.GetStatus()) } - keymanagementprovider.SetCertificatesInMap(resource, certificates) - isFetchSuccessful = true - emptyErrorString := "" - writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, certAttributes) - logger.Infof("%v certificates fetched for key management provider %v", len(certificates), resource) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, true, nil, lastFetchedTime, status) - // returning empty result and no error to indicate we’ve successfully reconciled this object - return ctrl.Result{}, nil + return result, nil +} + +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/finalizers,verbs=update +func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) } // SetupWithManager sets up the controller with the Manager. @@ -119,44 +152,11 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err Complete(r) } -// specToKeyManagementProvider creates KeyManagementProviderProvider from KeyManagementProviderSpec config -func specToKeyManagementProvider(spec configv1beta1.KeyManagementProviderSpec) (keymanagementprovider.KeyManagementProvider, error) { - kmProviderConfig, err := rawToKeyManagementProviderConfig(spec.Parameters.Raw, spec.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse key management provider config: %w", err) - } - - // TODO: add Version and Address to KeyManagementProviderSpec - keyManagementProviderProvider, err := factory.CreateKeyManagementProviderFromConfig(kmProviderConfig, "0.1.0", c.GetDefaultPluginPath()) - if err != nil { - return nil, fmt.Errorf("failed to create key management provider provider: %w", err) - } - - return keyManagementProviderProvider, nil -} - -// rawToKeyManagementProviderConfig converts raw json to KeyManagementProviderConfig -func rawToKeyManagementProviderConfig(raw []byte, keyManagamentSystemName string) (config.KeyManagementProviderConfig, error) { - pluginConfig := config.KeyManagementProviderConfig{} - - if string(raw) == "" { - return config.KeyManagementProviderConfig{}, fmt.Errorf("no key management provider parameters provided") - } - if err := json.Unmarshal(raw, &pluginConfig); err != nil { - return config.KeyManagementProviderConfig{}, fmt.Errorf("unable to decode key management provider parameters.Raw: %s, err: %w", raw, err) - } - - pluginConfig[types.Type] = keyManagamentSystemName - - return pluginConfig, nil -} - -// writeKMProviderStatus updates the status of the key management provider resource -func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus keymanagementprovider.KeyManagementProviderStatus) { +func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, err *re.Error, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { if isSuccess { updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus) } else { - updateKMProviderErrorStatus(keyManagementProvider, errorString, &operationTime) + updateKMProviderErrorStatus(keyManagementProvider, err, &operationTime) } if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { logger.Error(statusErr, ",unable to update key management provider error status") @@ -164,21 +164,16 @@ func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManage } // updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time -func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, errorString string, operationTime *metav1.Time) { - // truncate brief error string to maxBriefErrLength - briefErr := errorString - if len(errorString) > maxBriefErrLength { - briefErr = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) - } +func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, err *re.Error, operationTime *metav1.Time) { keyManagementProvider.Status.IsSuccess = false - keyManagementProvider.Status.Error = errorString - keyManagementProvider.Status.BriefError = briefErr + keyManagementProvider.Status.Error = err.Error() + keyManagementProvider.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) keyManagementProvider.Status.LastFetchedTime = operationTime } // updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil // Success status includes last fetched time and other provider-specific properties -func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus keymanagementprovider.KeyManagementProviderStatus) { +func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { keyManagementProvider.Status.IsSuccess = true keyManagementProvider.Status.Error = "" keyManagementProvider.Status.BriefError = "" diff --git a/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go new file mode 100644 index 000000000..60254eeef --- /dev/null +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go @@ -0,0 +1,548 @@ +/* +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 clusterresource + +import ( + "context" + "errors" + "fmt" + "reflect" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + + re "github.com/ratify-project/ratify/errors" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func init() { + refresh.Register("mockRefresher", &MockRefresher{}) +} + +type MockRefresher struct { + Result ctrl.Result + RefreshError bool + ResultError bool + StatusError bool + Status kmp.KeyManagementProviderStatus +} + +func (mr *MockRefresher) Refresh(_ context.Context) error { + if mr.RefreshError { + return errors.New("error from refresh") + } + return nil +} + +func (mr *MockRefresher) GetResult() interface{} { + if mr.ResultError { + return errors.New("error from result") + } + return mr.Result +} + +func (mr *MockRefresher) GetStatus() interface{} { + if mr.StatusError { + return errors.New("error from status") + } + return mr.Status +} + +func (mr *MockRefresher) Create(config refresh.RefresherConfig) (refresh.Refresher, error) { + if config.Resource == "refreshError" { + return &MockRefresher{ + RefreshError: true, + }, nil + } + if config.Resource == "resultError" { + return &MockRefresher{ + ResultError: true, + }, nil + } + if config.Resource == "statusError" { + return &MockRefresher{ + StatusError: true, + }, nil + } + return &MockRefresher{}, nil +} + +func TestKeyManagementProviderReconciler_ReconcileWithType(t *testing.T) { + tests := []struct { + name string + clientGetFunc func(_ context.Context, key types.NamespacedName, obj client.Object) error + clientListFunc func(_ context.Context, list client.ObjectList) error + resourceName string + refresherType string + expectedResult ctrl.Result + expectedError bool + }{ + { + // TODO: Add SetLogger to internal/logger/logger.go to compare log messages + // https://maxchadwick.xyz/blog/testing-log-output-in-go-logrus + name: "api is not found", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + resource := schema.GroupResource{ + Group: "", // Use an empty string for core resources (like Pod) + Resource: "pods", // Resource type, e.g., "pods" for Pod resources + } + return apierrors.NewNotFound(resource, "test") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to fetch key management provider", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return fmt.Errorf("unable to fetch key management provider") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to list certificate stores", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return fmt.Errorf("unable to list certificate stores") + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "certificate store already exists", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, list client.ObjectList) error { + certStoreList, ok := list.(*configv1beta1.CertificateStoreList) + if !ok { + return errors.New("expected CertificateStoreList") + } + + certStoreList.Items = []configv1beta1.CertificateStore{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }, + } + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "cutils.SpecToKeyManagementProvider failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.CreateRefresherFromConfig failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "invalidRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.Refresh failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "refreshError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetResult failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "resultError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetStatus failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceName: "statusError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "successfully reconciled", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.KeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.KeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + } + + for _, tt := range tests { + fmt.Println(tt.name) + mockClient := mocks.TestClient{ + GetFunc: tt.clientGetFunc, + ListFunc: tt.clientListFunc, + } + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: tt.resourceName, + Namespace: "test", + }, + } + scheme, _ := test.CreateScheme() + + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } + + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) + + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Fatalf("Expected result %v, got %v", tt.expectedResult, result) + } + if tt.expectedError && err == nil { + t.Fatalf("Expected error, got nil") + } + } +} + +func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: "fake-name", + Namespace: "fake-namespace", + }, + } + + // Create a fake client and scheme + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme).Build() + + r := &KeyManagementProviderReconciler{ + Client: client, + Scheme: runtime.NewScheme(), + } + + // Call the Reconcile method + result, err := r.Reconcile(context.TODO(), req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Check the result + expectedResult := ctrl.Result{} + if !reflect.DeepEqual(result, expectedResult) { + t.Errorf("Expected result %v, got %v", expectedResult, result) + } +} + +// TestUpdateErrorStatus tests the updateErrorStatus method +func TestKMProviderUpdateErrorStatus(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) + + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: true, + Properties: runtime.RawExtension{ + Raw: kmProviderStatus, + }, + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + Status: status, + } + expectedErr := re.ErrorCodeUnknown.WithDetail("it's a long error from unit test") + lastFetchedTime := metav1.Now() + updateKMProviderErrorStatus(&keyManagementProvider, &expectedErr, &lastFetchedTime) + + if keyManagementProvider.Status.IsSuccess != false { + t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != expectedErr.Error() { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) + } + if keyManagementProvider.Status.BriefError != expectedErr.GetConciseError(150) { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr.GetConciseError(150), keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was not overridden + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Unexpected properties, expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw)) + } +} + +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method +func TestKMProviderUpdateSuccessStatus(t *testing.T) { + kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{} + properties := map[string]string{} + properties["Name"] = "wabbit" + properties["Version"] = "ABC" + + kmProviderStatus["Certificates"] = properties + + lastFetchedTime := metav1.Now() + + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Properties should not be empty") + } +} + +// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties +func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { + lastFetchedTime := metav1.Now() + status := configv1beta1.KeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.KeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) != 0 { + t.Fatalf("Properties should be empty") + } +} +func TestWriteKMProviderStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + lastFetchedTime := metav1.Now() + testCases := []struct { + name string + isSuccess bool + kmProvider *configv1beta1.KeyManagementProvider + errString string + expectedErrString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + kmProvider: &configv1beta1.KeyManagementProvider{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + kmProvider: &configv1beta1.KeyManagementProvider{}, + errString: "a long error string that exceeds the max length of 150 characters", + expectedErrString: "UNKNOWN: a long error string that exceeds the max length of 150 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + kmProvider: &configv1beta1.KeyManagementProvider{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, &err, lastFetchedTime, nil) + + if tc.kmProvider.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be: %+v, actual: %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) + } + + if tc.kmProvider.Status.Error != tc.expectedErrString { + t.Fatalf("Expected Error to be: %+v, actual: %+v", tc.expectedErrString, tc.kmProvider.Status.Error) + } + }) + } +} diff --git a/pkg/controllers/policy_controller.go b/pkg/controllers/clusterresource/policy_controller.go similarity index 50% rename from pkg/controllers/policy_controller.go rename to pkg/controllers/clusterresource/policy_controller.go index 5f29a55e1..f511a50f1 100644 --- a/pkg/controllers/policy_controller.go +++ b/pkg/controllers/clusterresource/policy_controller.go @@ -13,18 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" - "encoding/json" "fmt" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/controllers/utils" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -38,17 +37,6 @@ type PolicyReconciler struct { Scheme *runtime.Scheme } -type policy struct { - // The name of the policy. - Name string - // The policy enforcer making a decision. - Enforcer policyprovider.PolicyProvider -} - -// ActivePolicy is the active policy generated from CRD. There would be exactly -// one active policy at any given time. -var ActivePolicy policy - //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies/status,verbs=get;update;patch //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=policies/finalizers,verbs=update @@ -63,12 +51,12 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr var policy configv1beta1.Policy var resource = req.Name - policyLogger.Infof("Reconciling Policy %s", resource) + policyLogger.Infof("Reconciling Cluster Policy %s", resource) if err := r.Get(ctx, req.NamespacedName, &policy); err != nil { - if apierrors.IsNotFound(err) && resource == ActivePolicy.Name { + if apierrors.IsNotFound(err) { policyLogger.Infof("delete event detected, removing policy %s", resource) - ActivePolicy.deletePolicy(resource) + controllers.NamespacedPolicies.DeletePolicy(constants.EmptyNamespace, resource) } else { policyLogger.Error("failed to get Policy: ", err) } @@ -76,19 +64,20 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr } if resource != constants.RatifyPolicy { - errStr := fmt.Sprintf("metadata.name must be ratify-policy, got %s", resource) - policyLogger.Error(errStr) - writePolicyStatus(ctx, r, &policy, policyLogger, false, errStr) + err := re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("metadata.name must be ratify-policy, got %s", resource)) + policyLogger.Error(err) + writePolicyStatus(ctx, r, &policy, policyLogger, false, &err) return ctrl.Result{}, nil } if err := policyAddOrReplace(policy.Spec); err != nil { - policyLogger.Error("unable to create policy from policy crd: ", err) - writePolicyStatus(ctx, r, &policy, policyLogger, false, err.Error()) - return ctrl.Result{}, err + policyErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Unable to create policy from policy CR") + policyLogger.Error(policyErr) + writePolicyStatus(ctx, r, &policy, policyLogger, false, &policyErr) + return ctrl.Result{}, policyErr } - writePolicyStatus(ctx, r, &policy, policyLogger, true, "") + writePolicyStatus(ctx, r, &policy, policyLogger, true, nil) return ctrl.Result{}, nil } @@ -100,64 +89,20 @@ func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { } func policyAddOrReplace(spec configv1beta1.PolicySpec) error { - policyEnforcer, err := specToPolicyEnforcer(spec) + policyEnforcer, err := utils.SpecToPolicyEnforcer(spec.Parameters.Raw, spec.Type) if err != nil { - return fmt.Errorf("failed to create policy enforcer: %w", err) + return err } - ActivePolicy.Name = spec.Type - ActivePolicy.Enforcer = policyEnforcer + controllers.NamespacedPolicies.AddPolicy(constants.EmptyNamespace, constants.RatifyPolicy, policyEnforcer) return nil } -func specToPolicyEnforcer(spec configv1beta1.PolicySpec) (policyprovider.PolicyProvider, error) { - policyConfig, err := rawToPolicyConfig(spec.Parameters.Raw, spec.Type) - if err != nil { - return nil, fmt.Errorf("failed to parse policy config: %w", err) - } - - policyEnforcer, err := pf.CreatePolicyProviderFromConfig(policyConfig) - if err != nil { - return nil, fmt.Errorf("failed to create policy provider: %w", err) - } - - return policyEnforcer, nil -} - -func rawToPolicyConfig(raw []byte, policyName string) (config.PoliciesConfig, error) { - pluginConfig := config.PolicyPluginConfig{} - - if string(raw) == "" { - return config.PoliciesConfig{}, fmt.Errorf("no policy parameters provided") - } - if err := json.Unmarshal(raw, &pluginConfig); err != nil { - return config.PoliciesConfig{}, fmt.Errorf("unable to decode policy parameters.Raw: %s, err: %w", raw, err) - } - - pluginConfig["name"] = policyName - - return config.PoliciesConfig{ - PolicyPlugin: pluginConfig, - }, nil -} - -func (p *policy) deletePolicy(resource string) { - if p.Name == resource { - p.Name = "" - p.Enforcer = nil - } -} - -// IsEmpty returns true if there is no policy set up. -func (p *policy) IsEmpty() bool { - return p.Name == "" && p.Enforcer == nil -} - -func writePolicyStatus(ctx context.Context, r client.StatusClient, policy *configv1beta1.Policy, logger *logrus.Entry, isSuccess bool, errString string) { +func writePolicyStatus(ctx context.Context, r client.StatusClient, policy *configv1beta1.Policy, logger *logrus.Entry, isSuccess bool, err *re.Error) { if isSuccess { updatePolicySuccessStatus(policy) } else { - updatePolicyErrorStatus(policy, errString) + updatePolicyErrorStatus(policy, err) } if statusErr := r.Status().Update(ctx, policy); statusErr != nil { logger.Error(statusErr, ", unable to update policy error status") @@ -170,12 +115,8 @@ func updatePolicySuccessStatus(policy *configv1beta1.Policy) { policy.Status.BriefError = "" } -func updatePolicyErrorStatus(policy *configv1beta1.Policy, errString string) { - briefErr := errString - if len(errString) > maxBriefErrLength { - briefErr = fmt.Sprintf("%s...", errString[:maxBriefErrLength]) - } +func updatePolicyErrorStatus(policy *configv1beta1.Policy, err *re.Error) { policy.Status.IsSuccess = false - policy.Status.Error = errString - policy.Status.BriefError = briefErr + policy.Status.Error = err.Error() + policy.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) } diff --git a/pkg/controllers/clusterresource/policy_controller_test.go b/pkg/controllers/clusterresource/policy_controller_test.go new file mode 100644 index 000000000..909c702b2 --- /dev/null +++ b/pkg/controllers/clusterresource/policy_controller_test.go @@ -0,0 +1,242 @@ +/* +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 clusterresource + +import ( + "context" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/policies" + _ "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + policyName1 = "policy1" + policyName2 = "policy2" +) + +func TestPolicyAddOrReplace(t *testing.T) { + testCases := []struct { + name string + spec configv1beta1.PolicySpec + policyName string + expectErr bool + }{ + { + name: "invalid spec", + spec: configv1beta1.PolicySpec{ + Type: policyName1, + }, + expectErr: true, + }, + { + name: "valid spec", + spec: configv1beta1.PolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := policyAddOrReplace(tc.spec) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +func TestWritePolicyStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + policy *configv1beta1.Policy + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + policy: &configv1beta1.Policy{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + policy: &configv1beta1.Policy{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + policy: &configv1beta1.Policy{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(_ *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, &err) + }) + } +} + +func TestPolicyReconcile(t *testing.T) { + tests := []struct { + name string + policy *configv1beta1.Policy + req *reconcile.Request + expectedErr bool + expectedPolicy bool + }{ + { + name: "nonexistent policy", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: policyName1, + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "no policy parameters provided", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "wrong policy name", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy2", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "invalid params", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "valid params", + policy: &configv1beta1.Policy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "ratify-policy", + }, + Spec: configv1beta1.PolicySpec{ + Type: "configpolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"passthroughEnabled:\": false}"), + }, + }, + }, + expectedErr: false, + expectedPolicy: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetPolicyMap() + scheme, err := test.CreateScheme() + if err != nil { + t.Fatalf("CreateScheme() expected no error, actual %v", err) + } + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.policy) + r := &PolicyReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.policy), + } + } + _, err = r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error to be %t, actual %t", tt.expectedErr, err != nil) + } + + policy := controllers.NamespacedPolicies.GetPolicy(constants.EmptyNamespace) + if (policy != nil) != tt.expectedPolicy { + t.Fatalf("Expected policy to be %t, got %t", tt.expectedPolicy, policy != nil) + } + }) + } +} + +func resetPolicyMap() { + controllers.NamespacedPolicies = policies.NewActivePolicies() +} diff --git a/pkg/controllers/store_controller.go b/pkg/controllers/clusterresource/store_controller.go similarity index 53% rename from pkg/controllers/store_controller.go rename to pkg/controllers/clusterresource/store_controller.go index b4421a6ef..65342b953 100644 --- a/pkg/controllers/store_controller.go +++ b/pkg/controllers/clusterresource/store_controller.go @@ -13,24 +13,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package clusterresource import ( "context" - "encoding/json" - "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/pkg/referrerstore" - rc "github.com/deislabs/ratify/pkg/referrerstore/config" - sf "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/referrerstore/types" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/controllers/utils" "github.com/sirupsen/logrus" ) @@ -40,11 +37,6 @@ type StoreReconciler struct { Scheme *runtime.Scheme } -var ( - // a map to track active stores - StoreMap = map[string]referrerstore.ReferrerStore{} -) - //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=stores,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=stores/status,verbs=get;update;patch //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=stores/finalizers,verbs=update @@ -59,12 +51,12 @@ func (r *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl var store configv1beta1.Store var resource = req.Name - storeLogger.Infof("reconciling store '%v'", resource) + storeLogger.Infof("reconciling cluster store '%v'", resource) if err := r.Get(ctx, req.NamespacedName, &store); err != nil { if apierrors.IsNotFound(err) { storeLogger.Infof("deletion detected, removing store %v", req.Name) - storeRemove(resource) + controllers.NamespacedStores.DeleteStore(constants.EmptyNamespace, resource) } else { storeLogger.Error(err, "unable to fetch store") } @@ -73,12 +65,13 @@ func (r *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } if err := storeAddOrReplace(store.Spec, resource); err != nil { - storeLogger.Error(err, "unable to create store from store crd") - writeStoreStatus(ctx, r, &store, storeLogger, false, err.Error()) - return ctrl.Result{}, err + storeErr := re.ErrorCodeReferrerStoreFailure.WithError(err).WithDetail("Unable to create store from store CR") + storeLogger.Error(err) + writeStoreStatus(ctx, r, &store, storeLogger, false, &storeErr) + return ctrl.Result{}, storeErr } - writeStoreStatus(ctx, r, &store, storeLogger, true, "") + writeStoreStatus(ctx, r, &store, storeLogger, true, nil) // returning empty result and no error to indicate we’ve successfully reconciled this object return ctrl.Result{}, nil @@ -93,68 +86,23 @@ func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager) error { // Creates a store reference from CRD spec and add store to map func storeAddOrReplace(spec configv1beta1.StoreSpec, fullname string) error { - storeConfig, err := specToStoreConfig(spec) + storeConfig, err := utils.CreateStoreConfig(spec.Parameters.Raw, spec.Name, spec.Source) if err != nil { - return fmt.Errorf("unable to convert store spec to store config, err: %w", err) - } - - // if the default version is not suitable, the plugin configuration should specify the desired version - if len(spec.Version) == 0 { - spec.Version = config.GetDefaultPluginVersion() - logrus.Infof("Version was empty, setting to default version: %v", spec.Version) - } - - if spec.Address == "" { - spec.Address = config.GetDefaultPluginPath() - logrus.Infof("Address was empty, setting to default path %v", spec.Address) - } - storeReference, err := sf.CreateStoreFromConfig(storeConfig, spec.Version, []string{spec.Address}) - - if err != nil || storeReference == nil { - logrus.Error(err, "store factory failed to create store from store config") - return fmt.Errorf("store factory failed to create store from store config, err: %w", err) + return err } - StoreMap[fullname] = storeReference - logrus.Infof("store '%v' added to store map", storeReference.Name()) - - return nil + return utils.UpsertStoreMap(spec.Version, spec.Address, fullname, constants.EmptyNamespace, storeConfig) } -// Remove store from map -func storeRemove(resourceName string) { - delete(StoreMap, resourceName) -} - -// Returns a store reference from spec -func specToStoreConfig(storeSpec configv1beta1.StoreSpec) (rc.StorePluginConfig, error) { - storeConfig := rc.StorePluginConfig{} - - if string(storeSpec.Parameters.Raw) != "" { - if err := json.Unmarshal(storeSpec.Parameters.Raw, &storeConfig); err != nil { - logrus.Error(err, "unable to decode store parameters", "Parameters.Raw", storeSpec.Parameters.Raw) - return rc.StorePluginConfig{}, err - } - } - storeConfig[types.Name] = storeSpec.Name - if storeSpec.Source != nil { - storeConfig[types.Source] = storeSpec.Source - } - - return storeConfig, nil -} - -func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv1beta1.Store, logger *logrus.Entry, isSuccess bool, errorString string) { +func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv1beta1.Store, logger *logrus.Entry, isSuccess bool, err *re.Error) { if isSuccess { store.Status.IsSuccess = true store.Status.Error = "" store.Status.BriefError = "" } else { store.Status.IsSuccess = false - store.Status.Error = errorString - if len(errorString) > maxBriefErrLength { - store.Status.BriefError = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) - } + store.Status.Error = err.Error() + store.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) } if statusErr := r.Status().Update(ctx, store); statusErr != nil { diff --git a/pkg/controllers/clusterresource/store_controller_test.go b/pkg/controllers/clusterresource/store_controller_test.go new file mode 100644 index 000000000..fbd09341d --- /dev/null +++ b/pkg/controllers/clusterresource/store_controller_test.go @@ -0,0 +1,312 @@ +/* +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 clusterresource + +import ( + "context" + "os" + "strings" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + rs "github.com/ratify-project/ratify/pkg/customresources/referrerstores" + "github.com/ratify-project/ratify/pkg/utils" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + storeName = "testStore" + testNamespace = "testNamespace" + sampleName = "sample" + orasName = "oras" +) + +func TestStoreAdd_EmptyParameter(t *testing.T) { + resetStoreMap() + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = configv1beta1.StoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(testStoreSpec, "oras"); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + stores := controllers.NamespacedStores.GetStores(constants.EmptyNamespace) + if len(stores) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(stores)) + } +} + +func TestStoreAdd_WithParameters(t *testing.T) { + resetStoreMap() + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + + if err := storeAddOrReplace(testStoreSpec, "testObject"); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } +} + +func TestWriteStoreStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + store *configv1beta1.Store + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + store: &configv1beta1.Store{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + store: &configv1beta1.Store{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + store: &configv1beta1.Store{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(_ *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeStoreStatus(context.Background(), tc.reconciler, tc.store, logger, tc.isSuccess, &err) + }) + } +} + +func TestStoreAddOrReplace_PluginNotFound(t *testing.T) { + resetStoreMap() + var resource = "invalidplugin" + expectedMsg := "plugin not found" + var spec = getInvalidStoreSpec() + err := storeAddOrReplace(spec, resource) + + if !strings.Contains(err.Error(), expectedMsg) { + t.Fatalf("TestStoreAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestStore_UpdateAndDelete(t *testing.T) { + resetStoreMap() + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + // add a Store + if err := storeAddOrReplace(testStoreSpec, sampleName); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + + // modify the Store + var updatedSpec = configv1beta1.StoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(updatedSpec, sampleName); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + + // validate no Store has been added + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 1 { + t.Fatalf("Store map should be 1 after replacement, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + + controllers.NamespacedStores.DeleteStore(constants.EmptyNamespace, sampleName) + + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != 0 { + t.Fatalf("Store map should be 0 after deletion, actual %v", len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } +} + +func TestStoreReconcile(t *testing.T) { + dirPath, err := test.CreatePlugin(orasName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + store *configv1beta1.Store + req *reconcile.Request + expectedErr bool + expectedStoreCount int + }{ + { + name: "nonexistent store", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Name: orasName, + }, + }, + expectedErr: false, + expectedStoreCount: 0, + }, + { + name: "valid spec", + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Name: orasName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedStoreCount: 1, + }, + { + name: "invalid parameters", + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, + { + name: "unsupported store", + store: &configv1beta1.Store{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: constants.EmptyNamespace, + Name: storeName, + }, + Spec: configv1beta1.StoreSpec{ + Name: "unsupported", + Address: dirPath, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetStoreMap() + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.store) + r := &StoreReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.store), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace)) != tt.expectedStoreCount { + t.Fatalf("Store map expected size %v, actual %v", tt.expectedStoreCount, len(controllers.NamespacedStores.GetStores(constants.EmptyNamespace))) + } + }) + } +} + +func resetStoreMap() { + controllers.NamespacedStores = rs.NewActiveStores() +} + +func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.StoreSpec { + var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}]},\"cosignEnabled\":false,\"useHttp\":false}" + var storeParameters = []byte(parametersString) + + return configv1beta1.StoreSpec{ + Name: pluginName, + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: storeParameters, + }, + } +} + +func getInvalidStoreSpec() configv1beta1.StoreSpec { + return configv1beta1.StoreSpec{ + Name: "pluginnotfound", + Address: "test/path", + } +} diff --git a/pkg/controllers/clusterresource/verifier_controller.go b/pkg/controllers/clusterresource/verifier_controller.go new file mode 100644 index 000000000..f752fa154 --- /dev/null +++ b/pkg/controllers/clusterresource/verifier_controller.go @@ -0,0 +1,119 @@ +/* +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 clusterresource + +import ( + "context" + "fmt" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// VerifierReconciler reconciles a Verifier object +type VerifierReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Verifier object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + verifierLogger := logrus.WithContext(ctx) + + var verifier configv1beta1.Verifier + var resource = req.Name + + verifierLogger.Infof("reconciling verifier '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &verifier); err != nil { + if apierrors.IsNotFound(err) { + verifierLogger.Infof("delete event detected, removing verifier %v", resource) + controllers.NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, resource) + } else { + verifierLogger.Error(err, "unable to fetch verifier") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := verifierAddOrReplace(verifier.Spec, resource); err != nil { + verifierErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Unable to create verifier from verifier CR") + verifierLogger.Error(verifierErr) + writeVerifierStatus(ctx, r, &verifier, verifierLogger, false, &verifierErr) + return ctrl.Result{}, verifierErr + } + + writeVerifierStatus(ctx, r, &verifier, verifierLogger, true, nil) + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// creates a verifier reference from CR spec and add verifier to map +func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string) error { + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, cutils.GetVerifierType(spec), spec.ArtifactTypes, spec.Source) + if err != nil { + errMsg := fmt.Sprintf("Unable to apply cluster-wide resource %s of Verifier kind", objectName) + logrus.Error(err, errMsg) + return re.ErrorCodeConfigInvalid.WithDetail(errMsg).WithError(err) + } + + return cutils.UpsertVerifier(spec.Version, spec.Address, constants.EmptyNamespace, objectName, verifierConfig) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VerifierReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.Verifier{}). + Complete(r) +} + +func writeVerifierStatus(ctx context.Context, r client.StatusClient, verifier *configv1beta1.Verifier, logger *logrus.Entry, isSuccess bool, err *re.Error) { + if isSuccess { + verifier.Status.IsSuccess = true + verifier.Status.Error = "" + verifier.Status.BriefError = "" + } else { + verifier.Status.IsSuccess = false + verifier.Status.Error = err.Error() + verifier.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) + } + + if statusErr := r.Status().Update(ctx, verifier); statusErr != nil { + logger.Error(statusErr, ",unable to update verifier status") + } +} diff --git a/pkg/controllers/clusterresource/verifier_controller_test.go b/pkg/controllers/clusterresource/verifier_controller_test.go new file mode 100644 index 000000000..96cfb5956 --- /dev/null +++ b/pkg/controllers/clusterresource/verifier_controller_test.go @@ -0,0 +1,380 @@ +/* +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 clusterresource + +import ( + "context" + "errors" + "os" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/verifiers" + "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +type mockResourceWriter struct { + updateFailed bool +} + +func (w mockResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (w mockResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if w.updateFailed { + return errors.New("update failed") + } + return nil +} + +func (w mockResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +type mockStatusClient struct { + updateFailed bool +} + +func (c mockStatusClient) Status() client.SubResourceWriter { + writer := mockResourceWriter{} + writer.updateFailed = c.updateFailed + return writer +} + +const ( + licenseChecker = "licensechecker" + verifierName = "verifierName" +) + +func TestMain(m *testing.M) { + // make sure to reset verifierMap before each test run + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() + code := m.Run() + os.Exit(code) +} + +func TestVerifierAdd_EmptyParameter(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = configv1beta1.VerifierSpec{ + Name: sampleName, + ArtifactTypes: "application/vnd.cncf.notary.signature", + Address: dirPath, + } + + if err := verifierAddOrReplace(testVerifierSpec, sampleName); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } +} + +func TestVerifierAdd_WithParameters(t *testing.T) { + resetVerifierMap() + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } + + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + if err := verifierAddOrReplace(testVerifierSpec, "testObject"); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } +} + +func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { + resetVerifierMap() + resource := "invalidplugin" + expectedMsg := "PLUGIN_NOT_FOUND: Verifier plugin pluginnotfound not found: failed to find plugin \"pluginnotfound\" in paths [test/path]: Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured." + var testVerifierSpec = getInvalidVerifierSpec() + err := verifierAddOrReplace(testVerifierSpec, resource) + + if err.Error() != expectedMsg { + t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestVerifierAdd_InvalidConfig(t *testing.T) { + resetVerifierMap() + var testVerifierSpec = configv1beta1.VerifierSpec{ + Name: "notation", + ArtifactTypes: "application/vnd.cncf.notary.signature", + Parameters: runtime.RawExtension{ + Raw: []byte("test\n"), + }, + } + var resource = "notation" + + if err := verifierAddOrReplace(testVerifierSpec, resource); err == nil { + t.Fatalf("Expected an error but returned nil") + } +} + +func TestVerifier_UpdateAndDelete(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + // add a verifier + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } + + // modify the verifier + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"GNU\"]}" + testVerifierSpec = getLicenseCheckerFromParam(parametersString, dirPath) + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + + // validate no verifier has been added + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 1 { + t.Fatalf("Verifier map should be 1 after replacement, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } + + controllers.NamespacedVerifiers.DeleteVerifier(constants.EmptyNamespace, licenseChecker) + + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != 0 { + t.Fatalf("Verifier map should be 0 after deletion, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } +} + +func TestWriteVerifierStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + verifier *configv1beta1.Verifier + errString string + expectedErrString string + expectedBriefErrString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + verifier: &configv1beta1.Verifier{}, + reconciler: &mockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + verifier: &configv1beta1.Verifier{}, + errString: "a long error string that exceeds the max length of 100 characters, a long error string that exceeds the max length of 100 characters", + expectedErrString: "UNKNOWN: a long error string that exceeds the max length of 100 characters, a long error string that exceeds the max length of 100 characters", + expectedBriefErrString: "UNKNOWN: a long error string that exceeds the max length of 100 characters, a long error string t...", + reconciler: &mockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + verifier: &configv1beta1.Verifier{}, + reconciler: &mockStatusClient{ + updateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ratifyErr := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeVerifierStatus(context.Background(), tc.reconciler, tc.verifier, logger, tc.isSuccess, &ratifyErr) + + if tc.verifier.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.verifier.Status.IsSuccess) + } + + if tc.verifier.Status.Error != tc.expectedErrString { + t.Fatalf("Expected Error to be %+v , actual %+v", tc.expectedErrString, tc.verifier.Status.Error) + } + + if tc.verifier.Status.BriefError != tc.expectedBriefErrString { + t.Fatalf("Expected BriefError to be %+v , actual %+v", tc.expectedBriefErrString, tc.verifier.Status.BriefError) + } + }) + } +} + +func TestVerifierReconcile(t *testing.T) { + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + verifier *configv1beta1.Verifier + req *reconcile.Request + expectedErr bool + expectedVerifierCount int + }{ + { + name: "nonexistent verifier", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + }, + expectedErr: false, + expectedVerifierCount: 0, + }, + { + name: "invalid parameters", + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + Spec: configv1beta1.VerifierSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "unsupported verifier", + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + Spec: configv1beta1.VerifierSpec{ + Name: "unsupported", + Address: dirPath, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "valid spec", + verifier: &configv1beta1.Verifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: verifierName, + }, + Spec: configv1beta1.VerifierSpec{ + Name: sampleName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedVerifierCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + scheme, _ := utils.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.verifier) + r := &VerifierReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: utils.KeyFor(tt.verifier), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace)) != tt.expectedVerifierCount { + t.Fatalf("Verifier map expected size %v, actual %v", tt.expectedVerifierCount, len(controllers.NamespacedVerifiers.GetVerifiers(constants.EmptyNamespace))) + } + }) + } +} + +func resetVerifierMap() { + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() +} + +func getLicenseCheckerFromParam(parametersString, pluginPath string) configv1beta1.VerifierSpec { + var allowedLicenses = []byte(parametersString) + + return configv1beta1.VerifierSpec{ + Name: licenseChecker, + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: allowedLicenses, + }, + } +} + +func getInvalidVerifierSpec() configv1beta1.VerifierSpec { + return configv1beta1.VerifierSpec{ + Name: "pluginnotfound", + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: "test/path", + } +} + +func getDefaultLicenseCheckerSpec(pluginPath string) configv1beta1.VerifierSpec { + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"Apache\"]}" + return getLicenseCheckerFromParam(parametersString, pluginPath) +} diff --git a/pkg/controllers/constants.go b/pkg/controllers/constants.go deleted file mode 100644 index 922ab6ef0..000000000 --- a/pkg/controllers/constants.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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 controllers - -const maxBriefErrLength = 30 diff --git a/pkg/controllers/keymanagementprovider_controller_test.go b/pkg/controllers/keymanagementprovider_controller_test.go deleted file mode 100644 index cb746f416..000000000 --- a/pkg/controllers/keymanagementprovider_controller_test.go +++ /dev/null @@ -1,267 +0,0 @@ -/* -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 controllers - -import ( - "context" - "fmt" - "reflect" - "testing" - - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/sirupsen/logrus" - "sigs.k8s.io/controller-runtime/pkg/client" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// TestUpdateErrorStatus tests the updateErrorStatus method -func TestKMProviderUpdateErrorStatus(t *testing.T) { - var parametersString = "{\"certs\":{\"name\":\"certName\"}}" - var kmProviderStatus = []byte(parametersString) - - status := configv1beta1.KeyManagementProviderStatus{ - IsSuccess: true, - Properties: runtime.RawExtension{ - Raw: kmProviderStatus, - }, - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - Status: status, - } - expectedErr := "it's a long error from unit test" - lastFetchedTime := metav1.Now() - updateKMProviderErrorStatus(&keyManagementProvider, expectedErr, &lastFetchedTime) - - if keyManagementProvider.Status.IsSuccess != false { - t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess) - } - - if keyManagementProvider.Status.Error != expectedErr { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) - } - expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30]) - if keyManagementProvider.Status.BriefError != expectedBriedErr { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error) - } - - //make sure properties of last cached cert was not overridden - if len(keyManagementProvider.Status.Properties.Raw) == 0 { - t.Fatalf("Unexpected properties, expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw)) - } -} - -// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method -func TestKMProviderUpdateSuccessStatus(t *testing.T) { - kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{} - properties := map[string]string{} - properties["CertName"] = "wabbit" - properties["Version"] = "ABC" - - kmProviderStatus["Certificates"] = properties - - lastFetchedTime := metav1.Now() - - status := configv1beta1.KeyManagementProviderStatus{ - IsSuccess: false, - Error: "error from last operation", - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - Status: status, - } - - updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus) - - if keyManagementProvider.Status.IsSuccess != true { - t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) - } - - if keyManagementProvider.Status.Error != "" { - t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) - } - - //make sure properties of last cached cert was updated - if len(keyManagementProvider.Status.Properties.Raw) == 0 { - t.Fatalf("Properties should not be empty") - } -} - -// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties -func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) { - lastFetchedTime := metav1.Now() - status := configv1beta1.KeyManagementProviderStatus{ - IsSuccess: false, - Error: "error from last operation", - } - keyManagementProvider := configv1beta1.KeyManagementProvider{ - Status: status, - } - - updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil) - - if keyManagementProvider.Status.IsSuccess != true { - t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) - } - - if keyManagementProvider.Status.Error != "" { - t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) - } - - //make sure properties of last cached cert was updated - if len(keyManagementProvider.Status.Properties.Raw) != 0 { - t.Fatalf("Properties should be empty") - } -} - -// TestRawToKeyManagementProviderConfig tests the rawToKeyManagementProviderConfig method -func TestRawToKeyManagementProviderConfig(t *testing.T) { - testCases := []struct { - name string - raw []byte - expectErr bool - expectConfig config.KeyManagementProviderConfig - }{ - { - name: "empty Raw", - raw: []byte{}, - expectErr: true, - expectConfig: config.KeyManagementProviderConfig{}, - }, - { - name: "unmarshal failure", - raw: []byte("invalid"), - expectErr: true, - expectConfig: config.KeyManagementProviderConfig{}, - }, - { - name: "valid Raw", - raw: []byte("{\"type\": \"inline\"}"), - expectErr: false, - expectConfig: config.KeyManagementProviderConfig{ - "type": "inline", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - config, err := rawToKeyManagementProviderConfig(tc.raw, "inline") - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - if !reflect.DeepEqual(config, tc.expectConfig) { - t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) - } - }) - } -} - -// TestSpecToKeyManagementProviderProvider tests the specToKeyManagementProviderProvider method -func TestSpecToKeyManagementProviderProvider(t *testing.T) { - testCases := []struct { - name string - spec configv1beta1.KeyManagementProviderSpec - expectErr bool - }{ - { - name: "empty spec", - spec: configv1beta1.KeyManagementProviderSpec{}, - expectErr: true, - }, - { - name: "missing inline provider required fields", - spec: configv1beta1.KeyManagementProviderSpec{ - Type: "inline", - Parameters: runtime.RawExtension{ - Raw: []byte("{\"type\": \"inline\"}"), - }, - }, - expectErr: true, - }, - { - name: "valid spec", - spec: configv1beta1.KeyManagementProviderSpec{ - Type: "inline", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), - }, - }, - expectErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - _, err := specToKeyManagementProvider(tc.spec) - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - }) - } -} - -func TestWriteKMProviderStatus(t *testing.T) { - logger := logrus.WithContext(context.Background()) - lastFetchedTime := metav1.Now() - testCases := []struct { - name string - isSuccess bool - kmProvider *configv1beta1.KeyManagementProvider - errString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - errString: "", - kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &mockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - kmProvider: &configv1beta1.KeyManagementProvider{}, - errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &mockStatusClient{ - updateFailed: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil) - - if tc.kmProvider.Status.IsSuccess != tc.isSuccess { - t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) - } - - if tc.kmProvider.Status.Error != tc.errString { - t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error) - } - }) - } -} diff --git a/pkg/controllers/certificatestore_controller.go b/pkg/controllers/namespaceresource/certificatestore_controller.go similarity index 88% rename from pkg/controllers/certificatestore_controller.go rename to pkg/controllers/namespaceresource/certificatestore_controller.go index 8615b3b79..20ca16467 100644 --- a/pkg/controllers/certificatestore_controller.go +++ b/pkg/controllers/namespaceresource/certificatestore_controller.go @@ -11,19 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package controllers +package namespaceresource import ( "context" - "crypto/x509" "encoding/json" "fmt" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/certificateprovider" - _ "github.com/deislabs/ratify/pkg/certificateprovider/azurekeyvault" // register azure keyvault certificate provider - _ "github.com/deislabs/ratify/pkg/certificateprovider/inline" // register inline certificate provider - "github.com/deislabs/ratify/pkg/utils" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/certificateprovider" + _ "github.com/ratify-project/ratify/pkg/certificateprovider/azurekeyvault" // register azure keyvault certificate provider + _ "github.com/ratify-project/ratify/pkg/certificateprovider/inline" // register inline certificate provider + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/utils" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -40,11 +41,6 @@ type CertificateStoreReconciler struct { Scheme *runtime.Scheme } -var ( - // a map between CertificateStore name to array of x509 certificates - certificatesMap = map[string][]*x509.Certificate{} -) - //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=certificatestores,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=certificatestores/status,verbs=get;update;patch //+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=certificatestores/finalizers,verbs=update @@ -68,7 +64,7 @@ func (r *CertificateStoreReconciler) Reconcile(ctx context.Context, req ctrl.Req if err := r.Get(ctx, req.NamespacedName, &certStore); err != nil { if apierrors.IsNotFound(err) { logger.Infof("deletion detected, removing certificate store %v", resource) - delete(certificatesMap, resource) + controllers.NamespacedCertStores.DeleteStore(resource) } else { logger.Error(err, "unable to fetch certificate store") } @@ -99,7 +95,7 @@ func (r *CertificateStoreReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, fmt.Errorf("Error fetching certificates in store %v with %v provider, error: %w", resource, certStore.Spec.Provider, err) } - certificatesMap[resource] = certificates + controllers.NamespacedCertStores.AddStore(resource, certificates) isFetchSuccessful = true emptyErrorString := "" writeCertStoreStatus(ctx, r, certStore, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, certAttributes) @@ -110,11 +106,6 @@ func (r *CertificateStoreReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil } -// returns the internal certificate map -func GetCertificatesMap() map[string][]*x509.Certificate { - return certificatesMap -} - // SetupWithManager sets up the controller with the Manager. func (r *CertificateStoreReconciler) SetupWithManager(mgr ctrl.Manager) error { pred := predicate.GenerationChangedPredicate{} @@ -156,8 +147,8 @@ func writeCertStoreStatus(ctx context.Context, r *CertificateStoreReconciler, ce func updateErrorStatus(certStore *configv1beta1.CertificateStore, errorString string, operationTime *metav1.Time) { // truncate brief error string to maxBriefErrLength briefErr := errorString - if len(errorString) > maxBriefErrLength { - briefErr = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) + if len(errorString) > constants.MaxBriefErrLength { + briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength]) } certStore.Status.IsSuccess = false certStore.Status.Error = errorString diff --git a/pkg/controllers/certificatestore_controller_test.go b/pkg/controllers/namespaceresource/certificatestore_controller_test.go similarity index 52% rename from pkg/controllers/certificatestore_controller_test.go rename to pkg/controllers/namespaceresource/certificatestore_controller_test.go index f60baddec..4e9da5189 100644 --- a/pkg/controllers/certificatestore_controller_test.go +++ b/pkg/controllers/namespaceresource/certificatestore_controller_test.go @@ -13,18 +13,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -package controllers +package namespaceresource import ( - "fmt" + "context" "testing" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/certificateprovider" - "github.com/deislabs/ratify/pkg/certificateprovider/inline" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/certificateprovider" + "github.com/ratify-project/ratify/pkg/certificateprovider/inline" + "github.com/ratify-project/ratify/pkg/controllers" + test "github.com/ratify-project/ratify/pkg/utils" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + ctxUtils "github.com/ratify-project/ratify/internal/context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ) func TestGetCertStoreConfig_ValidConfig(t *testing.T) { @@ -98,9 +105,8 @@ func TestUpdateErrorStatus(t *testing.T) { if certStore.Status.Error != expectedErr { t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, certStore.Status.Error) } - expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30]) - if certStore.Status.BriefError != expectedBriedErr { - t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, certStore.Status.Error) + if certStore.Status.BriefError != expectedErr { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, certStore.Status.Error) } //make sure properties of last cached cert was not overridden @@ -183,3 +189,96 @@ func TestGetCertificateProvider(t *testing.T) { t.Fatalf("Getting unregistered provider should returns an error") } } + +func TestCertStoreReconcile(t *testing.T) { + tests := []struct { + name string + description string + provider *configv1beta1.CertificateStore + req *reconcile.Request + expectedErr bool + expectedCertCount int + }{ + { + name: "nonexistent store", + description: "Reconciling a non-existent certStore CR, it should be deleted from map", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + provider: &configv1beta1.CertificateStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.CertificateStoreSpec{ + Provider: "inline", + }, + }, + expectedErr: false, + expectedCertCount: 0, + }, + { + name: "invalid params", + description: "Received invalid parameters of the certStore Spec, it should fail the reconcile and return an error", + provider: &configv1beta1.CertificateStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.CertificateStoreSpec{ + Provider: "inline", + }, + }, + expectedErr: true, + expectedCertCount: 0, + }, + { + name: "valid params", + description: "Received invalid parameters of the certStore Spec, it should fail the reconcile and return an error", + provider: &configv1beta1.CertificateStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.CertificateStoreSpec{ + Provider: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + }, + }, + expectedErr: false, + expectedCertCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.provider) + r := &CertificateStoreReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.provider), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + certs, _ := controllers.NamespacedCertStores.GetCertsFromStore(ctx, testNamespace+constants.NamespaceSeperator+storeName) + if len(certs) != tt.expectedCertCount { + t.Fatalf("Store map expected size %v, actual %v", tt.expectedCertCount, len(certs)) + } + }) + } +} diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go new file mode 100644 index 000000000..2bbdd0b29 --- /dev/null +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go @@ -0,0 +1,187 @@ +/* +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 namespaceresource + +import ( + "context" + "encoding/json" + + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" // register inline key management provider + "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" +) + +// KeyManagementProviderReconciler reconciles a KeyManagementProvider object +type KeyManagementProviderReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +func (r *KeyManagementProviderReconciler) ReconcileWithType(ctx context.Context, req ctrl.Request, refresherType string) (ctrl.Result, error) { + logger := logrus.WithContext(ctx) + + var resource = req.NamespacedName.String() + var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider + + logger.Infof("reconciling cluster key management provider '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil { + if apierrors.IsNotFound(err) { + logger.Infof("deletion detected, removing key management provider %v", resource) + kmp.DeleteResourceFromMap(resource) + } else { + logger.Error(err, "unable to fetch key management provider") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + lastFetchedTime := metav1.Now() + isFetchSuccessful := false + + // get certificate store list to check if certificate store is configured + // TODO: remove check in v2.0.0+ + var certificateStoreList configv1beta1.CertificateStoreList + if err := r.List(ctx, &certificateStoreList); err != nil { + logger.Error(err, "unable to list certificate stores") + return ctrl.Result{}, err + } + + if len(certificateStoreList.Items) > 0 { + // Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement. + logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.") + } + + provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to create key management provider from CR") + + kmp.SetCertificateError(resource, err) + kmp.SetKeyError(resource, err) + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + refresherConfig := refresh.RefresherConfig{ + RefresherType: refresherType, + Provider: provider, + ProviderType: keyManagementProvider.Spec.Type, + ProviderRefreshInterval: keyManagementProvider.Spec.RefreshInterval, + Resource: resource, + } + + refresher, err := refresh.CreateRefresherFromConfig(refresherConfig) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to create refresher from config") + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + err = refresher.Refresh(ctx) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to refresh key management provider") + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + result, ok := refresher.GetResult().(ctrl.Result) + if !ok { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to get result from refresher") + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, kmpErr + } + + status, ok := refresher.GetStatus().(kmp.KeyManagementProviderStatus) + if !ok { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail("Failed to get status from refresher") + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) + return ctrl.Result{}, &kmpErr + } + + writeKMProviderStatusNamespaced(ctx, r, &keyManagementProvider, logger, true, nil, lastFetchedTime, status) + + return result, nil +} + +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/finalizers,verbs=update +func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) error { + pred := predicate.GenerationChangedPredicate{} + + // status updates will trigger a reconcile event + // if there are no changes to spec of CRD, this event should be filtered out by using the predicate + // see more discussions at https://github.com/kubernetes-sigs/kubebuilder/issues/618 + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedKeyManagementProvider{}).WithEventFilter(pred). + Complete(r) +} + +// writeKMProviderStatusNamespaced updates the status of the key management provider resource +func writeKMProviderStatusNamespaced(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, logger *logrus.Entry, isSuccess bool, err *re.Error, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { + if isSuccess { + updateKMProviderSuccessStatusNamespaced(keyManagementProvider, &operationTime, kmProviderStatus) + } else { + updateKMProviderErrorStatusNamespaced(keyManagementProvider, err, &operationTime) + } + if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { + logger.Error(statusErr, ",unable to update key management provider error status") + } +} + +// updateKMProviderErrorStatusNamespaced updates the key management provider status with error, brief error and last fetched time +func updateKMProviderErrorStatusNamespaced(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, err *re.Error, operationTime *metav1.Time) { + keyManagementProvider.Status.IsSuccess = false + keyManagementProvider.Status.Error = err.Error() + keyManagementProvider.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) + keyManagementProvider.Status.LastFetchedTime = operationTime +} + +// Success status includes last fetched time and other provider-specific properties +func updateKMProviderSuccessStatusNamespaced(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) { + keyManagementProvider.Status.IsSuccess = true + keyManagementProvider.Status.Error = "" + keyManagementProvider.Status.BriefError = "" + keyManagementProvider.Status.LastFetchedTime = lastOperationTime + + if kmProviderStatus != nil { + jsonString, _ := json.Marshal(kmProviderStatus) + + raw := runtime.RawExtension{ + Raw: jsonString, + } + keyManagementProvider.Status.Properties = raw + } +} diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go new file mode 100644 index 000000000..d934754b8 --- /dev/null +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go @@ -0,0 +1,549 @@ +/* +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 namespaceresource + +import ( + "context" + "errors" + "fmt" + "reflect" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func init() { + refresh.Register("mockRefresher", &MockRefresher{}) +} + +type MockRefresher struct { + Result ctrl.Result + RefreshError bool + ResultError bool + StatusError bool + Status kmp.KeyManagementProviderStatus +} + +func (mr *MockRefresher) Refresh(_ context.Context) error { + if mr.RefreshError { + return errors.New("error from refresh") + } + return nil +} + +func (mr *MockRefresher) GetResult() interface{} { + if mr.ResultError { + return errors.New("error from result") + } + return mr.Result +} + +func (mr *MockRefresher) GetStatus() interface{} { + if mr.StatusError { + return errors.New("error from status") + } + return mr.Status +} + +func (mr *MockRefresher) Create(config refresh.RefresherConfig) (refresh.Refresher, error) { + if config.Resource == "refreshError/test" { + return &MockRefresher{ + RefreshError: true, + }, nil + } + if config.Resource == "resultError/test" { + return &MockRefresher{ + ResultError: true, + }, nil + } + if config.Resource == "statusError/test" { + return &MockRefresher{ + StatusError: true, + }, nil + } + return &MockRefresher{}, nil +} +func TestKeyManagementProviderReconciler_ReconcileWithType(t *testing.T) { + tests := []struct { + name string + clientGetFunc func(_ context.Context, key types.NamespacedName, obj client.Object) error + clientListFunc func(_ context.Context, list client.ObjectList) error + resourceNamespace string + refresherType string + expectedResult ctrl.Result + expectedError bool + }{ + { + // TODO: Add SetLogger to internal/logger/logger.go to compare log messages + // https://maxchadwick.xyz/blog/testing-log-output-in-go-logrus + name: "api is not found", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + resource := schema.GroupResource{ + Group: "", // Use an empty string for core resources (like Pod) + Resource: "pods", // Resource type, e.g., "pods" for Pod resources + } + return apierrors.NewNotFound(resource, "test") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to fetch key management provider", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return fmt.Errorf("unable to fetch key management provider") + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "unable to list certificate stores", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, _ client.Object) error { + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return fmt.Errorf("unable to list certificate stores") + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "certificate store already exists", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, list client.ObjectList) error { + certStoreList, ok := list.(*configv1beta1.CertificateStoreList) + if !ok { + return errors.New("expected CertificateStoreList") + } + + certStoreList.Items = []configv1beta1.CertificateStore{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + }, + }, + } + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "cutils.SpecToKeyManagementProvider failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.CreateRefresherFromConfig failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "invalidRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresh.Refresh failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "refreshError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetResult failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "resultError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "refresher.GetStatus failed", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "statusError", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "successfully reconciled", + clientGetFunc: func(_ context.Context, _ types.NamespacedName, obj client.Object) error { + getKMP, ok := obj.(*configv1beta1.NamespacedKeyManagementProvider) + if !ok { + return errors.New("expected KeyManagementProvider") + } + getKMP.ObjectMeta = metav1.ObjectMeta{ + Namespace: "test", + Name: "test", + } + getKMP.Spec = configv1beta1.NamespacedKeyManagementProviderSpec{ + Type: "inline", + Parameters: runtime.RawExtension{ + Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + }, + } + return nil + }, + clientListFunc: func(_ context.Context, _ client.ObjectList) error { + return nil + }, + resourceNamespace: "test", + refresherType: "mockRefresher", + expectedResult: ctrl.Result{}, + expectedError: false, + }, + } + + for _, tt := range tests { + fmt.Println(tt.name) + mockClient := mocks.TestClient{ + GetFunc: tt.clientGetFunc, + ListFunc: tt.clientListFunc, + } + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: "test", + Namespace: tt.resourceNamespace, + }, + } + scheme, _ := test.CreateScheme() + + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } + + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) + + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Fatalf("Expected result %v, got %v", tt.expectedResult, result) + } + if tt.expectedError && err == nil { + t.Fatalf("Expected error, got nil") + } + } +} + +func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { + req := ctrl.Request{ + NamespacedName: client.ObjectKey{ + Name: "fake-name", + Namespace: "fake-namespace", + }, + } + + // Create a fake client and scheme + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme).Build() + + r := &KeyManagementProviderReconciler{ + Client: client, + Scheme: runtime.NewScheme(), + } + + // Call the Reconcile method + result, err := r.Reconcile(context.TODO(), req) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Check the result + expectedResult := ctrl.Result{} + if !reflect.DeepEqual(result, expectedResult) { + t.Errorf("Expected result %v, got %v", expectedResult, result) + } +} +func TestKMProviderUpdateErrorStatusNamespaced(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) + + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: true, + Properties: runtime.RawExtension{ + Raw: kmProviderStatus, + }, + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + expectedErr := re.ErrorCodeUnknown.WithDetail("it's a long error from unit test") + lastFetchedTime := metav1.Now() + updateKMProviderErrorStatusNamespaced(&keyManagementProvider, &expectedErr, &lastFetchedTime) + + if keyManagementProvider.Status.IsSuccess != false { + t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != expectedErr.Error() { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error) + } + if keyManagementProvider.Status.BriefError != expectedErr.GetConciseError(150) { + t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr.GetConciseError(150), keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was not overridden + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Unexpected properties, expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw)) + } +} + +func TestKMProviderUpdateSuccessStatusNamespaced(t *testing.T) { + kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{} + properties := map[string]string{} + properties["Name"] = "wabbit" + properties["Version"] = "ABC" + + kmProviderStatus["Certificates"] = properties + + lastFetchedTime := metav1.Now() + + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatusNamespaced(&keyManagementProvider, &lastFetchedTime, kmProviderStatus) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) == 0 { + t.Fatalf("Properties should not be empty") + } +} + +func TestKMProviderUpdateSuccessStatusNamespaced_emptyProperties(t *testing.T) { + lastFetchedTime := metav1.Now() + status := configv1beta1.NamespacedKeyManagementProviderStatus{ + IsSuccess: false, + Error: "error from last operation", + } + keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{ + Status: status, + } + + updateKMProviderSuccessStatusNamespaced(&keyManagementProvider, &lastFetchedTime, nil) + + if keyManagementProvider.Status.IsSuccess != true { + t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess) + } + + if keyManagementProvider.Status.Error != "" { + t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + } + + //make sure properties of last cached cert was updated + if len(keyManagementProvider.Status.Properties.Raw) != 0 { + t.Fatalf("Properties should be empty") + } +} + +func TestWriteKMProviderStatusNamespaced(t *testing.T) { + logger := logrus.WithContext(context.Background()) + lastFetchedTime := metav1.Now() + testCases := []struct { + name string + isSuccess bool + kmProvider *configv1beta1.NamespacedKeyManagementProvider + errString string + expectedErrString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + errString: "a long error string that exceeds the max length of 30 characters", + expectedErrString: "UNKNOWN: a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + kmProvider: &configv1beta1.NamespacedKeyManagementProvider{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeKMProviderStatusNamespaced(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, &err, lastFetchedTime, nil) + + if tc.kmProvider.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) + } + + if tc.kmProvider.Status.Error != tc.expectedErrString { + t.Fatalf("Expected Error to be %+v , actual %+v", tc.expectedErrString, tc.kmProvider.Status.Error) + } + }) + } +} diff --git a/pkg/controllers/namespaceresource/policy_controller.go b/pkg/controllers/namespaceresource/policy_controller.go new file mode 100644 index 000000000..04417fbbf --- /dev/null +++ b/pkg/controllers/namespaceresource/policy_controller.go @@ -0,0 +1,122 @@ +/* +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 namespaceresource + +import ( + "context" + "fmt" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/controllers/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// PolicyReconciler reconciles a Policy object +type PolicyReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedpolicies/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + policyLogger := logrus.WithContext(ctx) + + var policy configv1beta1.NamespacedPolicy + var resource = req.Name + policyLogger.Infof("Reconciling Namespaced Policy %s", resource) + + if err := r.Get(ctx, req.NamespacedName, &policy); err != nil { + if apierrors.IsNotFound(err) { + policyLogger.Infof("delete event detected, removing policy %s", resource) + controllers.NamespacedPolicies.DeletePolicy(req.Namespace, resource) + } else { + policyLogger.Error("failed to get Policy: ", err) + } + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if resource != constants.RatifyPolicy { + err := re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("metadata.name must be ratify-policy, got %s", resource)) + policyLogger.Error(err) + writePolicyStatus(ctx, r, &policy, policyLogger, false, &err) + return ctrl.Result{}, nil + } + + if err := policyAddOrReplace(policy.Spec, req.Namespace); err != nil { + policyErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Unable to create policy from policy CR") + policyLogger.Error(policyErr) + writePolicyStatus(ctx, r, &policy, policyLogger, false, &policyErr) + return ctrl.Result{}, policyErr + } + + writePolicyStatus(ctx, r, &policy, policyLogger, true, nil) + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedPolicy{}). + Complete(r) +} + +func policyAddOrReplace(spec configv1beta1.NamespacedPolicySpec, namespace string) error { + policyEnforcer, err := utils.SpecToPolicyEnforcer(spec.Parameters.Raw, spec.Type) + if err != nil { + return err + } + + controllers.NamespacedPolicies.AddPolicy(namespace, constants.RatifyPolicy, policyEnforcer) + return nil +} + +func writePolicyStatus(ctx context.Context, r client.StatusClient, policy *configv1beta1.NamespacedPolicy, logger *logrus.Entry, isSuccess bool, err *re.Error) { + if isSuccess { + updatePolicySuccessStatus(policy) + } else { + updatePolicyErrorStatus(policy, err) + } + if statusErr := r.Status().Update(ctx, policy); statusErr != nil { + logger.Error(statusErr, ", unable to update policy error status") + } +} + +func updatePolicySuccessStatus(policy *configv1beta1.NamespacedPolicy) { + policy.Status.IsSuccess = true + policy.Status.Error = "" + policy.Status.BriefError = "" +} + +func updatePolicyErrorStatus(policy *configv1beta1.NamespacedPolicy, err *re.Error) { + policy.Status.IsSuccess = false + policy.Status.Error = err.Error() + policy.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) +} diff --git a/pkg/controllers/namespaceresource/policy_controller_test.go b/pkg/controllers/namespaceresource/policy_controller_test.go new file mode 100644 index 000000000..abf3b8a2e --- /dev/null +++ b/pkg/controllers/namespaceresource/policy_controller_test.go @@ -0,0 +1,271 @@ +/* +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 namespaceresource + +import ( + "context" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/policies" + _ "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" + _ "github.com/ratify-project/ratify/pkg/policyprovider/regopolicy" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + policyName1 = "policy1" + policyName2 = "policy2" + testNamespace = "testNamespace" +) + +func TestPolicyAddOrReplace(t *testing.T) { + testCases := []struct { + name string + spec configv1beta1.NamespacedPolicySpec + policyName string + expectErr bool + }{ + { + name: "invalid spec", + spec: configv1beta1.NamespacedPolicySpec{ + Type: policyName1, + }, + expectErr: true, + }, + { + name: "valid spec", + spec: configv1beta1.NamespacedPolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := policyAddOrReplace(tc.spec, testNamespace) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +func TestPolicyAddedTwice(t *testing.T) { + resetPolicyMap() + spec1 := configv1beta1.NamespacedPolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + } + spec2 := configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"regopolicy\", \"policy\": \"package ratify.policy\"}"), + }, + } + if err := policyAddOrReplace(spec1, testNamespace); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if err := policyAddOrReplace(spec2, testNamespace); err != nil { + t.Fatalf("expected no error, got %v", err) + } + + policyType := controllers.NamespacedPolicies.GetPolicy(testNamespace).GetPolicyType(context.Background()) + if policyType != "regopolicy" { + t.Fatalf("expected policy type to be regopolicy, got %s", policyType) + } +} + +func TestWritePolicyStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + policy *configv1beta1.NamespacedPolicy + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + policy: &configv1beta1.NamespacedPolicy{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + policy: &configv1beta1.NamespacedPolicy{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(_ *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, &err) + }) + } +} + +func TestPolicyReconcile(t *testing.T) { + tests := []struct { + name string + policy *configv1beta1.NamespacedPolicy + req *reconcile.Request + expectedErr bool + expectedPolicy bool + }{ + { + name: "nonexistent policy", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: policyName1, + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "no policy parameters provided", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "wrong policy name", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy2", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + }, + }, + expectedErr: false, + expectedPolicy: false, + }, + { + name: "invalid params", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "regopolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedPolicy: false, + }, + { + name: "valid params", + policy: &configv1beta1.NamespacedPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "ratify-policy", + }, + Spec: configv1beta1.NamespacedPolicySpec{ + Type: "configpolicy", + Parameters: runtime.RawExtension{ + Raw: []byte("{\"passthroughEnabled:\": false}"), + }, + }, + }, + expectedErr: false, + expectedPolicy: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetPolicyMap() + scheme, err := test.CreateScheme() + if err != nil { + t.Fatalf("CreateScheme() expected no error, actual %v", err) + } + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.policy) + r := &PolicyReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.policy), + } + } + _, err = r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error to be %t, actual %t", tt.expectedErr, err != nil) + } + test := controllers.NamespacedPolicies + + policy := test.GetPolicy(testNamespace) + if (policy != nil) != tt.expectedPolicy { + t.Fatalf("Expected policy to be %t, got %t", tt.expectedPolicy, policy != nil) + } + }) + } +} + +func resetPolicyMap() { + controllers.NamespacedPolicies = policies.NewActivePolicies() +} diff --git a/pkg/controllers/namespaceresource/store_controller.go b/pkg/controllers/namespaceresource/store_controller.go new file mode 100644 index 000000000..9c6b0f1b3 --- /dev/null +++ b/pkg/controllers/namespaceresource/store_controller.go @@ -0,0 +1,111 @@ +/* +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 namespaceresource + +import ( + "context" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/controllers/utils" + "github.com/sirupsen/logrus" +) + +// StoreReconciler reconciles a Store object +type StoreReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedstores/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *StoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + storeLogger := logrus.WithContext(ctx) + + var store configv1beta1.NamespacedStore + var resource = req.Name + storeLogger.Infof("reconciling namspaced store '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &store); err != nil { + if apierrors.IsNotFound(err) { + storeLogger.Infof("deletion detected, removing store %v", req.Name) + controllers.NamespacedStores.DeleteStore(req.Namespace, resource) + } else { + storeLogger.Error(err, "unable to fetch store") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := storeAddOrReplace(store.Spec, resource, req.Namespace); err != nil { + storeErr := re.ErrorCodeReferrerStoreFailure.WithError(err).WithDetail("Unable to create store from store CR") + storeLogger.Error(storeErr) + writeStoreStatus(ctx, r, &store, storeLogger, false, &storeErr) + return ctrl.Result{}, storeErr + } + + writeStoreStatus(ctx, r, &store, storeLogger, true, nil) + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *StoreReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedStore{}). + Complete(r) +} + +// Creates a store reference from CRD spec and add store to map +func storeAddOrReplace(spec configv1beta1.NamespacedStoreSpec, fullname, namespace string) error { + storeConfig, err := utils.CreateStoreConfig(spec.Parameters.Raw, spec.Name, spec.Source) + if err != nil { + return err + } + + return utils.UpsertStoreMap(spec.Version, spec.Address, fullname, namespace, storeConfig) +} + +func writeStoreStatus(ctx context.Context, r client.StatusClient, store *configv1beta1.NamespacedStore, logger *logrus.Entry, isSuccess bool, err *re.Error) { + if isSuccess { + store.Status.IsSuccess = true + store.Status.Error = "" + store.Status.BriefError = "" + } else { + store.Status.IsSuccess = false + store.Status.Error = err.Error() + store.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) + } + + if statusErr := r.Status().Update(ctx, store); statusErr != nil { + logger.Error(statusErr, ",unable to update store error status") + } +} diff --git a/pkg/controllers/namespaceresource/store_controller_test.go b/pkg/controllers/namespaceresource/store_controller_test.go new file mode 100644 index 000000000..08ceac2fb --- /dev/null +++ b/pkg/controllers/namespaceresource/store_controller_test.go @@ -0,0 +1,323 @@ +/* +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 namespaceresource + +import ( + "context" + "os" + "strings" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/referrerstores" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + storeName = "testStore" + sampleName = "sample" + orasName = "oras" +) + +func TestStoreAdd_EmptyParameter(t *testing.T) { + resetStoreMap() + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = configv1beta1.NamespacedStoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(testStoreSpec, "oras", testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAdd_InvalidConfig(t *testing.T) { + resetStoreMap() + var testStoreSpec = configv1beta1.NamespacedStoreSpec{ + Name: orasName, + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + } + + if err := storeAddOrReplace(testStoreSpec, orasName, testNamespace); err == nil { + t.Fatalf("storeAddOrReplace() expected error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAdd_WithParameters(t *testing.T) { + resetStoreMap() + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map expected size 0, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + + if err := storeAddOrReplace(testStoreSpec, "testObject", testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreAddOrReplace_PluginNotFound(t *testing.T) { + resetStoreMap() + var resource = "invalidplugin" + expectedMsg := "plugin not found" + var spec = getInvalidStoreSpec() + err := storeAddOrReplace(spec, resource, testNamespace) + + if !strings.Contains(err.Error(), expectedMsg) { + t.Fatalf("TestStoreAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestWriteStoreStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + store *configv1beta1.NamespacedStore + errString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + store: &configv1beta1.NamespacedStore{}, + reconciler: &test.MockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + store: &configv1beta1.NamespacedStore{}, + errString: "a long error string that exceeds the max length of 30 characters", + reconciler: &test.MockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + store: &configv1beta1.NamespacedStore{}, + reconciler: &test.MockStatusClient{ + UpdateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(_ *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeStoreStatus(context.Background(), tc.reconciler, tc.store, logger, tc.isSuccess, &err) + }) + } +} + +func TestStore_UpdateAndDelete(t *testing.T) { + resetStoreMap() + dirPath, err := test.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) + // add a Store + if err := storeAddOrReplace(testStoreSpec, sampleName, testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map expected size 1, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + + // modify the Store + var updatedSpec = configv1beta1.NamespacedStoreSpec{ + Name: sampleName, + Address: dirPath, + } + + if err := storeAddOrReplace(updatedSpec, sampleName, testNamespace); err != nil { + t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) + } + + // validate no Store has been added + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 1 { + t.Fatalf("Store map should be 1 after replacement, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } + + controllers.NamespacedStores.DeleteStore(testNamespace, sampleName) + + if len(controllers.NamespacedStores.GetStores(testNamespace)) != 0 { + t.Fatalf("Store map should be 0 after deletion, actual %v", len(controllers.NamespacedStores.GetStores(testNamespace))) + } +} + +func TestStoreReconcile(t *testing.T) { + dirPath, err := test.CreatePlugin(orasName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + store *configv1beta1.NamespacedStore + req *reconcile.Request + expectedErr bool + expectedStoreCount int + }{ + { + name: "nonexistent store", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Name: orasName, + }, + }, + expectedErr: false, + expectedStoreCount: 0, + }, + { + name: "valid spec", + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Name: orasName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedStoreCount: 1, + }, + { + name: "invalid parameters", + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, + { + name: "unsupported store", + store: &configv1beta1.NamespacedStore{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: storeName, + }, + Spec: configv1beta1.NamespacedStoreSpec{ + Name: "unsupported", + Address: dirPath, + }, + }, + expectedErr: true, + expectedStoreCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetStoreMap() + scheme, _ := test.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.store) + r := &StoreReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: test.KeyFor(tt.store), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedStores.GetStores(testNamespace)) != tt.expectedStoreCount { + t.Fatalf("Store map expected size %v, actual %v", tt.expectedStoreCount, len(controllers.NamespacedStores.GetStores(testNamespace))) + } + }) + } +} + +func resetStoreMap() { + controllers.NamespacedStores = referrerstores.NewActiveStores() +} + +func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.NamespacedStoreSpec { + var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}]},\"cosignEnabled\":false,\"useHttp\":false}" + var storeParameters = []byte(parametersString) + + return configv1beta1.NamespacedStoreSpec{ + Name: pluginName, + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: storeParameters, + }, + } +} + +func getInvalidStoreSpec() configv1beta1.NamespacedStoreSpec { + return configv1beta1.NamespacedStoreSpec{ + Name: "pluginnotfound", + Address: "test/path", + } +} diff --git a/pkg/controllers/namespaceresource/verifier_controller.go b/pkg/controllers/namespaceresource/verifier_controller.go new file mode 100644 index 000000000..a2926efe9 --- /dev/null +++ b/pkg/controllers/namespaceresource/verifier_controller.go @@ -0,0 +1,118 @@ +/* +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 namespaceresource + +import ( + "context" + "fmt" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/controllers" + + re "github.com/ratify-project/ratify/errors" + cutils "github.com/ratify-project/ratify/pkg/controllers/utils" + "github.com/sirupsen/logrus" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// VerifierReconciler reconciles a Verifier object +type VerifierReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedverifiers/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Verifier object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + verifierLogger := logrus.WithContext(ctx) + + var verifier configv1beta1.NamespacedVerifier + var resource = req.Name + + verifierLogger.Infof("reconciling verifier '%v'", resource) + + if err := r.Get(ctx, req.NamespacedName, &verifier); err != nil { + if apierrors.IsNotFound(err) { + verifierLogger.Infof("delete event detected, removing verifier %v", resource) + controllers.NamespacedVerifiers.DeleteVerifier(req.Namespace, resource) + } else { + verifierLogger.Error(err, "unable to fetch verifier") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if err := verifierAddOrReplace(verifier.Spec, resource, req.Namespace); err != nil { + verifierErr := re.ErrorCodePluginInitFailure.WithError(err).WithDetail("Unable to create verifier from verifier CR") + writeVerifierStatus(ctx, r, &verifier, verifierLogger, false, &verifierErr) + return ctrl.Result{}, verifierErr + } + + writeVerifierStatus(ctx, r, &verifier, verifierLogger, true, nil) + + // returning empty result and no error to indicate we’ve successfully reconciled this object + return ctrl.Result{}, nil +} + +// creates a verifier reference from CRD spec and add store to map +func verifierAddOrReplace(spec configv1beta1.NamespacedVerifierSpec, objectName string, namespace string) error { + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, cutils.GetVerifierType(spec), spec.ArtifactTypes, spec.Source) + if err != nil { + errMsg := fmt.Sprintf("Unable to apply the resource %s of NamespacedVerifier kind in the namespace %s", objectName, namespace) + logrus.Error(err, errMsg) + return re.ErrorCodeConfigInvalid.WithDetail(errMsg).WithError(err) + } + + return cutils.UpsertVerifier(spec.Version, spec.Address, namespace, objectName, verifierConfig) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *VerifierReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&configv1beta1.NamespacedVerifier{}). + Complete(r) +} + +func writeVerifierStatus(ctx context.Context, r client.StatusClient, verifier *configv1beta1.NamespacedVerifier, logger *logrus.Entry, isSuccess bool, err *re.Error) { + if isSuccess { + verifier.Status.IsSuccess = true + verifier.Status.Error = "" + verifier.Status.BriefError = "" + } else { + verifier.Status.IsSuccess = false + verifier.Status.Error = err.Error() + verifier.Status.BriefError = err.GetConciseError(constants.MaxBriefErrLength) + } + + if statusErr := r.Status().Update(ctx, verifier); statusErr != nil { + logger.Error(statusErr, ",unable to update verifier status") + } +} diff --git a/pkg/controllers/namespaceresource/verifier_controller_test.go b/pkg/controllers/namespaceresource/verifier_controller_test.go new file mode 100644 index 000000000..5c65f1645 --- /dev/null +++ b/pkg/controllers/namespaceresource/verifier_controller_test.go @@ -0,0 +1,382 @@ +/* +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 namespaceresource + +import ( + "context" + "errors" + "os" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/verifiers" + "github.com/ratify-project/ratify/pkg/utils" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + verifierName = "verifierName" + licenseChecker = "licensechecker" +) + +type mockResourceWriter struct { + updateFailed bool +} + +func (w mockResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (w mockResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if w.updateFailed { + return errors.New("update failed") + } + return nil +} + +func (w mockResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +type mockStatusClient struct { + updateFailed bool +} + +func (c mockStatusClient) Status() client.SubResourceWriter { + writer := mockResourceWriter{} + writer.updateFailed = c.updateFailed + return writer +} + +func TestMain(m *testing.M) { + // make sure to reset verifierMap before each test run + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() + code := m.Run() + os.Exit(code) +} + +func TestVerifierAdd_EmptyParameter(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = configv1beta1.NamespacedVerifierSpec{ + Name: sampleName, + ArtifactTypes: "application/vnd.cncf.notary.signature", + Address: dirPath, + } + + if err := verifierAddOrReplace(testVerifierSpec, sampleName, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAdd_InvalidParameter(t *testing.T) { + resetVerifierMap() + var testVerifierSpec = configv1beta1.NamespacedVerifierSpec{ + Name: "notation", + ArtifactTypes: "application/vnd.cncf.notary.signature", + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + } + var resource = "notation" + + if err := verifierAddOrReplace(testVerifierSpec, resource, testNamespace); err == nil { + t.Fatalf("verifierAddOrReplace() expected error, actual nil") + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAdd_WithParameters(t *testing.T) { + resetVerifierMap() + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map expected size 0, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + if err := verifierAddOrReplace(testVerifierSpec, "testObject", testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { + resetVerifierMap() + resource := "invalidplugin" + expectedMsg := "PLUGIN_NOT_FOUND: Verifier plugin pluginnotfound not found: failed to find plugin \"pluginnotfound\" in paths [test/path]: Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured." + var testVerifierSpec = getInvalidVerifierSpec() + err := verifierAddOrReplace(testVerifierSpec, resource, testNamespace) + + if err.Error() != expectedMsg { + t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) + } +} + +func TestVerifier_UpdateAndDelete(t *testing.T) { + resetVerifierMap() + dirPath, err := utils.CreatePlugin(licenseChecker) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) + + // add a verifier + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map expected size 1, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + // modify the verifier + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"GNU\"]}" + testVerifierSpec = getLicenseCheckerFromParam(parametersString, dirPath) + if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, testNamespace); err != nil { + t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) + } + + // validate no verifier has been added + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 1 { + t.Fatalf("Verifier map should be 1 after replacement, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + + controllers.NamespacedVerifiers.DeleteVerifier(testNamespace, licenseChecker) + + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != 0 { + t.Fatalf("Verifier map should be 0 after deletion, actual %v", len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } +} + +func TestWriteVerifierStatus(t *testing.T) { + logger := logrus.WithContext(context.Background()) + testCases := []struct { + name string + isSuccess bool + verifier *configv1beta1.NamespacedVerifier + errString string + expectedErrString string + expectedBriefErrString string + reconciler client.StatusClient + }{ + { + name: "success status", + isSuccess: true, + errString: "", + verifier: &configv1beta1.NamespacedVerifier{}, + reconciler: &mockStatusClient{}, + }, + { + name: "error status", + isSuccess: false, + verifier: &configv1beta1.NamespacedVerifier{}, + errString: "a long error string that exceeds the max length of 100 characters, a long error string that exceeds the max length of 100 characters", + expectedErrString: "UNKNOWN: a long error string that exceeds the max length of 100 characters, a long error string that exceeds the max length of 100 characters", + expectedBriefErrString: "UNKNOWN: a long error string that exceeds the max length of 100 characters, a long error string t...", + reconciler: &mockStatusClient{}, + }, + { + name: "status update failed", + isSuccess: true, + verifier: &configv1beta1.NamespacedVerifier{}, + reconciler: &mockStatusClient{ + updateFailed: true, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := re.ErrorCodeUnknown.WithDetail(tc.errString) + writeVerifierStatus(context.Background(), tc.reconciler, tc.verifier, logger, tc.isSuccess, &err) + + if tc.verifier.Status.IsSuccess != tc.isSuccess { + t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.verifier.Status.IsSuccess) + } + + if tc.verifier.Status.Error != tc.expectedErrString { + t.Fatalf("Expected Error to be %+v , actual %+v", tc.expectedErrString, tc.verifier.Status.Error) + } + + if tc.verifier.Status.BriefError != tc.expectedBriefErrString { + t.Fatalf("Expected BriefError to be %+v , actual %+v", tc.expectedBriefErrString, tc.verifier.Status.BriefError) + } + }) + } +} + +func TestVerifierReconcile(t *testing.T) { + dirPath, err := utils.CreatePlugin(sampleName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + verifier *configv1beta1.NamespacedVerifier + req *reconcile.Request + expectedErr bool + expectedVerifierCount int + }{ + { + name: "nonexistent verifier", + req: &reconcile.Request{ + NamespacedName: types.NamespacedName{Name: "nonexistent"}, + }, + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + }, + expectedErr: false, + expectedVerifierCount: 0, + }, + { + name: "invalid parameters", + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + Spec: configv1beta1.NamespacedVerifierSpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("test"), + }, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "unsupported verifier", + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + Spec: configv1beta1.NamespacedVerifierSpec{ + Name: "unsupported", + Address: dirPath, + }, + }, + expectedErr: true, + expectedVerifierCount: 0, + }, + { + name: "valid spec", + verifier: &configv1beta1.NamespacedVerifier{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: verifierName, + }, + Spec: configv1beta1.NamespacedVerifierSpec{ + Name: sampleName, + Address: dirPath, + }, + }, + expectedErr: false, + expectedVerifierCount: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + scheme, _ := utils.CreateScheme() + client := fake.NewClientBuilder().WithScheme(scheme) + client.WithObjects(tt.verifier) + r := &VerifierReconciler{ + Scheme: scheme, + Client: client.Build(), + } + var req reconcile.Request + if tt.req != nil { + req = *tt.req + } else { + req = reconcile.Request{ + NamespacedName: utils.KeyFor(tt.verifier), + } + } + + _, err := r.Reconcile(context.Background(), req) + if tt.expectedErr != (err != nil) { + t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err) + } + if len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace)) != tt.expectedVerifierCount { + t.Fatalf("Verifier map expected size %v, actual %v", tt.expectedVerifierCount, len(controllers.NamespacedVerifiers.GetVerifiers(testNamespace))) + } + }) + } +} + +func resetVerifierMap() { + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() +} + +func getLicenseCheckerFromParam(parametersString, pluginPath string) configv1beta1.NamespacedVerifierSpec { + var allowedLicenses = []byte(parametersString) + + return configv1beta1.NamespacedVerifierSpec{ + Name: licenseChecker, + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: pluginPath, + Parameters: runtime.RawExtension{ + Raw: allowedLicenses, + }, + } +} + +func getInvalidVerifierSpec() configv1beta1.NamespacedVerifierSpec { + return configv1beta1.NamespacedVerifierSpec{ + Name: "pluginnotfound", + ArtifactTypes: "application/vnd.ratify.spdx.v0", + Address: "test/path", + } +} + +func getDefaultLicenseCheckerSpec(pluginPath string) configv1beta1.NamespacedVerifierSpec { + var parametersString = "{\"allowedLicenses\":[\"MIT\",\"Apache\"]}" + return getLicenseCheckerFromParam(parametersString, pluginPath) +} diff --git a/pkg/controllers/policy_controller_test.go b/pkg/controllers/policy_controller_test.go deleted file mode 100644 index d3b041719..000000000 --- a/pkg/controllers/policy_controller_test.go +++ /dev/null @@ -1,301 +0,0 @@ -/* -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 controllers - -import ( - "context" - "errors" - "reflect" - "testing" - - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/policyprovider/config" - _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - policyName1 = "policy1" - policyName2 = "policy2" -) - -type mockResourceWriter struct { - updateFailed bool -} - -func (w mockResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { - return nil -} - -func (w mockResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { - if w.updateFailed { - return errors.New("update failed") - } - return nil -} - -func (w mockResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { - return nil -} - -type mockStatusClient struct { - updateFailed bool -} - -func (c mockStatusClient) Status() client.SubResourceWriter { - writer := mockResourceWriter{} - writer.updateFailed = c.updateFailed - return writer -} - -func TestDeletePolicy(t *testing.T) { - testCases := []struct { - name string - policyName string - expectPolicyName string - }{ - { - name: "Delete same name", - policyName: policyName1, - expectPolicyName: "", - }, - { - name: "Delete different name", - policyName: policyName2, - expectPolicyName: policyName1, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - policy := &policy{ - Name: policyName1, - } - policy.deletePolicy(tc.policyName) - if policy.Name != tc.expectPolicyName { - t.Fatalf("Expected policy name to be %s, got %s", tc.expectPolicyName, policy.Name) - } - }) - } -} - -func TestIsEmpty(t *testing.T) { - testCases := []struct { - name string - policy *policy - expect bool - }{ - { - name: "Empty policy", - policy: &policy{}, - expect: true, - }, - { - name: "Non-empty policy", - policy: &policy{ - Name: policyName1, - }, - expect: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - isEmpty := tc.policy.IsEmpty() - if isEmpty != tc.expect { - t.Fatalf("Expected to be %t, got %t", tc.expect, isEmpty) - } - }) - } -} - -func TestRawToPolicyConfig(t *testing.T) { - testCases := []struct { - name string - raw []byte - expectErr bool - expectConfig config.PoliciesConfig - }{ - { - name: "empty Raw", - raw: []byte{}, - expectErr: true, - expectConfig: config.PoliciesConfig{}, - }, - { - name: "unmarshal failure", - raw: []byte("invalid"), - expectErr: true, - expectConfig: config.PoliciesConfig{}, - }, - { - name: "valid Raw", - raw: []byte("{\"name\": \"policy1\"}"), - expectErr: false, - expectConfig: config.PoliciesConfig{ - PolicyPlugin: config.PolicyPluginConfig{ - "name": policyName1, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - config, err := rawToPolicyConfig(tc.raw, policyName1) - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - if !reflect.DeepEqual(config, tc.expectConfig) { - t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) - } - }) - } -} - -func TestSpecToPolicyEnforcer(t *testing.T) { - testCases := []struct { - name string - policyName string - spec configv1beta1.PolicySpec - expectErr bool - expectProvider bool - }{ - { - name: "invalid spec", - policyName: policyName1, - spec: configv1beta1.PolicySpec{ - Type: policyName1, - }, - expectErr: true, - expectProvider: false, - }, - { - name: "non-supported policy", - spec: configv1beta1.PolicySpec{ - Parameters: runtime.RawExtension{ - Raw: []byte("{\"name\": \"policy1\"}"), - }, - Type: policyName1, - }, - expectErr: true, - expectProvider: false, - }, - { - name: "valid spec", - spec: configv1beta1.PolicySpec{ - Parameters: runtime.RawExtension{ - Raw: []byte("{\"name\": \"configpolicy\"}"), - }, - Type: "configpolicy", - }, - expectErr: false, - expectProvider: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - provider, err := specToPolicyEnforcer(tc.spec) - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - if tc.expectProvider != (provider != nil) { - t.Fatalf("expected provider to be %t, got %t", tc.expectProvider, provider != nil) - } - }) - } -} - -func TestPolicyAddOrReplace(t *testing.T) { - testCases := []struct { - name string - spec configv1beta1.PolicySpec - policyName string - expectErr bool - }{ - { - name: "invalid spec", - spec: configv1beta1.PolicySpec{ - Type: policyName1, - }, - expectErr: true, - }, - { - name: "valid spec", - spec: configv1beta1.PolicySpec{ - Parameters: runtime.RawExtension{ - Raw: []byte("{\"name\": \"configpolicy\"}"), - }, - Type: "configpolicy", - }, - expectErr: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := policyAddOrReplace(tc.spec) - - if tc.expectErr != (err != nil) { - t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) - } - }) - } -} - -func TestWritePolicyStatus(t *testing.T) { - logger := logrus.WithContext(context.Background()) - testCases := []struct { - name string - isSuccess bool - policy *configv1beta1.Policy - errString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - policy: &configv1beta1.Policy{}, - errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - policy: &configv1beta1.Policy{}, - reconciler: &mockStatusClient{ - updateFailed: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - writePolicyStatus(context.Background(), tc.reconciler, tc.policy, logger, tc.isSuccess, tc.errString) - }) - } -} diff --git a/pkg/controllers/resource_map.go b/pkg/controllers/resource_map.go new file mode 100644 index 000000000..2e9c758ee --- /dev/null +++ b/pkg/controllers/resource_map.go @@ -0,0 +1,36 @@ +/* +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 controllers + +import ( + cs "github.com/ratify-project/ratify/pkg/customresources/certificatestores" + "github.com/ratify-project/ratify/pkg/customresources/policies" + rs "github.com/ratify-project/ratify/pkg/customresources/referrerstores" + "github.com/ratify-project/ratify/pkg/customresources/verifiers" +) + +var ( + // NamespacedVerifiers is a map between namespace and verifiers. + NamespacedVerifiers = verifiers.NewActiveVerifiers() + + // NamespacedPolicies is the active policy generated from CRD. There would be exactly + // one active policy belonging to a namespace at any given time. + NamespacedPolicies = policies.NewActivePolicies() + + // NamespacedStores is a map to track active stores across namespaces. + NamespacedStores = rs.NewActiveStores() + + // NamespacedCertStores is a map between namespace and CertificateStores. + NamespacedCertStores = cs.NewActiveCertStores() +) diff --git a/pkg/controllers/store_controller_test.go b/pkg/controllers/store_controller_test.go deleted file mode 100644 index 8838b9c6d..000000000 --- a/pkg/controllers/store_controller_test.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -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 controllers - -import ( - "context" - "os" - "strings" - "testing" - - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/utils" - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const sampleName = "sample" - -func TestStoreAdd_EmptyParameter(t *testing.T) { - resetStoreMap() - dirPath, err := utils.CreatePlugin(sampleName) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testStoreSpec = configv1beta1.StoreSpec{ - Name: sampleName, - Address: dirPath, - } - - if err := storeAddOrReplace(testStoreSpec, "oras"); err != nil { - t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) - } - if len(StoreMap) != 1 { - t.Fatalf("Store map expected size 1, actual %v", len(StoreMap)) - } -} - -func TestStoreAdd_WithParameters(t *testing.T) { - resetStoreMap() - if len(StoreMap) != 0 { - t.Fatalf("Store map expected size 0, actual %v", len(StoreMap)) - } - dirPath, err := utils.CreatePlugin(sampleName) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) - - if err := storeAddOrReplace(testStoreSpec, "testObject"); err != nil { - t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) - } - if len(StoreMap) != 1 { - t.Fatalf("Store map expected size 1, actual %v", len(StoreMap)) - } -} - -func TestWriteStoreStatus(t *testing.T) { - logger := logrus.WithContext(context.Background()) - testCases := []struct { - name string - isSuccess bool - store *configv1beta1.Store - errString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - store: &configv1beta1.Store{}, - reconciler: &mockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - store: &configv1beta1.Store{}, - errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - store: &configv1beta1.Store{}, - reconciler: &mockStatusClient{ - updateFailed: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - writeStoreStatus(context.Background(), tc.reconciler, tc.store, logger, tc.isSuccess, tc.errString) - }) - } -} - -func TestStoreAddOrReplace_PluginNotFound(t *testing.T) { - resetStoreMap() - var resource = "invalidplugin" - expectedMsg := "plugin not found" - var spec = getInvalidStoreSpec() - err := storeAddOrReplace(spec, resource) - - if !strings.Contains(err.Error(), expectedMsg) { - t.Fatalf("TestStoreAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) - } -} - -func TestStore_UpdateAndDelete(t *testing.T) { - resetStoreMap() - dirPath, err := utils.CreatePlugin(sampleName) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testStoreSpec = getOrasStoreSpec(sampleName, dirPath) - // add a Store - if err := storeAddOrReplace(testStoreSpec, sampleName); err != nil { - t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) - } - if len(StoreMap) != 1 { - t.Fatalf("Store map expected size 1, actual %v", len(StoreMap)) - } - - // modify the Store - var updatedSpec = configv1beta1.StoreSpec{ - Name: sampleName, - Address: dirPath, - } - - if err := storeAddOrReplace(updatedSpec, sampleName); err != nil { - t.Fatalf("storeAddOrReplace() expected no error, actual %v", err) - } - - // validate no Store has been added - if len(StoreMap) != 1 { - t.Fatalf("Store map should be 1 after replacement, actual %v", len(StoreMap)) - } - - storeRemove(sampleName) - - if len(StoreMap) != 0 { - t.Fatalf("Store map should be 0 after deletion, actual %v", len(StoreMap)) - } -} - -func resetStoreMap() { - StoreMap = map[string]referrerstore.ReferrerStore{} -} - -func getOrasStoreSpec(pluginName, pluginPath string) configv1beta1.StoreSpec { - var parametersString = "{\"authProvider\":{\"name\":\"k8Secrets\",\"secrets\":[{\"secretName\":\"myregistrykey\"}]},\"cosignEnabled\":false,\"useHttp\":false}" - var storeParameters = []byte(parametersString) - - return configv1beta1.StoreSpec{ - Name: pluginName, - Address: pluginPath, - Parameters: runtime.RawExtension{ - Raw: storeParameters, - }, - } -} - -func getInvalidStoreSpec() configv1beta1.StoreSpec { - return configv1beta1.StoreSpec{ - Name: "pluginnotfound", - Address: "test/path", - } -} diff --git a/pkg/controllers/utils/kmp.go b/pkg/controllers/utils/kmp.go new file mode 100644 index 000000000..a2085cfb5 --- /dev/null +++ b/pkg/controllers/utils/kmp.go @@ -0,0 +1,58 @@ +/* +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 utils + +import ( + "encoding/json" + "fmt" + + c "github.com/ratify-project/ratify/config" + re "github.com/ratify-project/ratify/errors" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/factory" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/types" +) + +// SpecToKeyManagementProvider creates KeyManagementProvider from KeyManagementProviderSpec config +func SpecToKeyManagementProvider(raw []byte, keyManagamentSystemName string) (kmp.KeyManagementProvider, error) { + kmProviderConfig, err := rawToKeyManagementProviderConfig(raw, keyManagamentSystemName) + if err != nil { + return nil, err + } + + // TODO: add Version and Address to KeyManagementProviderSpec + keyManagementProviderProvider, err := factory.CreateKeyManagementProviderFromConfig(kmProviderConfig, "0.1.0", c.GetDefaultPluginPath()) + if err != nil { + return nil, err + } + + return keyManagementProviderProvider, nil +} + +// rawToKeyManagementProviderConfig converts raw json to KeyManagementProviderConfig +func rawToKeyManagementProviderConfig(raw []byte, keyManagamentSystemName string) (config.KeyManagementProviderConfig, error) { + pluginConfig := config.KeyManagementProviderConfig{} + + if string(raw) == "" { + return config.KeyManagementProviderConfig{}, fmt.Errorf("no key management provider parameters provided") + } + if err := json.Unmarshal(raw, &pluginConfig); err != nil { + return config.KeyManagementProviderConfig{}, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Unable to decode key management provider parameters.Raw: %s", string(raw))).WithError(err) + } + + pluginConfig[types.Type] = keyManagamentSystemName + + return pluginConfig, nil +} diff --git a/pkg/controllers/utils/kmp_test.go b/pkg/controllers/utils/kmp_test.go new file mode 100644 index 000000000..e3223e481 --- /dev/null +++ b/pkg/controllers/utils/kmp_test.go @@ -0,0 +1,101 @@ +/* +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 utils + +import ( + "reflect" + "testing" + + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" +) + +func TestSpecToKeyManagementProviderProvider(t *testing.T) { + testCases := []struct { + name string + raw []byte + kmpType string + expectErr bool + }{ + { + name: "empty spec", + expectErr: true, + }, + { + name: "missing inline provider required fields", + raw: []byte("{\"type\": \"inline\"}"), + kmpType: "inline", + expectErr: true, + }, + { + name: "valid spec", + raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + kmpType: "inline", + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := SpecToKeyManagementProvider(tc.raw, tc.kmpType) + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + }) + } +} + +// TestRawToKeyManagementProviderConfig tests the rawToKeyManagementProviderConfig method +func TestRawToKeyManagementProviderConfig(t *testing.T) { + testCases := []struct { + name string + raw []byte + expectErr bool + expectConfig config.KeyManagementProviderConfig + }{ + { + name: "empty Raw", + raw: []byte{}, + expectErr: true, + expectConfig: config.KeyManagementProviderConfig{}, + }, + { + name: "unmarshal failure", + raw: []byte("invalid"), + expectErr: true, + expectConfig: config.KeyManagementProviderConfig{}, + }, + { + name: "valid Raw", + raw: []byte("{\"type\": \"inline\"}"), + expectErr: false, + expectConfig: config.KeyManagementProviderConfig{ + "type": "inline", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config, err := rawToKeyManagementProviderConfig(tc.raw, "inline") + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + if !reflect.DeepEqual(config, tc.expectConfig) { + t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) + } + }) + } +} diff --git a/pkg/controllers/utils/policy.go b/pkg/controllers/utils/policy.go new file mode 100644 index 000000000..1408972a5 --- /dev/null +++ b/pkg/controllers/utils/policy.go @@ -0,0 +1,54 @@ +/* +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 utils + +import ( + "encoding/json" + "fmt" + + "github.com/ratify-project/ratify/pkg/policyprovider" + "github.com/ratify-project/ratify/pkg/policyprovider/config" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" +) + +func SpecToPolicyEnforcer(raw []byte, policyType string) (policyprovider.PolicyProvider, error) { + policyConfig, err := rawToPolicyConfig(raw, policyType) + if err != nil { + return nil, fmt.Errorf("failed to parse policy config: %w", err) + } + + policyEnforcer, err := pf.CreatePolicyProviderFromConfig(policyConfig) + if err != nil { + return nil, fmt.Errorf("failed to create policy provider: %w", err) + } + + return policyEnforcer, nil +} + +func rawToPolicyConfig(raw []byte, policyType string) (config.PoliciesConfig, error) { + pluginConfig := config.PolicyPluginConfig{} + + if string(raw) == "" { + return config.PoliciesConfig{}, fmt.Errorf("no policy parameters provided") + } + if err := json.Unmarshal(raw, &pluginConfig); err != nil { + return config.PoliciesConfig{}, fmt.Errorf("unable to decode policy parameters.Raw: %s, err: %w", raw, err) + } + + pluginConfig["name"] = policyType + + return config.PoliciesConfig{ + PolicyPlugin: pluginConfig, + }, nil +} diff --git a/pkg/controllers/utils/policy_test.go b/pkg/controllers/utils/policy_test.go new file mode 100644 index 000000000..d505e9cb2 --- /dev/null +++ b/pkg/controllers/utils/policy_test.go @@ -0,0 +1,127 @@ +/* +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 utils + +import ( + "reflect" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + _ "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" + + "github.com/ratify-project/ratify/pkg/policyprovider/config" + "k8s.io/apimachinery/pkg/runtime" +) + +const policyName1 = "policy1" + +func TestRawToPolicyConfig(t *testing.T) { + testCases := []struct { + name string + raw []byte + expectErr bool + expectConfig config.PoliciesConfig + }{ + { + name: "empty Raw", + raw: []byte{}, + expectErr: true, + expectConfig: config.PoliciesConfig{}, + }, + { + name: "unmarshal failure", + raw: []byte("invalid"), + expectErr: true, + expectConfig: config.PoliciesConfig{}, + }, + { + name: "valid Raw", + raw: []byte("{\"name\": \"policy1\"}"), + expectErr: false, + expectConfig: config.PoliciesConfig{ + PolicyPlugin: config.PolicyPluginConfig{ + "name": policyName1, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config, err := rawToPolicyConfig(tc.raw, policyName1) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + if !reflect.DeepEqual(config, tc.expectConfig) { + t.Fatalf("Expected config to be %v, got %v", tc.expectConfig, config) + } + }) + } +} + +func TestSpecToPolicyEnforcer(t *testing.T) { + testCases := []struct { + name string + policyName string + spec configv1beta1.PolicySpec + expectErr bool + expectProvider bool + }{ + { + name: "invalid spec", + policyName: policyName1, + spec: configv1beta1.PolicySpec{ + Type: policyName1, + }, + expectErr: true, + expectProvider: false, + }, + { + name: "non-supported policy", + spec: configv1beta1.PolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"policy1\"}"), + }, + Type: policyName1, + }, + expectErr: true, + expectProvider: false, + }, + { + name: "valid spec", + spec: configv1beta1.PolicySpec{ + Parameters: runtime.RawExtension{ + Raw: []byte("{\"name\": \"configpolicy\"}"), + }, + Type: "configpolicy", + }, + expectErr: false, + expectProvider: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + provider, err := SpecToPolicyEnforcer(tc.spec.Parameters.Raw, tc.spec.Type) + + if tc.expectErr != (err != nil) { + t.Fatalf("Expected error to be %t, got %t", tc.expectErr, err != nil) + } + if tc.expectProvider != (provider != nil) { + t.Fatalf("expected provider to be %t, got %t", tc.expectProvider, provider != nil) + } + }) + } +} diff --git a/pkg/controllers/utils/store.go b/pkg/controllers/utils/store.go new file mode 100644 index 000000000..d3e5fd2b2 --- /dev/null +++ b/pkg/controllers/utils/store.go @@ -0,0 +1,68 @@ +/* +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 utils + +import ( + "encoding/json" + "fmt" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/pkg/controllers" + rc "github.com/ratify-project/ratify/pkg/referrerstore/config" + sf "github.com/ratify-project/ratify/pkg/referrerstore/factory" + "github.com/ratify-project/ratify/pkg/verifier/types" + "github.com/sirupsen/logrus" +) + +func UpsertStoreMap(version, address, fullname, namespace string, storeConfig rc.StorePluginConfig) error { + // if the default version is not suitable, the plugin configuration should specify the desired version + if len(version) == 0 { + version = config.GetDefaultPluginVersion() + logrus.Infof("Version was empty, setting to default version: %v", version) + } + + if address == "" { + address = config.GetDefaultPluginPath() + logrus.Infof("Address was empty, setting to default path %v", address) + } + storeReference, err := sf.CreateStoreFromConfig(storeConfig, version, []string{address}) + + if err != nil || storeReference == nil { + logrus.Error(err, "store factory failed to create store from store config") + return fmt.Errorf("store factory failed to create store from store config, err: %w", err) + } + controllers.NamespacedStores.AddStore(namespace, fullname, storeReference) + logrus.Infof("store '%v' added to store map in namespace: %s", storeReference.Name(), namespace) + + return nil +} + +// Returns a store reference from spec +func CreateStoreConfig(raw []byte, name string, source *configv1beta1.PluginSource) (rc.StorePluginConfig, error) { + storeConfig := rc.StorePluginConfig{} + + if string(raw) != "" { + if err := json.Unmarshal(raw, &storeConfig); err != nil { + logrus.Error(err, "unable to decode store parameters", "Parameters.Raw", raw) + return rc.StorePluginConfig{}, err + } + } + storeConfig[types.Name] = name + if source != nil { + storeConfig[types.Source] = source + } + + return storeConfig, nil +} diff --git a/pkg/controllers/utils/store_test.go b/pkg/controllers/utils/store_test.go new file mode 100644 index 000000000..aea018556 --- /dev/null +++ b/pkg/controllers/utils/store_test.go @@ -0,0 +1,105 @@ +/* +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 utils + +import ( + "os" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + rc "github.com/ratify-project/ratify/pkg/referrerstore/config" + test "github.com/ratify-project/ratify/pkg/utils" + "github.com/ratify-project/ratify/pkg/verifier/types" +) + +const ( + storeName = "storeName" + testNamespace = "testNamespace" +) + +func TestUpsertStoreMap(t *testing.T) { + dirPath, err := test.CreatePlugin(storeName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + address string + storeConfig rc.StorePluginConfig + expectedErr bool + }{ + { + name: "empty config", + storeConfig: rc.StorePluginConfig{}, + expectedErr: true, + }, + { + name: "valid config", + address: dirPath, + storeConfig: rc.StorePluginConfig{ + "name": storeName, + }, + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := UpsertStoreMap("", tt.address, storeName, testNamespace, tt.storeConfig) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) + } + }) + } +} + +func TestCreateStoreConfig(t *testing.T) { + tests := []struct { + name string + raw []byte + source *configv1beta1.PluginSource + expectedErr bool + expectedStoreName string + }{ + { + name: "invalid raw", + raw: []byte("invalid\n"), + expectedErr: true, + }, + { + name: "valid raw", + raw: []byte("{\"name\": \"storeName\"}"), + source: &configv1beta1.PluginSource{}, + expectedErr: false, + expectedStoreName: storeName, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := CreateStoreConfig(tt.raw, storeName, tt.source) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) + } + if _, ok := config[types.Name]; !ok { + config[types.Name] = "" + } + if config[types.Name] != tt.expectedStoreName { + t.Fatalf("expected store name: %s, got: %s", tt.expectedStoreName, config[types.Name]) + } + }) + } +} diff --git a/pkg/controllers/utils/verifier.go b/pkg/controllers/utils/verifier.go new file mode 100644 index 000000000..43a9e858d --- /dev/null +++ b/pkg/controllers/utils/verifier.go @@ -0,0 +1,93 @@ +/* +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 utils + +import ( + "encoding/json" + "fmt" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" + vc "github.com/ratify-project/ratify/pkg/verifier/config" + vf "github.com/ratify-project/ratify/pkg/verifier/factory" + "github.com/ratify-project/ratify/pkg/verifier/types" + + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/sirupsen/logrus" +) + +// UpsertVerifier creates and adds a new verifier based on the provided configuration. +func UpsertVerifier(version, address, namespace, objectName string, verifierConfig vc.VerifierConfig) error { + if len(version) == 0 { + version = config.GetDefaultPluginVersion() + logrus.Infof("Version was empty, setting to default version: %v", version) + } + + if address == "" { + address = config.GetDefaultPluginPath() + logrus.Infof("Address was empty, setting to default path: %v", address) + } + + referenceVerifier, err := vf.CreateVerifierFromConfig(verifierConfig, version, []string{address}, namespace) + if err != nil || referenceVerifier == nil { + logrus.Error(err, " unable to create verifier from verifier config") + return err + } + + controllers.NamespacedVerifiers.AddVerifier(namespace, objectName, referenceVerifier) + logrus.Infof("verifier '%v' added to verifier map in namespace: %s", referenceVerifier.Name(), namespace) + + return nil +} + +// SpecToVerifierConfig returns a VerifierConfig from VerifierSpec +func SpecToVerifierConfig(raw []byte, verifierName, verifierType, artifactTypes string, source *configv1beta1.PluginSource) (vc.VerifierConfig, error) { + verifierConfig := vc.VerifierConfig{} + + if string(raw) != "" { + if err := json.Unmarshal(raw, &verifierConfig); err != nil { + errMsg := fmt.Sprintf("Unable to recognize the parameters of the Verifier resource %s", string(raw)) + logrus.Error(err, errMsg) + return vc.VerifierConfig{}, re.ErrorCodeConfigInvalid.WithError(err).WithDetail(errMsg).WithRemediation("Please update the Verifier parameters and try again. Refer to the Verifier configuration guide: https://ratify.dev/docs/reference/custom%20resources/verifiers") + } + } + verifierConfig[types.Name] = verifierName + verifierConfig[types.Type] = verifierType + verifierConfig[types.ArtifactTypes] = artifactTypes + if source != nil { + verifierConfig[types.Source] = source + } + + return verifierConfig, nil +} + +// GetVerifierType returns verifier type and is backward compatible with the deprecated name field +func GetVerifierType(verifierSpec interface{}) string { + switch spec := verifierSpec.(type) { + case configv1beta1.VerifierSpec: + if spec.Type == "" { + return spec.Name + } + return spec.Type + case configv1beta1.NamespacedVerifierSpec: + if spec.Type == "" { + return spec.Name + } + return spec.Type + default: + logrus.Error("unable to assert verifierSpec type", spec) + } + return "" +} diff --git a/pkg/controllers/utils/verifier_test.go b/pkg/controllers/utils/verifier_test.go new file mode 100644 index 000000000..349653a07 --- /dev/null +++ b/pkg/controllers/utils/verifier_test.go @@ -0,0 +1,164 @@ +/* +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 utils + +import ( + "os" + "testing" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/customresources/verifiers" + test "github.com/ratify-project/ratify/pkg/utils" + vc "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/types" +) + +const ( + verifierName = "verifierName" + verifierType = "verifierType" +) + +func TestUpsertVerifier(t *testing.T) { + dirPath, err := test.CreatePlugin(verifierName) + if err != nil { + t.Fatalf("createPlugin() expected no error, actual %v", err) + } + defer os.RemoveAll(dirPath) + + tests := []struct { + name string + address string + namespace string + objectName string + verifierConfig vc.VerifierConfig + expectedErr bool + }{ + { + name: "empty config", + verifierConfig: vc.VerifierConfig{}, + expectedErr: true, + }, + { + name: "empty address", + address: dirPath, + verifierConfig: vc.VerifierConfig{ + "name": verifierName, + }, + expectedErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resetVerifierMap() + + err = UpsertVerifier("", tt.address, tt.namespace, tt.objectName, tt.verifierConfig) + if tt.expectedErr != (err != nil) { + t.Fatalf("UpsertVerifier() expected error %v, actual %v", tt.expectedErr, err) + } + }) + } +} + +func TestSpecToVerifierConfig(t *testing.T) { + tests := []struct { + name string + raw []byte + verifierName string + verifierType string + artifactTypes string + source *configv1beta1.PluginSource + expectedErr bool + expectedVerifierName string + }{ + { + name: "empty raw", + raw: []byte{}, + verifierName: verifierName, + verifierType: verifierType, + source: &configv1beta1.PluginSource{}, + expectedVerifierName: verifierName, + expectedErr: false, + }, + { + name: "invalid raw", + raw: []byte("test\n"), + expectedVerifierName: "", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config, err := SpecToVerifierConfig(tt.raw, tt.verifierName, tt.verifierType, tt.artifactTypes, tt.source) + if tt.expectedErr != (err != nil) { + t.Fatalf("SpecToVerifierConfig() expected error %v, actual %v", tt.expectedErr, err) + } + if _, ok := config[types.Name]; !ok { + config[types.Name] = "" + } + if config[types.Name] != tt.expectedVerifierName { + t.Fatalf("SpecToVerifierConfig() expected verifier name %s, actual %s", tt.expectedVerifierName, config[types.Name]) + } + }) + } +} + +func resetVerifierMap() { + controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() +} + +func TestGetType(t *testing.T) { + tests := []struct { + name string + input interface{} + expected string + }{ + { + name: "cluster verifier spec with name", + input: configv1beta1.VerifierSpec{Name: "clusterV"}, + expected: "clusterV", + }, + { + name: "cluster verifier spec with type", + input: configv1beta1.VerifierSpec{Type: "clusterV"}, + expected: "clusterV", + }, + { + name: "namespaced verifier spec with name", + input: configv1beta1.NamespacedVerifierSpec{Name: "namespacedV"}, + expected: "namespacedV", + }, + { + name: "namespaced verifier spec with type", + input: configv1beta1.NamespacedVerifierSpec{Type: "namespacedV"}, + expected: "namespacedV", + }, + { + name: "verifier spec with no name or type", + input: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := GetVerifierType(tt.input) + if tt.expected != output { + t.Fatalf("GetType() expected %v, actual %v", tt.expected, output) + } + }) + } +} diff --git a/pkg/controllers/verifier_controller.go b/pkg/controllers/verifier_controller.go deleted file mode 100644 index 31cb83a5b..000000000 --- a/pkg/controllers/verifier_controller.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -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 controllers - -import ( - "context" - "encoding/json" - "fmt" - "os" - - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/config" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/utils" - vr "github.com/deislabs/ratify/pkg/verifier" - vc "github.com/deislabs/ratify/pkg/verifier/config" - vf "github.com/deislabs/ratify/pkg/verifier/factory" - "github.com/deislabs/ratify/pkg/verifier/types" - - "github.com/sirupsen/logrus" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// VerifierReconciler reconciles a Verifier object -type VerifierReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -var ( - // a map to track of active verifiers - VerifierMap = map[string]vr.ReferenceVerifier{} -) - -//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=verifiers/finalizers,verbs=update - -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Verifier object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile -func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - verifierLogger := logrus.WithContext(ctx) - - var verifier configv1beta1.Verifier - var resource = req.Name - - verifierLogger.Infof("reconciling verifier '%v'", resource) - - if err := r.Get(ctx, req.NamespacedName, &verifier); err != nil { - if apierrors.IsNotFound(err) { - verifierLogger.Infof("delete event detected, removing verifier %v", resource) - verifierRemove(resource) - } else { - verifierLogger.Error(err, "unable to fetch verifier") - } - - return ctrl.Result{}, client.IgnoreNotFound(err) - } - - namespace, err := getCertStoreNamespace(req.Namespace) - if err != nil { - verifierLogger.Error(err, "unable to get default namespace for certstore specified in verifier crd") - return ctrl.Result{}, err - } - - if err = verifierAddOrReplace(verifier.Spec, resource, namespace); err != nil { - verifierLogger.Error(err, "unable to create verifier from verifier crd") - writeVerifierStatus(ctx, r, &verifier, verifierLogger, false, err.Error()) - return ctrl.Result{}, err - } - - writeVerifierStatus(ctx, r, &verifier, verifierLogger, true, "") - - // returning empty result and no error to indicate we’ve successfully reconciled this object - return ctrl.Result{}, nil -} - -// creates a verifier reference from CRD spec and add store to map -func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string, namespace string) error { - verifierConfig, err := specToVerifierConfig(spec, objectName) - if err != nil { - logrus.Error(err, "unable to convert crd specification to verifier config") - return fmt.Errorf("unable to convert crd specification to verifier config, err: %w", err) - } - - if len(spec.Version) == 0 { - spec.Version = config.GetDefaultPluginVersion() - logrus.Infof("Version was empty, setting to default version: %v", spec.Version) - } - - if spec.Address == "" { - spec.Address = config.GetDefaultPluginPath() - logrus.Infof("Address was empty, setting to default path: %v", spec.Address) - } - - referenceVerifier, err := vf.CreateVerifierFromConfig(verifierConfig, spec.Version, []string{spec.Address}, namespace) - - if err != nil || referenceVerifier == nil { - logrus.Error(err, "unable to create verifier from verifier config") - return err - } - VerifierMap[objectName] = referenceVerifier - logrus.Infof("verifier '%v' added to verifier map", referenceVerifier.Name()) - - return nil -} - -// remove verifier from map -func verifierRemove(objectName string) { - delete(VerifierMap, objectName) -} - -// returns a verifier reference from spec -func specToVerifierConfig(verifierSpec configv1beta1.VerifierSpec, verifierName string) (vc.VerifierConfig, error) { - verifierConfig := vc.VerifierConfig{} - - if string(verifierSpec.Parameters.Raw) != "" { - if err := json.Unmarshal(verifierSpec.Parameters.Raw, &verifierConfig); err != nil { - logrus.Error(err, "unable to decode verifier parameters", "Parameters.Raw", verifierSpec.Parameters.Raw) - return vc.VerifierConfig{}, err - } - } - verifierConfig[types.Name] = verifierName - verifierConfig[types.Type] = verifierSpec.Name - verifierConfig[types.ArtifactTypes] = verifierSpec.ArtifactTypes - if verifierSpec.Source != nil { - verifierConfig[types.Source] = verifierSpec.Source - } - - return verifierConfig, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *VerifierReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&configv1beta1.Verifier{}). - Complete(r) -} - -// Historically certStore defined in trust policy only contains name which means the CertStore cannot be uniquely identified -// If verifierNamespace is not empty, this method returns the default cert store namespace else returns the ratify deployed namespace -func getCertStoreNamespace(verifierNamespace string) (string, error) { - // first, check if we can use the verifier namespace as the cert store namespace - if verifierNamespace != "" { - return verifierNamespace, nil - } - - // next, return the ratify deployed namespace - ns, found := os.LookupEnv(utils.RatifyNamespaceEnvVar) - if !found { - return "", re.ErrorCodeEnvNotSet.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("environment variable %s not set", utils.RatifyNamespaceEnvVar)) - } - - return ns, nil -} - -func writeVerifierStatus(ctx context.Context, r client.StatusClient, verifier *configv1beta1.Verifier, logger *logrus.Entry, isSuccess bool, errorString string) { - if isSuccess { - verifier.Status.IsSuccess = true - verifier.Status.Error = "" - verifier.Status.BriefError = "" - } else { - verifier.Status.IsSuccess = false - verifier.Status.Error = errorString - if len(errorString) > maxBriefErrLength { - verifier.Status.BriefError = fmt.Sprintf("%s...", errorString[:maxBriefErrLength]) - } - } - - if statusErr := r.Status().Update(ctx, verifier); statusErr != nil { - logger.Error(statusErr, ",unable to update verifier status") - } -} diff --git a/pkg/controllers/verifier_controller_test.go b/pkg/controllers/verifier_controller_test.go deleted file mode 100644 index 6ecad4020..000000000 --- a/pkg/controllers/verifier_controller_test.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -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 controllers - -import ( - "context" - "os" - "strings" - "testing" - - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/pkg/utils" - vr "github.com/deislabs/ratify/pkg/verifier" - "github.com/sirupsen/logrus" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const licenseChecker = "licensechecker" - -func TestMain(m *testing.M) { - // make sure to reset verifierMap before each test run - VerifierMap = map[string]vr.ReferenceVerifier{} - code := m.Run() - os.Exit(code) -} - -func TestVerifierAdd_EmptyParameter(t *testing.T) { - resetVerifierMap() - dirPath, err := utils.CreatePlugin(sampleName) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testVerifierSpec = configv1beta1.VerifierSpec{ - Name: sampleName, - ArtifactTypes: "application/vnd.cncf.notary.signature", - Address: dirPath, - } - - if err := verifierAddOrReplace(testVerifierSpec, sampleName, constants.EmptyNamespace); err != nil { - t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) - } - if len(VerifierMap) != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", len(VerifierMap)) - } -} - -func TestVerifierAdd_WithParameters(t *testing.T) { - resetVerifierMap() - if len(VerifierMap) != 0 { - t.Fatalf("Verifier map expected size 0, actual %v", len(VerifierMap)) - } - - dirPath, err := utils.CreatePlugin(licenseChecker) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) - - if err := verifierAddOrReplace(testVerifierSpec, "testObject", constants.EmptyNamespace); err != nil { - t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) - } - if len(VerifierMap) != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", len(VerifierMap)) - } -} - -func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { - resetVerifierMap() - var resource = "invalidplugin" - expectedMsg := "plugin not found" - var testVerifierSpec = getInvalidVerifierSpec() - err := verifierAddOrReplace(testVerifierSpec, resource, constants.EmptyNamespace) - - if !strings.Contains(err.Error(), expectedMsg) { - t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) - } -} - -func TestVerifier_UpdateAndDelete(t *testing.T) { - resetVerifierMap() - dirPath, err := utils.CreatePlugin(licenseChecker) - if err != nil { - t.Fatalf("createPlugin() expected no error, actual %v", err) - } - defer os.RemoveAll(dirPath) - - var testVerifierSpec = getDefaultLicenseCheckerSpec(dirPath) - - // add a verifier - if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, constants.EmptyNamespace); err != nil { - t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) - } - if len(VerifierMap) != 1 { - t.Fatalf("Verifier map expected size 1, actual %v", len(VerifierMap)) - } - - // modify the verifier - var parametersString = "{\"allowedLicenses\":[\"MIT\",\"GNU\"]}" - testVerifierSpec = getLicenseCheckerFromParam(parametersString, dirPath) - if err := verifierAddOrReplace(testVerifierSpec, licenseChecker, constants.EmptyNamespace); err != nil { - t.Fatalf("verifierAddOrReplace() expected no error, actual %v", err) - } - - // validate no verifier has been added - if len(VerifierMap) != 1 { - t.Fatalf("Verifier map should be 1 after replacement, actual %v", len(VerifierMap)) - } - - verifierRemove(licenseChecker) - - if len(VerifierMap) != 0 { - t.Fatalf("Verifier map should be 0 after deletion, actual %v", len(VerifierMap)) - } -} - -func TestWriteVerifierStatus(t *testing.T) { - logger := logrus.WithContext(context.Background()) - testCases := []struct { - name string - isSuccess bool - verifier *configv1beta1.Verifier - errString string - reconciler client.StatusClient - }{ - { - name: "success status", - isSuccess: true, - errString: "", - verifier: &configv1beta1.Verifier{}, - reconciler: &mockStatusClient{}, - }, - { - name: "error status", - isSuccess: false, - verifier: &configv1beta1.Verifier{}, - errString: "a long error string that exceeds the max length of 30 characters", - reconciler: &mockStatusClient{}, - }, - { - name: "status update failed", - isSuccess: true, - verifier: &configv1beta1.Verifier{}, - reconciler: &mockStatusClient{ - updateFailed: true, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - writeVerifierStatus(context.Background(), tc.reconciler, tc.verifier, logger, tc.isSuccess, tc.errString) - - if tc.verifier.Status.IsSuccess != tc.isSuccess { - t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.verifier.Status.IsSuccess) - } - - if tc.verifier.Status.Error != tc.errString { - t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.verifier.Status.Error) - } - }) - } -} - -func TestGetCertStoreNamespace(t *testing.T) { - // error scenario, everything is empty, expect error - _, err := getCertStoreNamespace("") - if err.Error() == "environment variable" { - t.Fatalf("env not set should trigger an error") - } - - ratifyDeployedNamespace := "sample" - os.Setenv(utils.RatifyNamespaceEnvVar, ratifyDeployedNamespace) - defer os.Unsetenv(utils.RatifyNamespaceEnvVar) - - // scenario1, when default namespace is provided, then we should expect default - verifierNamespace := "verifierNamespace" - ns, _ := getCertStoreNamespace(verifierNamespace) - if ns != verifierNamespace { - t.Fatalf("default namespace expected") - } - - // scenario2, default is empty, should return ratify installed namespace - ns, _ = getCertStoreNamespace("") - if ns != ratifyDeployedNamespace { - t.Fatalf("default namespace expected") - } -} - -func resetVerifierMap() { - VerifierMap = map[string]vr.ReferenceVerifier{} -} - -func getLicenseCheckerFromParam(parametersString, pluginPath string) configv1beta1.VerifierSpec { - var allowedLicenses = []byte(parametersString) - - return configv1beta1.VerifierSpec{ - Name: licenseChecker, - ArtifactTypes: "application/vnd.ratify.spdx.v0", - Address: pluginPath, - Parameters: runtime.RawExtension{ - Raw: allowedLicenses, - }, - } -} - -func getInvalidVerifierSpec() configv1beta1.VerifierSpec { - return configv1beta1.VerifierSpec{ - Name: "pluginnotfound", - ArtifactTypes: "application/vnd.ratify.spdx.v0", - Address: "test/path", - } -} - -func getDefaultLicenseCheckerSpec(pluginPath string) configv1beta1.VerifierSpec { - var parametersString = "{\"allowedLicenses\":[\"MIT\",\"Apache\"]}" - return getLicenseCheckerFromParam(parametersString, pluginPath) -} diff --git a/pkg/customresources/certificatestores/api.go b/pkg/customresources/certificatestores/api.go new file mode 100644 index 000000000..95d720144 --- /dev/null +++ b/pkg/customresources/certificatestores/api.go @@ -0,0 +1,34 @@ +/* +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 certificatestores + +import ( + "context" + "crypto/x509" +) + +// CertStoreManager is an interface that defines the methods for managing certificate stores across different scopes. +type CertStoreManager interface { + // GetCertsFromStore returns certificates from the given certificate store. + GetCertsFromStore(ctx context.Context, storeName string) ([]*x509.Certificate, error) + + // AddStore adds the given certificate. + AddStore(storeName string, cert []*x509.Certificate) + + // DeleteStore deletes the certificate from the given scope. + DeleteStore(storeName string) + + // AddStoreError adds an error to the given certificate store. + AddStoreError(storeName string, err error) +} diff --git a/pkg/customresources/certificatestores/certificatestores.go b/pkg/customresources/certificatestores/certificatestores.go new file mode 100644 index 000000000..96d33d3b3 --- /dev/null +++ b/pkg/customresources/certificatestores/certificatestores.go @@ -0,0 +1,116 @@ +/* +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 certificatestores + +import ( + "context" + "crypto/x509" + "fmt" + "os" + "strings" + "sync" + + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/pkg/utils" + vu "github.com/ratify-project/ratify/pkg/verifier/utils" +) + +// ActiveCertStores implements the CertStoreManager interface +type ActiveCertStores struct { + // scopedCertStores is mapping from cert store name to certificate list. + // The certificate store name is prefixed with the namespace. + // Example: + // { + // "namespace1/store1": []*x509.Certificate, + // "namespace2/store2": []*x509.Certificate + // } + scopedCertStores sync.Map + + // scopedStoreErrors is mapping from cert store name to error generated while reconciling the store. + // The certificate store name is prefixed with the namespace. + // Example: + // { + // "namespace1/store1": error, + // "namespace2/store2": error + // } + scopedStoreErrors sync.Map +} + +func NewActiveCertStores() CertStoreManager { + return &ActiveCertStores{} +} + +// GetCertStores fulfills the CertStoreManager interface. +// It returns a list of certificates in the given store. +func (c *ActiveCertStores) GetCertsFromStore(ctx context.Context, storeName string) ([]*x509.Certificate, error) { + prependedName, prepended := prependNamespaceToStoreName(storeName) + if !prepended { + return []*x509.Certificate{}, fmt.Errorf("The given store name %s is not namespaced", storeName) + } + + if !hasAccessToStore(ctx, storeName) { + return []*x509.Certificate{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("namespace: [%s] does not have access to certificate store: %s", ctxUtils.GetNamespace(ctx), storeName)).WithRemediation(fmt.Sprintf("Ensure the certificate store: %s is created under namespace: [%s]", storeName, ctxUtils.GetNamespace(ctx))) + } + if err, ok := c.scopedStoreErrors.Load(prependedName); ok && err != nil { + return []*x509.Certificate{}, err.(error) + } + + if certs, ok := c.scopedCertStores.Load(prependedName); ok { + return certs.([]*x509.Certificate), nil + } + return []*x509.Certificate{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("failed to access non-existent certificate store: %s", storeName)) +} + +// AddStore fulfills the CertStoreManager interface. +// It adds the given certificate under cert store. +func (c *ActiveCertStores) AddStore(storeName string, cert []*x509.Certificate) { + c.scopedCertStores.Store(storeName, cert) + c.scopedStoreErrors.Delete(storeName) +} + +// DeleteStore fulfills the CertStoreManager interface. +// It deletes the given cert store. +func (c *ActiveCertStores) DeleteStore(storeName string) { + c.scopedCertStores.Delete(storeName) + c.scopedStoreErrors.Delete(storeName) +} + +func (c *ActiveCertStores) AddStoreError(storeName string, err error) { + c.scopedStoreErrors.Store(storeName, err) +} + +// A namespaced verification request could access certStores in the same namespace. +// A cluster-wide (context namespace is "") verification request could access certStores across all namespaces. +// Note: the cluster-wide behavior is different from KMP as we need to keep the behavior backward compatible. +func hasAccessToStore(ctx context.Context, storeName string) bool { + namespace := ctxUtils.GetNamespace(ctx) + if namespace == constants.EmptyNamespace { + return true + } + return strings.HasPrefix(storeName, namespace+constants.NamespaceSeperator) +} + +// prependNamespaceToStoreName prepends namespace to store name if not already present. +// If the namespace where Ratify deployed is not set, prepended would be set to false. +func prependNamespaceToStoreName(storeName string) (prependedName string, prepended bool) { + if vu.IsNamespacedNamed(storeName) { + return storeName, true + } + if ns, found := os.LookupEnv(utils.RatifyNamespaceEnvVar); found { + return ns + constants.NamespaceSeperator + storeName, true + } + return storeName, false +} diff --git a/pkg/customresources/certificatestores/certificatestores_test.go b/pkg/customresources/certificatestores/certificatestores_test.go new file mode 100644 index 000000000..e6065e4be --- /dev/null +++ b/pkg/customresources/certificatestores/certificatestores_test.go @@ -0,0 +1,176 @@ +/* +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 certificatestores + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "errors" + "math/big" + "os" + "testing" + "time" + + "github.com/ratify-project/ratify/internal/constants" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/pkg/utils" +) + +const ( + namespace1 = "namespace1" + namespace2 = "namespace2" + name1 = "name1" + name2 = "name2" + store1 = namespace1 + "/" + name1 + store2 = namespace2 + "/" + name2 + ratifyDeployedNamespace = "sample" + storeInRatifyNS = ratifyDeployedNamespace + "/" + name1 + storeWithoutNamespace = name1 +) + +var ( + cert1 = generateTestCert() + cert2 = generateTestCert() + certInRatifyNS = generateTestCert() +) + +func TestCertStoresOperations(t *testing.T) { + activeCertStores := NewActiveCertStores() + ctx := context.Background() + certStore1 := []*x509.Certificate{cert1} + + activeCertStores.AddStoreError(store1, errors.New("error1")) + if _, err := activeCertStores.GetCertsFromStore(ctx, store1); err == nil { + t.Fatalf("expect to get error, but got nil") + } + + activeCertStores.AddStore(store1, certStore1) + certs, _ := activeCertStores.GetCertsFromStore(ctx, store1) + if len(certs) != 1 { + t.Fatalf("expect to get 1 certificate, but got: %d", len(certs)) + } + + activeCertStores.DeleteStore(store1) + certs, _ = activeCertStores.GetCertsFromStore(ctx, store1) + if len(certs) != 0 { + t.Fatalf("expect to get 0 certificate, but got: %d", len(certs)) + } +} + +func TestGetCertsFromStore(t *testing.T) { + activeCertStores := NewActiveCertStores() + activeCertStores.AddStore(store1, []*x509.Certificate{cert1}) + activeCertStores.AddStore(store2, []*x509.Certificate{cert2}) + activeCertStores.AddStore(storeInRatifyNS, []*x509.Certificate{certInRatifyNS}) + + os.Setenv(utils.RatifyNamespaceEnvVar, ratifyDeployedNamespace) + defer os.Unsetenv(utils.RatifyNamespaceEnvVar) + + testCases := []struct { + name string + scope string + storeName string + expectedCert *x509.Certificate + }{ + { + name: "clustered access to store with namespace", + scope: constants.EmptyNamespace, + storeName: store1, + expectedCert: cert1, + }, + { + name: "clustered access to store without namespace", + scope: constants.EmptyNamespace, + storeName: storeWithoutNamespace, + expectedCert: certInRatifyNS, + }, + { + name: "clustered access to nonexisting store", + scope: constants.EmptyNamespace, + storeName: "nonexisting", + expectedCert: nil, + }, + { + name: "namespaced access to store under same namespace", + scope: namespace1, + storeName: store1, + expectedCert: cert1, + }, + { + name: "namespaced access to nonexisting store", + scope: "nonexisting", + storeName: "nonexisting/nonexisting", + expectedCert: nil, + }, + { + name: "namespaced access to store under different namespace", + scope: namespace1, + storeName: store2, + expectedCert: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := ctxUtils.SetContextWithNamespace(context.Background(), tc.scope) + certs, _ := activeCertStores.GetCertsFromStore(ctx, tc.storeName) + if len(certs) == 0 { + if tc.expectedCert != nil { + t.Fatalf("Expected to get certificate, but got none") + } + } else { + if certs[0] != tc.expectedCert { + t.Fatalf("Got unexpected certificate") + } + } + }) + } +} + +func generateTestCert() *x509.Certificate { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil + } + + // Create a certificate template + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Example Org"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + // Create the certificate + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil + } + + // Parse the certificate + cert, err := x509.ParseCertificate(certDER) + if err != nil { + return nil + } + + return cert +} diff --git a/pkg/customresources/policies/api.go b/pkg/customresources/policies/api.go new file mode 100644 index 000000000..8ad1170ea --- /dev/null +++ b/pkg/customresources/policies/api.go @@ -0,0 +1,30 @@ +/* +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 policies + +import "github.com/ratify-project/ratify/pkg/policyprovider" + +// PolicyManager is an interface that defines the methods for managing policies across different scopes. +type PolicyManager interface { + // GetPolicy returns the policy for the given scope. + GetPolicy(scope string) policyprovider.PolicyProvider + + // AddPolicy adds the given policy under the given scope. + AddPolicy(scope, policyName string, policy policyprovider.PolicyProvider) + + // DeletePolicy deletes the policy from the given scope. + DeletePolicy(scope, policyName string) +} diff --git a/pkg/customresources/policies/policies.go b/pkg/customresources/policies/policies.go new file mode 100644 index 000000000..338abb9b5 --- /dev/null +++ b/pkg/customresources/policies/policies.go @@ -0,0 +1,74 @@ +/* +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 policies + +import ( + "sync" + + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/policyprovider" +) + +// PolicyWrapper wraps policy provider with its policy name. +type PolicyWrapper struct { + Name string + Policy policyprovider.PolicyProvider +} + +// ActivePolicies implements PolicyManager interface. +type ActivePolicies struct { + // scopedPolicies is a mapping from scope to a policy. + // Note: Scope is utilized for organizing and isolating policies. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide policy. + scopedPolicies sync.Map +} + +func NewActivePolicies() PolicyManager { + return &ActivePolicies{} +} + +// GetPolicy fulfills the PolicyManager interface. +// It returns the policy for the given scope. If no policy is found for the given scope, it returns cluster-wide policy. +func (p *ActivePolicies) GetPolicy(scope string) policyprovider.PolicyProvider { + if scopedPolicy, ok := p.scopedPolicies.Load(scope); ok { + return scopedPolicy.(PolicyWrapper).Policy + } + + if scope != constants.EmptyNamespace { + if policy, ok := p.scopedPolicies.Load(constants.EmptyNamespace); ok { + return policy.(PolicyWrapper).Policy + } + } + return nil +} + +// AddPolicy fulfills the PolicyManager interface. +// It adds the given policy under the given scope. +func (p *ActivePolicies) AddPolicy(scope, policyName string, policy policyprovider.PolicyProvider) { + p.scopedPolicies.Store(scope, PolicyWrapper{ + Name: policyName, + Policy: policy, + }) +} + +// DeletePolicy fulfills the PolicyManager interface. +// It deletes the policy from the given scope. +func (p *ActivePolicies) DeletePolicy(scope, policyName string) { + if policy, ok := p.scopedPolicies.Load(scope); ok { + if policy.(PolicyWrapper).Name == policyName { + p.scopedPolicies.Delete(scope) + } + } +} diff --git a/pkg/customresources/policies/policies_test.go b/pkg/customresources/policies/policies_test.go new file mode 100644 index 000000000..811303f77 --- /dev/null +++ b/pkg/customresources/policies/policies_test.go @@ -0,0 +1,87 @@ +/* +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 policies + +import ( + "context" + "testing" + + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" +) + +type mockPolicy struct{} + +func (p mockPolicy) VerifyNeeded(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) bool { + return true +} + +func (p mockPolicy) ContinueVerifyOnFailure(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor, _ types.VerifyResult) bool { + return true +} + +func (p mockPolicy) ErrorToVerifyResult(_ context.Context, _ string, _ error) types.VerifyResult { + return types.VerifyResult{} +} + +func (p mockPolicy) OverallVerifyResult(_ context.Context, _ []interface{}) bool { + return true +} + +func (p mockPolicy) GetPolicyType(_ context.Context) string { + return "" +} + +const ( + namespace1 = constants.EmptyNamespace + namespace2 = "namespace2" + name1 = "name1" + name2 = "name2" +) + +var ( + policy1 = mockPolicy{} + policy2 = mockPolicy{} +) + +func TestPoliciesOperations(t *testing.T) { + policies := NewActivePolicies() + + policies.AddPolicy(namespace1, name1, policy1) + policies.AddPolicy(namespace2, name1, policy2) + + if policies.GetPolicy(namespace1) != policy1 { + t.Errorf("Expected policy1 to be returned") + } + + if policies.GetPolicy(namespace2) != policy2 { + t.Errorf("Expected policy2 to be returned") + } + + policies.DeletePolicy(namespace2, name1) + + if policies.GetPolicy(namespace2) != policy1 { + t.Errorf("Expected policy1 to be returned") + } + + policies.DeletePolicy(namespace1, name1) + + if policies.GetPolicy(namespace1) != nil { + t.Errorf("Expected no policy to be returned") + } +} diff --git a/pkg/customresources/referrerstores/api.go b/pkg/customresources/referrerstores/api.go new file mode 100644 index 000000000..04f07e40b --- /dev/null +++ b/pkg/customresources/referrerstores/api.go @@ -0,0 +1,32 @@ +/* +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 referrerstores + +import ( + "github.com/ratify-project/ratify/pkg/referrerstore" +) + +// ReferrerStoreManager is an interface that defines the methods for managing referrer stores across different scopes. +type ReferrerStoreManager interface { + // Stores returns the list of referrer stores for the given scope. + GetStores(scope string) []referrerstore.ReferrerStore + + // AddStore adds the given store under the given scope. + AddStore(scope, storeName string, store referrerstore.ReferrerStore) + + // DeleteStore deletes the policy from the given scope. + DeleteStore(scope, storeName string) +} diff --git a/pkg/customresources/referrerstores/stores.go b/pkg/customresources/referrerstores/stores.go new file mode 100644 index 000000000..4de864c5d --- /dev/null +++ b/pkg/customresources/referrerstores/stores.go @@ -0,0 +1,77 @@ +/* +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 referrerstores + +import ( + "sync" + + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/referrerstore" +) + +// ActiveStores implements the ReferrerStoreManager interface. +type ActiveStores struct { + // The structure of the map is as follows: + // The first level maps from scope to stores + // The second level maps from store name to store + // Example: + // { + // "namespace1": { + // "store1": store1, + // "store2": store2 + // } + // } + // Note: Scope is utilized for organizing and isolating stores. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide stores. + ScopedStores sync.Map +} + +func NewActiveStores() ReferrerStoreManager { + return &ActiveStores{} +} + +// GetStores fulfills the ReferrerStoreManager interface. +// It returns all the stores in the ActiveStores for the given scope. If no stores are found for the given scope, it returns cluster-wide stores. +func (s *ActiveStores) GetStores(scope string) []referrerstore.ReferrerStore { + stores := []referrerstore.ReferrerStore{} + if scopedStore, ok := s.ScopedStores.Load(scope); ok { + for _, store := range scopedStore.(map[string]referrerstore.ReferrerStore) { + stores = append(stores, store) + } + } + if len(stores) == 0 && scope != constants.EmptyNamespace { + if clusterStore, ok := s.ScopedStores.Load(constants.EmptyNamespace); ok { + for _, store := range clusterStore.(map[string]referrerstore.ReferrerStore) { + stores = append(stores, store) + } + } + } + return stores +} + +// AddStore fulfills the ReferrerStoreManager interface. +// It adds the given store under the given scope. +func (s *ActiveStores) AddStore(scope, storeName string, store referrerstore.ReferrerStore) { + scopedStore, _ := s.ScopedStores.LoadOrStore(scope, make(map[string]referrerstore.ReferrerStore)) + scopedStore.(map[string]referrerstore.ReferrerStore)[storeName] = store +} + +// DeleteStore fulfills the ReferrerStoreManager interface. +// It deletes the store with the given name under the given scope. +func (s *ActiveStores) DeleteStore(scope, storeName string) { + if scopedStore, ok := s.ScopedStores.Load(scope); ok { + delete(scopedStore.(map[string]referrerstore.ReferrerStore), storeName) + } +} diff --git a/pkg/customresources/referrerstores/stores_test.go b/pkg/customresources/referrerstores/stores_test.go new file mode 100644 index 000000000..7552a8b5e --- /dev/null +++ b/pkg/customresources/referrerstores/stores_test.go @@ -0,0 +1,103 @@ +/* +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 referrerstores + +import ( + "context" + "testing" + + "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + rs "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" +) + +type mockStore struct { + name string +} + +func (s mockStore) Name() string { + return s.name +} + +func (s mockStore) ListReferrers(_ context.Context, _ common.Reference, _ []string, _ string, _ *ocispecs.SubjectDescriptor) (rs.ListReferrersResult, error) { + return rs.ListReferrersResult{}, nil +} + +func (s mockStore) GetBlobContent(_ context.Context, _ common.Reference, _ digest.Digest) ([]byte, error) { + return nil, nil +} + +func (s mockStore) GetReferenceManifest(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) { + return ocispecs.ReferenceManifest{}, nil +} + +func (s mockStore) GetConfig() *config.StoreConfig { + return nil +} + +func (s mockStore) GetSubjectDescriptor(_ context.Context, _ common.Reference) (*ocispecs.SubjectDescriptor, error) { + return nil, nil +} + +const ( + namespace1 = constants.EmptyNamespace + namespace2 = "namespace2" + name1 = "name1" + name2 = "name2" +) + +var ( + store1 = mockStore{name: name1} + store2 = mockStore{name: name2} +) + +func TestStoresOperations(t *testing.T) { + stores := NewActiveStores() + stores.AddStore(namespace1, store1.Name(), store1) + stores.AddStore(namespace1, store2.Name(), store2) + stores.AddStore(namespace2, store1.Name(), store1) + stores.AddStore(namespace2, store2.Name(), store2) + + if len(stores.GetStores(namespace1)) != 2 { + t.Fatalf("Expected 2 stores in namespace %s, got %d", namespace1, len(stores.GetStores(namespace1))) + } + if len(stores.GetStores(namespace2)) != 2 { + t.Fatalf("Expected 2 stores in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) + } + + stores.DeleteStore(namespace2, store1.Name()) + if len(stores.GetStores(namespace2)) != 1 { + t.Fatalf("Expected 1 store in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) + } + + stores.DeleteStore(namespace2, store2.Name()) + if len(stores.GetStores(namespace2)) != 2 { + t.Fatalf("Expected 2 stores in namespace %s, got %d", namespace2, len(stores.GetStores(namespace2))) + } + + stores.DeleteStore(namespace1, store1.Name()) + if len(stores.GetStores(namespace1)) != 1 { + t.Fatalf("Expected 1 store in namespace %s, got %d", namespace1, len(stores.GetStores(namespace1))) + } + + stores.DeleteStore(namespace1, store2.Name()) + if len(stores.GetStores(namespace1)) != 0 { + t.Fatalf("Expected 0 stores in namespace %s, got %d", namespace1, len(stores.GetStores(namespace1))) + } +} diff --git a/pkg/customresources/verifiers/api.go b/pkg/customresources/verifiers/api.go new file mode 100644 index 000000000..4ea34e5dd --- /dev/null +++ b/pkg/customresources/verifiers/api.go @@ -0,0 +1,32 @@ +/* +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 verifiers + +import ( + vr "github.com/ratify-project/ratify/pkg/verifier" +) + +// VerifierManager is an interface that defines the methods for managing verifiers across different scopes. +type VerifierManager interface { + // GetVerifiers returns verifiers under the given scope. + GetVerifiers(scope string) []vr.ReferenceVerifier + + // AddVerifier adds a verifier to the given scope. + AddVerifier(scope, verifierName string, verifier vr.ReferenceVerifier) + + // DeleteVerifier deletes a verifier from the given scope. + DeleteVerifier(scope, verifierName string) +} diff --git a/pkg/customresources/verifiers/verifiers.go b/pkg/customresources/verifiers/verifiers.go new file mode 100644 index 000000000..8d24b1a2a --- /dev/null +++ b/pkg/customresources/verifiers/verifiers.go @@ -0,0 +1,78 @@ +/* +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 verifiers + +import ( + "sync" + + "github.com/ratify-project/ratify/internal/constants" + vr "github.com/ratify-project/ratify/pkg/verifier" +) + +// ActiveVerifiers implements VerifierManger interface. +type ActiveVerifiers struct { + // The structure of the map is as follows: + // The first level maps from scope to verifiers + // The second level maps from verifier name to verifier + // Example: + // { + // "namespace1": { + // "verifier1": verifier1, + // "verifier2": verifier2 + // } + // } + // Note: Scope is utilized for organizing and isolating verifiers. In a Kubernetes (K8s) environment, the scope can be either a namespace or an empty string ("") for cluster-wide verifiers. + //scopedVerifiers map[string]map[string]vr.ReferenceVerifier + scopedVerifiers sync.Map +} + +func NewActiveVerifiers() VerifierManager { + return &ActiveVerifiers{} +} + +// GetVerifiers implements the VerifierManager interface. +// It returns a list of verifiers for the given scope. If no verifiers are found for the given scope, it returns cluster-wide verifiers. +func (v *ActiveVerifiers) GetVerifiers(scope string) []vr.ReferenceVerifier { + verifiers := []vr.ReferenceVerifier{} + if scopedVerifier, ok := v.scopedVerifiers.Load(scope); ok { + for _, verifier := range scopedVerifier.(map[string]vr.ReferenceVerifier) { + verifiers = append(verifiers, verifier) + } + } + if len(verifiers) == 0 && scope != constants.EmptyNamespace { + if clusterVerifier, ok := v.scopedVerifiers.Load(constants.EmptyNamespace); ok { + for _, verifier := range clusterVerifier.(map[string]vr.ReferenceVerifier) { + verifiers = append(verifiers, verifier) + } + } + } + return verifiers +} + +// AddVerifier fulfills the VerifierManager interface. +// It adds the given verifier under the given scope. +func (v *ActiveVerifiers) AddVerifier(scope, verifierName string, verifier vr.ReferenceVerifier) { + scopedVerifier, _ := v.scopedVerifiers.LoadOrStore(scope, make(map[string]vr.ReferenceVerifier)) + scopedVerifier.(map[string]vr.ReferenceVerifier)[verifierName] = verifier +} + +// DeleteVerifier fulfills the VerifierManager interface. +// It deletes the verfier of the given name under the given scope. +func (v *ActiveVerifiers) DeleteVerifier(scope, verifierName string) { + if scopedVerifier, ok := v.scopedVerifiers.Load(scope); ok { + delete(scopedVerifier.(map[string]vr.ReferenceVerifier), verifierName) + } +} diff --git a/pkg/customresources/verifiers/verifiers_test.go b/pkg/customresources/verifiers/verifiers_test.go new file mode 100644 index 000000000..852d664fe --- /dev/null +++ b/pkg/customresources/verifiers/verifiers_test.go @@ -0,0 +1,98 @@ +/* +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 verifiers + +import ( + "context" + "testing" + + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/verifier" +) + +type mockVerifier struct { + name string +} + +func (v mockVerifier) Name() string { + return v.name +} + +func (v mockVerifier) Type() string { + return "mockType" +} + +func (v mockVerifier) CanVerify(_ context.Context, _ ocispecs.ReferenceDescriptor) bool { + return true +} + +func (v mockVerifier) Verify(_ context.Context, _ common.Reference, _ ocispecs.ReferenceDescriptor, _ referrerstore.ReferrerStore) (verifier.VerifierResult, error) { + return verifier.VerifierResult{}, nil +} + +func (v mockVerifier) GetNestedReferences() []string { + return nil +} + +const ( + namespace1 = constants.EmptyNamespace + namespace2 = "namespace2" + name1 = "name1" + name2 = "name2" +) + +var ( + verifier1 = mockVerifier{name: name1} + verifier2 = mockVerifier{name: name2} +) + +func TestVerifiersOperations(t *testing.T) { + verifiers := NewActiveVerifiers() + verifiers.AddVerifier(namespace1, verifier1.Name(), verifier1) + verifiers.AddVerifier(namespace1, verifier2.Name(), verifier2) + verifiers.AddVerifier(namespace2, verifier1.Name(), verifier1) + verifiers.AddVerifier(namespace2, verifier2.Name(), verifier2) + + if len(verifiers.GetVerifiers(namespace1)) != 2 { + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace1))) + } + if len(verifiers.GetVerifiers(namespace2)) != 2 { + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) + } + + verifiers.DeleteVerifier(namespace2, verifier1.Name()) + if len(verifiers.GetVerifiers(namespace2)) != 1 { + t.Fatalf("Expected 1 verifier, got %d", len(verifiers.GetVerifiers(namespace2))) + } + + verifiers.DeleteVerifier(namespace2, verifier2.Name()) + if len(verifiers.GetVerifiers(namespace2)) != 2 { + t.Fatalf("Expected 2 verifiers, got %d", len(verifiers.GetVerifiers(namespace2))) + } + + verifiers.DeleteVerifier(namespace1, verifier1.Name()) + if len(verifiers.GetVerifiers(namespace1)) != 1 { + t.Fatalf("Expected 1 verifier, got %d", len(verifiers.GetVerifiers(namespace1))) + } + + verifiers.DeleteVerifier(namespace1, verifier2.Name()) + if len(verifiers.GetVerifiers(namespace1)) != 0 { + t.Fatalf("Expected 0 verifiers, got %d", len(verifiers.GetVerifiers(namespace1))) + } +} diff --git a/pkg/executor/api.go b/pkg/executor/api.go index 8ea2d5c95..c40592125 100644 --- a/pkg/executor/api.go +++ b/pkg/executor/api.go @@ -19,7 +19,7 @@ import ( "context" "time" - "github.com/deislabs/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/executor/types" ) // VerifyParameters describes the subject verification parameters diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go index 74a071d26..b364bee30 100644 --- a/pkg/executor/core/executor.go +++ b/pkg/executor/core/executor.go @@ -21,21 +21,21 @@ import ( "sync" "time" - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - e "github.com/deislabs/ratify/pkg/executor" - "github.com/deislabs/ratify/pkg/executor/config" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/metrics" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/policyprovider" - pt "github.com/deislabs/ratify/pkg/policyprovider/types" - "github.com/deislabs/ratify/pkg/referrerstore" - su "github.com/deislabs/ratify/pkg/referrerstore/utils" - "github.com/deislabs/ratify/pkg/utils" - vr "github.com/deislabs/ratify/pkg/verifier" - vt "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + e "github.com/ratify-project/ratify/pkg/executor" + "github.com/ratify-project/ratify/pkg/executor/config" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/metrics" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/policyprovider" + pt "github.com/ratify-project/ratify/pkg/policyprovider/types" + "github.com/ratify-project/ratify/pkg/referrerstore" + su "github.com/ratify-project/ratify/pkg/referrerstore/utils" + "github.com/ratify-project/ratify/pkg/utils" + vr "github.com/ratify-project/ratify/pkg/verifier" + vt "github.com/ratify-project/ratify/pkg/verifier/types" "golang.org/x/sync/errgroup" ) @@ -60,6 +60,9 @@ type Executor struct { // TODO Logging within executor // VerifySubject verifies the subject and returns results. func (executor Executor) VerifySubject(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) { + if executor.PolicyEnforcer == nil { + return types.VerifyResult{}, errors.ErrorCodePolicyProviderNotFound.WithDetail("Policy configuration not found") + } result, err := executor.verifySubjectInternal(ctx, verifyParameters) if err != nil { // get the result for the error based on the policy. @@ -80,7 +83,7 @@ func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParame } if executor.PolicyEnforcer.GetPolicyType(ctx) == pt.ConfigPolicy { if len(verifierReports) == 0 { - return types.VerifyResult{}, errors.ErrorCodeReferrersNotFound.WithComponentType(errors.Executor) + return types.VerifyResult{}, errors.ErrorCodeNoVerifierReport.WithDetail(fmt.Sprintf("No verification results for the artifact %s. Ensure verifiers are properly configured and that artifact metadata is attached", verifyParameters.Subject)) } } // If it requires embedded Rego Policy Engine make the decision, execute @@ -172,19 +175,17 @@ func (executor Executor) verifyReferenceForJSONPolicy(ctx context.Context, subje if verifier.CanVerify(ctx, referenceDesc) { verifierStartTime := time.Now() verifyResult, err := verifier.Verify(ctx, subjectRef, referenceDesc, referrerStore) - verifyResult.Subject = subjectRef.String() if err != nil { - verifyResult = vr.VerifierResult{ - IsSuccess: false, - Name: verifier.Name(), - Type: verifier.Type(), - Message: errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()} + verifierErr := errors.ErrorCodeVerifyReferenceFailure.WithError(err) + verifyResult = vr.NewVerifierResult("", verifier.Name(), verifier.Type(), "", false, &verifierErr, nil) } if len(verifier.GetNestedReferences()) > 0 { executor.addNestedVerifierResult(ctx, referenceDesc, subjectRef, &verifyResult) } + verifyResult.Subject = subjectRef.String() + verifyResult.ReferenceDigest = referenceDesc.Digest.String() verifyResult.ArtifactType = referenceDesc.ArtifactType verifyResults = append(verifyResults, verifyResult) isSuccess = verifyResult.IsSuccess @@ -223,11 +224,8 @@ func (executor Executor) verifyReferenceForRegoPolicy(ctx context.Context, subje verifierStartTime := time.Now() verifierResult, err := verifier.Verify(errCtx, subjectRef, referenceDesc, referrerStore) if err != nil { - verifierReport = vt.VerifierResult{ - IsSuccess: false, - Name: verifier.Name(), - Type: verifier.Type(), - Message: errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()} + verifierErr := errors.ErrorCodeVerifyReferenceFailure.WithError(err) + verifierReport = vt.CreateVerifierResult(verifier.Name(), verifier.Type(), "", false, &verifierErr) } else { verifierReport = vt.NewVerifierResult(verifierResult) } @@ -290,7 +288,7 @@ func (executor Executor) addNestedReports(ctx context.Context, referenceDes ocis for _, report := range reports.VerifierReports { nestedReport, err := types.NewNestedVerifierReport(report) if err != nil { - return errors.ErrorCodeExecutorFailure.WithError(err).WithComponentType(errors.Executor) + return errors.ErrorCodeExecutorFailure.WithError(err) } nestedReports = append(nestedReports, nestedReport) } diff --git a/pkg/executor/core/executor_test.go b/pkg/executor/core/executor_test.go index 36edaff76..b1e7c3aef 100644 --- a/pkg/executor/core/executor_test.go +++ b/pkg/executor/core/executor_test.go @@ -22,22 +22,22 @@ import ( "testing" "time" - ratifyerrors "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common" - e "github.com/deislabs/ratify/pkg/executor" - exConfig "github.com/deislabs/ratify/pkg/executor/config" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/policyprovider" - policyConfig "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" - policyTypes "github.com/deislabs/ratify/pkg/policyprovider/types" - pt "github.com/deislabs/ratify/pkg/policyprovider/types" - "github.com/deislabs/ratify/pkg/referrerstore" - storeConfig "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/mocks" - "github.com/deislabs/ratify/pkg/verifier" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" + ratifyerrors "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + e "github.com/ratify-project/ratify/pkg/executor" + exConfig "github.com/ratify-project/ratify/pkg/executor/config" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/policyprovider" + policyConfig "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" + policyTypes "github.com/ratify-project/ratify/pkg/policyprovider/types" + pt "github.com/ratify-project/ratify/pkg/policyprovider/types" + "github.com/ratify-project/ratify/pkg/referrerstore" + storeConfig "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier" ) const ( @@ -188,7 +188,7 @@ func TestVerifySubjectInternal_ResolveSubjectDescriptor_Success(t *testing.T) { Subject: "localhost:5000/net-monitor:v1", } - if _, err := executor.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ratifyerrors.ErrorCodeReferrersNotFound.WithDetail("")) { + if _, err := executor.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ratifyerrors.ErrorCodeNoVerifierReport.WithDetail("")) { t.Fatalf("expected ErrReferrersNotFound actual %v", err) } } @@ -214,7 +214,7 @@ func TestVerifySubjectInternal_Verify_NoReferrers(t *testing.T) { Subject: "localhost:5000/net-monitor:v1", } - if _, err := ex.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ratifyerrors.ErrorCodeReferrersNotFound.WithDetail("")) { + if _, err := ex.verifySubjectInternal(context.Background(), verifyParameters); !errors.Is(err, ratifyerrors.ErrorCodeNoVerifierReport.WithDetail("")) { t.Fatalf("expected ErrReferrersNotFound actual %v", err) } } @@ -240,7 +240,7 @@ func TestVerifySubjectInternal_CanVerify_ExpectedResults(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == testArtifactType1 }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -293,7 +293,7 @@ func TestVerifySubjectInternal_VerifyFailures_ExpectedResults(t *testing.T) { }, } ver := &TestVerifier{ - CanVerifyFunc: func(at string) bool { + CanVerifyFunc: func(_ string) bool { return true }, VerifyResult: func(artifactType string) bool { @@ -345,10 +345,10 @@ func TestVerifySubjectInternal_VerifySuccess_ExpectedResults(t *testing.T) { }, } ver := &TestVerifier{ - CanVerifyFunc: func(at string) bool { + CanVerifyFunc: func(_ string) bool { return true }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -402,7 +402,7 @@ func TestVerifySubjectInternalWithDecision_MultipleArtifacts_ExpectedResults(t * }, } ver := &TestVerifier{ - CanVerifyFunc: func(at string) bool { + CanVerifyFunc: func(_ string) bool { return true }, VerifyResult: func(artifactType string) bool { @@ -460,7 +460,7 @@ func TestVerifySubjectInternal_NestedReferences_Expected(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == mocks.SbomArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, nestedReferences: []string{"string-content-does-not-matter"}, @@ -470,7 +470,7 @@ func TestVerifySubjectInternal_NestedReferences_Expected(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == mocks.SignatureArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -538,7 +538,7 @@ func TestVerifySubjectInternal_NoNestedReferences_Expected(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == mocks.SbomArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } @@ -547,7 +547,7 @@ func TestVerifySubjectInternal_NoNestedReferences_Expected(t *testing.T) { CanVerifyFunc: func(at string) bool { return at == mocks.SignatureArtifactType }, - VerifyResult: func(artifactType string) bool { + VerifyResult: func(_ string) bool { return true }, } diff --git a/pkg/executor/core/executorwithcache.go b/pkg/executor/core/executorwithcache.go index c3e1c2c52..c95171fa7 100644 --- a/pkg/executor/core/executorwithcache.go +++ b/pkg/executor/core/executorwithcache.go @@ -19,9 +19,9 @@ import ( "context" "time" - "github.com/deislabs/ratify/pkg/executor" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/verifiercache" + "github.com/ratify-project/ratify/pkg/executor" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/verifiercache" ) // ExecutorWithCache wraps the executor with a verifier cache diff --git a/pkg/executor/core/testtypes.go b/pkg/executor/core/testtypes.go index fd28a410d..e3717d4cf 100644 --- a/pkg/executor/core/testtypes.go +++ b/pkg/executor/core/testtypes.go @@ -18,10 +18,10 @@ package core import ( "context" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/verifier" ) type TestVerifier struct { diff --git a/pkg/executor/types/types.go b/pkg/executor/types/types.go index d615991f5..30bac9a1b 100644 --- a/pkg/executor/types/types.go +++ b/pkg/executor/types/types.go @@ -18,7 +18,7 @@ package types import ( "fmt" - "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/ratify-project/ratify/pkg/verifier/types" ) // VerifyResult describes the results of verifying a subject @@ -29,6 +29,7 @@ type VerifyResult struct { // NestedVerifierReport describes the results of verifying an artifact and its // nested artifacts by available verifiers. +// Note: NestedVerifierReport is used for verification results in v1. type NestedVerifierReport struct { Subject string `json:"subject"` ReferenceDigest string `json:"referenceDigest"` diff --git a/pkg/keymanagementprovider/azurekeyvault/auth.go b/pkg/keymanagementprovider/azurekeyvault/auth.go index 6769038eb..cd4d248f7 100644 --- a/pkg/keymanagementprovider/azurekeyvault/auth.go +++ b/pkg/keymanagementprovider/azurekeyvault/auth.go @@ -25,10 +25,9 @@ import ( "strings" "time" - "github.com/deislabs/ratify/pkg/utils/azureauth" + "github.com/ratify-project/ratify/pkg/utils/azureauth" "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" ) const ( @@ -37,7 +36,6 @@ const ( // the format for expires_on in UTC without AM/PM expiresOnDateFormat = "1/2/2006 15:04:05 +00:00" - tokenTypeBearer = "Bearer" // For Azure AD Workload Identity, the audience recommended for use is // "api://AzureADTokenExchange" DefaultTokenAudience = "api://AzureADTokenExchange" //nolint @@ -64,13 +62,7 @@ func getAuthorizerForWorkloadIdentity(ctx context.Context, tenantID, clientID, r return nil, fmt.Errorf("failed to acquire token: %w", err) } - token := adal.Token{ - AccessToken: result.AccessToken, - Resource: resource, - Type: tokenTypeBearer, - } - token.ExpiresOn, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)) - if err != nil { + if _, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)); err != nil { return nil, fmt.Errorf("failed to parse expires_on: %w", err) } diff --git a/pkg/keymanagementprovider/azurekeyvault/provider.go b/pkg/keymanagementprovider/azurekeyvault/provider.go index 3230ae0ff..da885af90 100644 --- a/pkg/keymanagementprovider/azurekeyvault/provider.go +++ b/pkg/keymanagementprovider/azurekeyvault/provider.go @@ -19,22 +19,24 @@ package azurekeyvault // Source: https://github.com/Azure/secrets-store-csi-driver-provider-azure/tree/release-1.4/pkg/provider import ( "context" + "crypto" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" - "reflect" "strings" "time" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault/types" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" - "github.com/deislabs/ratify/pkg/metrics" + "github.com/go-jose/go-jose/v3" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/internal/version" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault/types" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/factory" + "github.com/ratify-project/ratify/pkg/metrics" "golang.org/x/crypto/pkcs12" kv "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" @@ -42,22 +44,23 @@ import ( ) const ( - providerName string = "azurekeyvault" + ProviderName string = "azurekeyvault" PKCS12ContentType string = "application/x-pkcs12" PEMContentType string = "application/x-pem-file" ) var logOpt = logger.Option{ - ComponentType: logger.CertProvider, + ComponentType: logger.KeyManagementProvider, } type AKVKeyManagementProviderConfig struct { - Type string `json:"type"` - VaultURI string `json:"vaultURI"` - TenantID string `json:"tenantID"` - ClientID string `json:"clientID"` - CloudName string `json:"cloudName,omitempty"` - Certificates []types.KeyVaultCertificate `json:"certificates,omitempty"` + Type string `json:"type"` + VaultURI string `json:"vaultURI"` + TenantID string `json:"tenantID"` + ClientID string `json:"clientID"` + CloudName string `json:"cloudName,omitempty"` + Certificates []types.KeyVaultValue `json:"certificates,omitempty"` + Keys []types.KeyVaultValue `json:"keys,omitempty"` } type akvKMProvider struct { @@ -66,14 +69,20 @@ type akvKMProvider struct { tenantID string clientID string cloudName string - certificates []types.KeyVaultCertificate + certificates []types.KeyVaultValue + keys []types.KeyVaultValue cloudEnv *azure.Environment + kvClient *kv.BaseClient } type akvKMProviderFactory struct{} +// initKVClient is a function to initialize the keyvault client +// used for mocking purposes +var initKVClient = initializeKvClient + // init calls to register the provider func init() { - factory.Register(providerName, &akvKMProviderFactory{}) + factory.Register(ProviderName, &akvKMProviderFactory{}) } // Create creates a new instance of the provider after marshalling and validating the configuration @@ -86,60 +95,60 @@ func (f *akvKMProviderFactory) Create(_ string, keyManagementProviderConfig conf } if err := json.Unmarshal(keyManagementProviderConfigBytes, &conf); err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, "", re.EmptyLink, err, "failed to parse AKV key management provider configuration", re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, "", re.EmptyLink, err, "failed to parse AKV key management provider configuration", re.HideStackTrace) } azureCloudEnv, err := parseAzureEnvironment(conf.CloudName) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, fmt.Sprintf("cloudName %s is not valid", conf.CloudName), re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, fmt.Sprintf("cloudName %s is not valid", conf.CloudName), re.HideStackTrace) } - if len(conf.Certificates) == 0 { - return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "no keyvault certificates configured", re.HideStackTrace) + if len(conf.Certificates) == 0 && len(conf.Keys) == 0 { + return nil, re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "no keyvault certificates or keys configured", re.HideStackTrace) } provider := &akvKMProvider{ - provider: providerName, + provider: ProviderName, vaultURI: strings.TrimSpace(conf.VaultURI), tenantID: strings.TrimSpace(conf.TenantID), clientID: strings.TrimSpace(conf.ClientID), cloudName: strings.TrimSpace(conf.CloudName), certificates: conf.Certificates, + keys: conf.Keys, cloudEnv: azureCloudEnv, } if err := provider.validate(); err != nil { return nil, err } + logger.GetLogger(context.Background(), logOpt).Debugf("vaultURI %s", provider.vaultURI) + + kvClient, err := initKVClient(context.Background(), provider.cloudEnv.KeyVaultEndpoint, provider.tenantID, provider.clientID, version.UserAgent) + if err != nil { + return nil, re.ErrorCodePluginInitFailure.NewError(re.KeyManagementProvider, ProviderName, re.AKVLink, err, "failed to create keyvault client", re.HideStackTrace) + } + provider.kvClient = kvClient + return provider, nil } // GetCertificates returns an array of certificates based on certificate properties defined in config // get certificate retrieve the entire cert chain using getSecret API call func (s *akvKMProvider) GetCertificates(ctx context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { - logger.GetLogger(ctx, logOpt).Debugf("vaultURI %s", s.vaultURI) - - kvClient, err := initializeKvClient(ctx, s.cloudEnv.KeyVaultEndpoint, s.tenantID, s.clientID) - if err != nil { - return nil, nil, re.ErrorCodePluginInitFailure.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to get keyvault client", re.HideStackTrace) - } - certsMap := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{} certsStatus := []map[string]string{} for _, keyVaultCert := range s.certificates { logger.GetLogger(ctx, logOpt).Debugf("fetching secret from key vault, certName %v, keyvault %v", keyVaultCert.Name, s.vaultURI) // fetch the object from Key Vault - // GetSecret is required so we can fetch the entire cert chain. See issue https://github.com/deislabs/ratify/issues/695 for details + // GetSecret is required so we can fetch the entire cert chain. See issue https://github.com/ratify-project/ratify/issues/695 for details startTime := time.Now() - secretBundle, err := kvClient.GetSecret(ctx, s.vaultURI, keyVaultCert.Name, keyVaultCert.Version) - + secretBundle, err := s.kvClient.GetSecret(ctx, s.vaultURI, keyVaultCert.Name, keyVaultCert.Version) if err != nil { return nil, nil, fmt.Errorf("failed to get secret objectName:%s, objectVersion:%s, error: %w", keyVaultCert.Name, keyVaultCert.Version, err) } certResult, certProperty, err := getCertsFromSecretBundle(ctx, secretBundle, keyVaultCert.Name) - if err != nil { return nil, nil, fmt.Errorf("failed to get certificates from secret bundle:%w", err) } @@ -150,42 +159,59 @@ func (s *akvKMProvider) GetCertificates(ctx context.Context) (map[keymanagementp certsMap[certMapKey] = certResult } - return certsMap, getCertStatusMap(certsStatus), nil + return certsMap, getStatusMap(certsStatus, types.CertificatesStatus), nil } -// azure keyvault provider certificate status is a map from "certificates" key to an array of of certificate status -func getCertStatusMap(certsStatus []map[string]string) keymanagementprovider.KeyManagementProviderStatus { - status := keymanagementprovider.KeyManagementProviderStatus{} - status[types.CertificatesStatus] = certsStatus - return status -} +// GetKeys returns an array of keys based on key properties defined in config +func (s *akvKMProvider) GetKeys(ctx context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + keysMap := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{} + keysStatus := []map[string]string{} -// return a certificate status object that consist of the cert name, version and last refreshed time -func getCertStatusProperty(certificateName, version, lastRefreshed string) map[string]string { - certProperty := map[string]string{} - certProperty[types.CertificateName] = certificateName - certProperty[types.CertificateVersion] = version - certProperty[types.CertificateLastRefreshed] = lastRefreshed - return certProperty -} + for _, keyVaultKey := range s.keys { + logger.GetLogger(ctx, logOpt).Debugf("fetching key from key vault, keyName %v, keyvault %v", keyVaultKey.Name, s.vaultURI) -// formatKeyVaultCertificate formats the fields in KeyVaultCertificate -func formatKeyVaultCertificate(object *types.KeyVaultCertificate) { - if object == nil { - return - } - objectPtr := reflect.ValueOf(object) - objectValue := objectPtr.Elem() + // fetch the key object from Key Vault + startTime := time.Now() + keyBundle, err := s.kvClient.GetKey(ctx, s.vaultURI, keyVaultKey.Name, keyVaultKey.Version) + if err != nil { + return nil, nil, fmt.Errorf("failed to get key objectName:%s, objectVersion:%s, error: %w", keyVaultKey.Name, keyVaultKey.Version, err) + } - for i := 0; i < objectValue.NumField(); i++ { - field := objectValue.Field(i) - if field.Type() != reflect.TypeOf("") { - continue + if keyBundle.Attributes != nil && keyBundle.Attributes.Enabled != nil && !*keyBundle.Attributes.Enabled { + return nil, nil, fmt.Errorf("key %s version %s is disabled. please re-enable in azure key vault or remove reference to this key", keyVaultKey.Name, keyVaultKey.Version) } - str := field.Interface().(string) - str = strings.TrimSpace(str) - field.SetString(str) + + publicKey, err := getKeyFromKeyBundle(keyBundle) + if err != nil { + return nil, nil, fmt.Errorf("failed to get key from key bundle:%w", err) + } + keysMap[keymanagementprovider.KMPMapKey{Name: keyVaultKey.Name, Version: keyVaultKey.Version}] = publicKey + metrics.ReportAKVCertificateDuration(ctx, time.Since(startTime).Milliseconds(), keyVaultKey.Name) + properties := getStatusProperty(keyVaultKey.Name, keyVaultKey.Version, time.Now().Format(time.RFC3339)) + keysStatus = append(keysStatus, properties) } + + return keysMap, getStatusMap(keysStatus, types.KeysStatus), nil +} + +func (s *akvKMProvider) IsRefreshable() bool { + return true +} + +// azure keyvault provider certificate/key status is a map from "certificates" key or "keys" key to an array of key management provider status +func getStatusMap(statusMap []map[string]string, contentType string) keymanagementprovider.KeyManagementProviderStatus { + status := keymanagementprovider.KeyManagementProviderStatus{} + status[contentType] = statusMap + return status +} + +// return a status object that consist of the cert/key name, version and last refreshed time +func getStatusProperty(name, version, lastRefreshed string) map[string]string { + properties := map[string]string{} + properties[types.StatusName] = name + properties[types.StatusVersion] = version + properties[types.StatusLastRefreshed] = lastRefreshed + return properties } // parseAzureEnvironment returns azure environment by name @@ -200,18 +226,18 @@ func parseAzureEnvironment(cloudName string) (*azure.Environment, error) { return &env, err } -func initializeKvClient(ctx context.Context, keyVaultEndpoint, tenantID, clientID string) (*kv.BaseClient, error) { +func initializeKvClient(ctx context.Context, keyVaultEndpoint, tenantID, clientID, userAgent string) (*kv.BaseClient, error) { kvClient := kv.New() kvEndpoint := strings.TrimSuffix(keyVaultEndpoint, "/") - err := kvClient.AddToUserAgent("ratify") + err := kvClient.AddToUserAgent(userAgent) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to add user agent to keyvault client", re.PrintStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to add user agent to keyvault client.").WithRemediation(re.AKVLink).WithError(err) } kvClient.Authorizer, err = getAuthorizerForWorkloadIdentity(ctx, tenantID, clientID, kvEndpoint) if err != nil { - return nil, re.ErrorCodeAuthDenied.NewError(re.CertProvider, providerName, re.AKVLink, err, "failed to get authorizer for keyvault client", re.PrintStackTrace) + return nil, re.ErrorCodeAuthDenied.WithDetail("failed to get authorizer for keyvault client").WithRemediation(re.AKVLink).WithError(err) } return &kvClient, nil } @@ -220,7 +246,7 @@ func initializeKvClient(ctx context.Context, keyVaultEndpoint, tenantID, clientI // In a certificate chain scenario, all certificates from root to leaf will be returned func getCertsFromSecretBundle(ctx context.Context, secretBundle kv.SecretBundle, certName string) ([]*x509.Certificate, []map[string]string, error) { if secretBundle.ContentType == nil || secretBundle.Value == nil || secretBundle.ID == nil { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "found invalid secret bundle for certificate %s, contentType, value, and id must not be nil", re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "found invalid secret bundle for certificate %s, contentType, value, and id must not be nil", re.HideStackTrace) } version := getObjectVersion(*secretBundle.ID) @@ -229,7 +255,7 @@ func getCertsFromSecretBundle(ctx context.Context, secretBundle kv.SecretBundle, // akv plugin supports both PKCS12 and PEM. https://github.com/Azure/notation-azure-kv/blob/558e7345ef8318783530de6a7a0a8420b9214ba8/Notation.Plugin.AzureKeyVault/KeyVault/KeyVaultClient.cs#L192 if *secretBundle.ContentType != PKCS12ContentType && *secretBundle.ContentType != PEMContentType { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, fmt.Sprintf("certificate %s version %s, unsupported secret content type %s, supported type are %s and %s", certName, version, *secretBundle.ContentType, PKCS12ContentType, PEMContentType), re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, fmt.Sprintf("certificate %s version %s, unsupported secret content type %s, supported type are %s and %s", certName, version, *secretBundle.ContentType, PKCS12ContentType, PEMContentType), re.HideStackTrace) } results := []*x509.Certificate{} @@ -241,12 +267,12 @@ func getCertsFromSecretBundle(ctx context.Context, secretBundle kv.SecretBundle, if *secretBundle.ContentType == PKCS12ContentType { p12, err := base64.StdEncoding.DecodeString(*secretBundle.Value) if err != nil { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, err, fmt.Sprintf("azure keyvault certificate provider: failed to decode PKCS12 Value. Certificate %s, version %s", certName, version), re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, err, fmt.Sprintf("azure keyvault key management provider: failed to decode PKCS12 Value. Certificate %s, version %s", certName, version), re.HideStackTrace) } blocks, err := pkcs12.ToPEM(p12, "") if err != nil { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, err, fmt.Sprintf("azure keyvault certificate provider: failed to convert PKCS12 Value to PEM. Certificate %s, version %s", certName, version), re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, err, fmt.Sprintf("azure keyvault key management provider: failed to convert PKCS12 Value to PEM. Certificate %s, version %s", certName, version), re.HideStackTrace) } var pemData []byte @@ -261,32 +287,61 @@ func getCertsFromSecretBundle(ctx context.Context, secretBundle kv.SecretBundle, for block != nil { switch block.Type { case "PRIVATE KEY": - logger.GetLogger(ctx, logOpt).Warnf("azure keyvault certificate provider: certificate %s, version %s private key skipped. Please see doc to learn how to create a new certificate in keyvault with non exportable keys. https://learn.microsoft.com/en-us/azure/key-vault/certificates/how-to-export-certificate?tabs=azure-cli#exportable-and-non-exportable-keys", certName, version) + logger.GetLogger(ctx, logOpt).Warnf("azure keyvault key management provider: certificate %s, version %s private key skipped. Please see doc to learn how to create a new certificate in keyvault with non exportable keys. https://learn.microsoft.com/en-us/azure/key-vault/certificates/how-to-export-certificate?tabs=azure-cli#exportable-and-non-exportable-keys", certName, version) case "CERTIFICATE": var pemData []byte pemData = append(pemData, pem.EncodeToMemory(block)...) decodedCerts, err := keymanagementprovider.DecodeCertificates(pemData) if err != nil { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, err, fmt.Sprintf("azure keyvault certificate provider: failed to decode Certificate %s, version %s", certName, version), re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, err, fmt.Sprintf("azure keyvault key management provider: failed to decode Certificate %s, version %s", certName, version), re.HideStackTrace) } for _, cert := range decodedCerts { results = append(results, cert) - certProperty := getCertStatusProperty(certName, version, lastRefreshed) + certProperty := getStatusProperty(certName, version, lastRefreshed) certsStatus = append(certsStatus, certProperty) } default: - logger.GetLogger(ctx, logOpt).Warnf("certificate '%s', version '%s': azure keyvault certificate provider detected unknown block type %s", certName, version, block.Type) + logger.GetLogger(ctx, logOpt).Warnf("certificate '%s', version '%s': azure keyvault key management provider detected unknown block type %s", certName, version, block.Type) } block, rest = pem.Decode(rest) if block == nil && len(rest) > 0 { - return nil, nil, re.ErrorCodeCertInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, fmt.Sprintf("certificate '%s', version '%s': azure keyvault certificate provider error, block is nil and remaining block to parse > 0", certName, version), re.HideStackTrace) + return nil, nil, re.ErrorCodeCertInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, fmt.Sprintf("certificate '%s', version '%s': azure keyvault key management provider error, block is nil and remaining block to parse > 0", certName, version), re.HideStackTrace) } } logger.GetLogger(ctx, logOpt).Debugf("azurekeyvault certprovider getCertsFromSecretBundle: %v certificates parsed, Certificate '%s', version '%s'", len(results), certName, version) return results, certsStatus, nil } +// Based on https://github.com/sigstore/sigstore/blob/8b208f7d608b80a7982b2a66358b8333b1eec542/pkg/signature/kms/azure/client.go#L258 +func getKeyFromKeyBundle(keyBundle kv.KeyBundle) (crypto.PublicKey, error) { + webKey := keyBundle.Key + if webKey == nil { + return nil, re.ErrorCodeKeyInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "found invalid key bundle, key must not be nil", re.HideStackTrace) + } + + keyType := webKey.Kty + switch keyType { + case kv.ECHSM: + webKey.Kty = kv.EC + case kv.RSAHSM: + webKey.Kty = kv.RSA + } + + keyBytes, err := json.Marshal(webKey) + if err != nil { + return nil, re.ErrorCodeKeyInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, err, "failed to marshal key", re.HideStackTrace) + } + + key := jose.JSONWebKey{} + err = key.UnmarshalJSON(keyBytes) + if err != nil { + return nil, re.ErrorCodeKeyInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, err, "failed to unmarshal key into JSON Web Key", re.HideStackTrace) + } + + return key.Key, nil +} + // getObjectVersion parses the id to retrieve the version // of object fetched // example id format - https://kindkv.vault.azure.net/secrets/actual/1f304204f3624873aab40231241243eb @@ -297,25 +352,29 @@ func getObjectVersion(id string) string { return splitID[len(splitID)-1] } -// validate checks vaultURI, tenantID, clientID are set and all certificates have a name -// removes all whitespace from key vault certificate fields +// validate checks vaultURI, tenantID, clientID are set and all certificates/keys have a name func (s *akvKMProvider) validate() error { if s.vaultURI == "" { - return re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "vaultURI is not set", re.HideStackTrace) + return re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "vaultURI is not set", re.HideStackTrace) } if s.tenantID == "" { - return re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "tenantID is not set", re.HideStackTrace) + return re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "tenantID is not set", re.HideStackTrace) } if s.clientID == "" { - return re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, "clientID is not set", re.HideStackTrace) + return re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, "clientID is not set", re.HideStackTrace) } // all certificates must have a name for i := range s.certificates { - // remove whitespace from all fields in key vault cert - formatKeyVaultCertificate(&s.certificates[i]) if s.certificates[i].Name == "" { - return re.ErrorCodeConfigInvalid.NewError(re.CertProvider, providerName, re.EmptyLink, nil, fmt.Sprintf("certificate name is not set for certificate %d", i), re.HideStackTrace) + return re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, fmt.Sprintf("name is not set for the %d th certificate", i+1), re.HideStackTrace) + } + } + + // all keys must have a name + for i := range s.keys { + if s.keys[i].Name == "" { + return re.ErrorCodeConfigInvalid.NewError(re.KeyManagementProvider, ProviderName, re.EmptyLink, nil, fmt.Sprintf("name is not set for the %d th key", i+1), re.HideStackTrace) } } diff --git a/pkg/keymanagementprovider/azurekeyvault/provider_test.go b/pkg/keymanagementprovider/azurekeyvault/provider_test.go index f850bf0b6..676a43892 100644 --- a/pkg/keymanagementprovider/azurekeyvault/provider_test.go +++ b/pkg/keymanagementprovider/azurekeyvault/provider_test.go @@ -19,15 +19,16 @@ package azurekeyvault // Source: https://github.com/Azure/secrets-store-csi-driver-provider-azure/tree/release-1.4/pkg/provider import ( "context" - "reflect" + "crypto" "strings" "testing" "time" kv "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" "github.com/Azure/go-autorest/autorest/azure" - "github.com/deislabs/ratify/pkg/keymanagementprovider/azurekeyvault/types" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/internal/version" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault/types" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" "github.com/stretchr/testify/assert" ) @@ -53,47 +54,6 @@ func TestParseAzureEnvironment(t *testing.T) { } } -// TestFormatKeyVaultCertificate tests the formatKeyVaultCertificate function -func TestFormatKeyVaultCertificate(t *testing.T) { - cases := []struct { - desc string - keyVaultObject types.KeyVaultCertificate - expectedKeyVaultObject types.KeyVaultCertificate - }{ - { - desc: "leading and trailing whitespace trimmed from all fields", - keyVaultObject: types.KeyVaultCertificate{ - Name: "cert1 ", - Version: "", - }, - expectedKeyVaultObject: types.KeyVaultCertificate{ - Name: "cert1", - Version: "", - }, - }, - { - desc: "no data loss for already sanitized object", - keyVaultObject: types.KeyVaultCertificate{ - Name: "cert1", - Version: "version1", - }, - expectedKeyVaultObject: types.KeyVaultCertificate{ - Name: "cert1", - Version: "version1", - }, - }, - } - - for i, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - formatKeyVaultCertificate(&cases[i].keyVaultObject) - if !reflect.DeepEqual(cases[i].keyVaultObject, tc.expectedKeyVaultObject) { - t.Fatalf("expected: %+v, but got: %+v", tc.expectedKeyVaultObject, cases[i].keyVaultObject) - } - }) - } -} - func SkipTestInitializeKVClient(t *testing.T) { testEnvs := []azure.Environment{ azure.PublicCloud, @@ -103,11 +63,11 @@ func SkipTestInitializeKVClient(t *testing.T) { } for i := range testEnvs { - kvBaseClient, err := initializeKvClient(context.TODO(), testEnvs[i].KeyVaultEndpoint, "", "") + kvBaseClient, err := initializeKvClient(context.TODO(), testEnvs[i].KeyVaultEndpoint, "", "", version.UserAgent) assert.NoError(t, err) assert.NotNil(t, kvBaseClient) assert.NotNil(t, kvBaseClient.Authorizer) - assert.Contains(t, kvBaseClient.UserAgent, "ratify") + assert.Contains(t, kvBaseClient.UserAgent, version.UserAgent) } } @@ -164,7 +124,7 @@ func TestCreate(t *testing.T) { expectErr: true, }, { - name: "certificates array not set", + name: "certificates & keys array not set", config: config.KeyManagementProviderConfig{ "vaultUri": "https://testkv.vault.azure.net/", "tenantID": "tid", @@ -197,9 +157,26 @@ func TestCreate(t *testing.T) { }, expectErr: true, }, + { + name: "invalid key name", + config: config.KeyManagementProviderConfig{ + "vaultUri": "https://testkv.vault.azure.net/", + "tenantID": "tid", + "clientID": "clientid", + "keys": []map[string]interface{}{ + { + "name": "", + }, + }, + }, + expectErr: true, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + initKVClient = func(_ context.Context, _, _, _, _ string) (*kv.BaseClient, error) { + return &kv.BaseClient{}, nil + } _, err := factory.Create("v1", tc.config, "") if tc.expectErr != (err != nil) { t.Fatalf("error = %v, expectErr = %v", err, tc.expectErr) @@ -234,8 +211,56 @@ func TestGetCertificates(t *testing.T) { assert.Nil(t, certStatus) } -// TestGetCertStatusMap tests the getCertStatusMap function -func TestGetCertStatusMap(t *testing.T) { +// TestGetKeys tests the GetKeys function +func TestGetKeys(t *testing.T) { + factory := &akvKMProviderFactory{} + config := config.KeyManagementProviderConfig{ + "vaultUri": "https://testkv.vault.azure.net/", + "tenantID": "tid", + "clientID": "clientid", + "keys": []map[string]interface{}{ + { + "name": "key1", + }, + }, + } + + initKVClient = func(_ context.Context, _, _, _, _ string) (*kv.BaseClient, error) { + return &kv.BaseClient{}, nil + } + provider, err := factory.Create("v1", config, "") + if err != nil { + t.Fatalf("expected no err but got error = %v", err) + } + + keys, keyStatus, err := provider.GetKeys(context.Background()) + assert.NotNil(t, err) + assert.Nil(t, keys) + assert.Nil(t, keyStatus) +} + +func TestIsRefreshable(t *testing.T) { + factory := &akvKMProviderFactory{} + config := config.KeyManagementProviderConfig{ + "vaultUri": "https://testkv.vault.azure.net/", + "tenantID": "tid", + "clientID": "clientid", + "certificates": []map[string]interface{}{ + { + "name": "cert1", + "version": "", + }, + }, + } + + provider, _ := factory.Create("v1", config, "") + if provider.IsRefreshable() != true { + t.Fatalf("expected true, got false") + } +} + +// TestGetStatusMap tests the getStatusMap function +func TestGetStatusMap(t *testing.T) { certsStatus := []map[string]string{} certsStatus = append(certsStatus, map[string]string{ "CertName": "Cert1", @@ -246,7 +271,7 @@ func TestGetCertStatusMap(t *testing.T) { "CertVersion": "VersionEDF", }) - actual := getCertStatusMap(certsStatus) + actual := getStatusMap(certsStatus, types.CertificatesStatus) assert.NotNil(t, actual[types.CertificatesStatus]) } @@ -258,16 +283,16 @@ func TestGetObjectVersion(t *testing.T) { assert.Equal(t, expectedVersion, actual) } -// TestGetCertStatus tests the getCertStatusProperty function -func TestGetCertStatusProperty(t *testing.T) { +// TestGetStatus tests the getStatusProperty function +func TestGetStatusProperty(t *testing.T) { timeNow := time.Now().String() certName := "certName" certVersion := "versionABC" - status := getCertStatusProperty(certName, certVersion, timeNow) - assert.Equal(t, certName, status[types.CertificateName]) - assert.Equal(t, timeNow, status[types.CertificateLastRefreshed]) - assert.Equal(t, certVersion, status[types.CertificateVersion]) + status := getStatusProperty(certName, certVersion, timeNow) + assert.Equal(t, certName, status[types.StatusName]) + assert.Equal(t, timeNow, status[types.StatusLastRefreshed]) + assert.Equal(t, certVersion, status[types.StatusVersion]) } // TestGetCertsFromSecretBundle tests the getCertsFromSecretBundle function @@ -335,3 +360,185 @@ func TestGetCertsFromSecretBundle(t *testing.T) { }) } } + +func TestGetKeyFromKeyBundle(t *testing.T) { + cases := []struct { + desc string + keyBundle kv.KeyBundle + expectedErr bool + output crypto.PublicKey + }{ + { + desc: "no key in key bundle", + keyBundle: kv.KeyBundle{ + Key: nil, + }, + expectedErr: true, + output: nil, + }, + { + desc: "invalid key in key bundle", + keyBundle: kv.KeyBundle{ + Key: &kv.JSONWebKey{}, + }, + expectedErr: true, + output: nil, + }, + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + key, err := getKeyFromKeyBundle(tc.keyBundle) + if tc.expectedErr { + assert.NotNil(t, err) + assert.Nil(t, key) + } else { + assert.Nil(t, err) + assert.NotNil(t, key) + } + if tc.output != nil { + assert.Equal(t, tc.output, key) + } + }) + } +} + +func TestValidate(t *testing.T) { + vaultURI := "https://test.vault.azure.net" + tenantID := "testTenantID" + clientID := "testClientID" + validTestCerts := []types.KeyVaultValue{ + { + Name: "testCert", + Version: "testVersion", + }, + } + validTestKeys := []types.KeyVaultValue{ + { + Name: "testKey", + Version: "testVersion", + }, + } + + cases := []struct { + desc string + provider akvKMProvider + expectedErr bool + }{ + { + desc: "Valid Provider", + expectedErr: false, + provider: akvKMProvider{ + vaultURI: vaultURI, + tenantID: tenantID, + clientID: clientID, + certificates: validTestCerts, + keys: validTestKeys, + }, + }, + { + desc: "Missing Vault URI", + expectedErr: true, + provider: akvKMProvider{ + tenantID: tenantID, + clientID: clientID, + certificates: validTestCerts, + keys: validTestKeys, + }, + }, + { + desc: "Missing Tenant ID", + expectedErr: true, + provider: akvKMProvider{ + vaultURI: vaultURI, + clientID: clientID, + certificates: validTestCerts, + keys: validTestKeys, + }, + }, + { + desc: "Missing Client ID", + expectedErr: true, + provider: akvKMProvider{ + vaultURI: vaultURI, + tenantID: tenantID, + certificates: validTestCerts, + keys: validTestKeys, + }, + }, + { + desc: "Missing Certificate Name", + expectedErr: true, + provider: akvKMProvider{ + vaultURI: vaultURI, + tenantID: tenantID, + clientID: clientID, + keys: validTestKeys, + certificates: []types.KeyVaultValue{ + { + Version: "testVersion", + }, + }, + }, + }, + { + desc: "Missing Key Name", + expectedErr: true, + provider: akvKMProvider{ + vaultURI: vaultURI, + tenantID: tenantID, + clientID: clientID, + certificates: validTestCerts, + keys: []types.KeyVaultValue{ + { + Version: "testVersion", + }, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + err := tc.provider.validate() + if tc.expectedErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + +func TestInitializeKvClient(t *testing.T) { + tests := []struct { + name string + kvEndpoint string + userAgent string + tenantID string + clientID string + expectedErr bool + }{ + { + name: "Empty user agent", + kvEndpoint: "https://test.vault.azure.net", + userAgent: "", + expectedErr: true, + }, + { + name: "Auth failure", + kvEndpoint: "https://test.vault.azure.net", + userAgent: version.UserAgent, + tenantID: "testTenantID", + clientID: "testClientID", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := initializeKvClient(context.Background(), tt.kvEndpoint, tt.tenantID, tt.clientID, tt.userAgent) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error: %v, got: %v", tt.expectedErr, err) + } + }) + } +} diff --git a/pkg/keymanagementprovider/azurekeyvault/types/types.go b/pkg/keymanagementprovider/azurekeyvault/types/types.go index f6caa11ba..5cde59583 100644 --- a/pkg/keymanagementprovider/azurekeyvault/types/types.go +++ b/pkg/keymanagementprovider/azurekeyvault/types/types.go @@ -19,18 +19,20 @@ const ( CertificateType = "CERTIFICATE" // key of the certificate status property CertificatesStatus = "Certificates" + // key of the key status property + KeysStatus = "Keys" // Static string for certificate name for the certificate status property - CertificateName = "Name" + StatusName = "Name" // Certificate version string for the certificate status property - CertificateVersion = "Version" + StatusVersion = "Version" // Last refreshed string for the certificate status property - CertificateLastRefreshed = "LastRefreshed" + StatusLastRefreshed = "LastRefreshed" ) -// KeyVaultCertificate holds keyvault certificate related config -type KeyVaultCertificate struct { - // the name of the Azure Key Vault certificate +// KeyVaultValue holds keyvault certificate/key related config +type KeyVaultValue struct { + // the name of the Azure Key Vault certificate/key Name string `json:"name" yaml:"name"` - // the version of the Azure Key Vault certificate + // the version of the Azure Key Vault certificate/key Version string `json:"version" yaml:"version"` } diff --git a/pkg/keymanagementprovider/factory/factory.go b/pkg/keymanagementprovider/factory/factory.go index 150093b54..401ebb8ce 100644 --- a/pkg/keymanagementprovider/factory/factory.go +++ b/pkg/keymanagementprovider/factory/factory.go @@ -18,9 +18,9 @@ package factory import ( "fmt" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/types" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/types" ) // map of key management provider names to key management provider factories diff --git a/pkg/keymanagementprovider/factory/factory_test.go b/pkg/keymanagementprovider/factory/factory_test.go index 9a89904ad..8f28bee0d 100644 --- a/pkg/keymanagementprovider/factory/factory_test.go +++ b/pkg/keymanagementprovider/factory/factory_test.go @@ -18,9 +18,9 @@ package factory import ( "testing" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/mocks" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" ) type TestKeyManagementProviderFactory struct{} diff --git a/pkg/keymanagementprovider/inline/provider.go b/pkg/keymanagementprovider/inline/provider.go index 4a480d603..81740a750 100644 --- a/pkg/keymanagementprovider/inline/provider.go +++ b/pkg/keymanagementprovider/inline/provider.go @@ -17,13 +17,15 @@ package inline import ( "context" + "crypto" "crypto/x509" "encoding/json" + "fmt" - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" - "github.com/deislabs/ratify/pkg/keymanagementprovider/factory" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/factory" ) const ( @@ -32,6 +34,7 @@ const ( providerName string = "inline" certificateContentType string = "certificate" certificatesMapKey string = "certs" + keyContentType string = "key" ) //nolint:revive @@ -43,6 +46,7 @@ type InlineKMProviderConfig struct { type inlineKMProvider struct { certs map[keymanagementprovider.KMPMapKey][]*x509.Certificate + keys map[keymanagementprovider.KMPMapKey]crypto.PublicKey contentType string } type inlineKMProviderFactory struct{} @@ -70,26 +74,47 @@ func (f *inlineKMProviderFactory) Create(_ string, keyManagementProviderConfig c return nil, errors.ErrorCodeConfigInvalid.WithComponentType(errors.KeyManagementProvider).WithDetail("contentType parameter is not set") } - // only support certificate content type for now - if conf.ContentType != certificateContentType { - return nil, errors.ErrorCodeConfigInvalid.WithComponentType(errors.KeyManagementProvider).WithDetail("contentType parameter is not set to 'certificate'") - } - if conf.Value == "" { return nil, errors.ErrorCodeConfigInvalid.WithComponentType(errors.KeyManagementProvider).WithDetail("value parameter is not set") } - certs, err := keymanagementprovider.DecodeCertificates([]byte(conf.Value)) - if err != nil { - return nil, errors.ErrorCodeCertInvalid.WithComponentType(errors.KeyManagementProvider) + var certMap map[keymanagementprovider.KMPMapKey][]*x509.Certificate + var keyMap map[keymanagementprovider.KMPMapKey]crypto.PublicKey + + switch conf.ContentType { + case certificateContentType: + certs, err := keymanagementprovider.DecodeCertificates([]byte(conf.Value)) + if err != nil { + return nil, err + } + certMap = map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ + {}: certs, + } + case keyContentType: + key, err := keymanagementprovider.DecodeKey([]byte(conf.Value)) + if err != nil { + return nil, err + } + keyMap = map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ + {}: key, + } + default: + return nil, errors.ErrorCodeConfigInvalid.WithComponentType(errors.KeyManagementProvider).WithDetail(fmt.Sprintf("content type %s is not supported", conf.ContentType)) } - certMap := map[keymanagementprovider.KMPMapKey][]*x509.Certificate{ - {}: certs, - } - return &inlineKMProvider{certs: certMap, contentType: conf.ContentType}, nil + + return &inlineKMProvider{certs: certMap, keys: keyMap, contentType: conf.ContentType}, nil } // GetCertificates returns previously fetched certificates func (s *inlineKMProvider) GetCertificates(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { return s.certs, nil, nil } + +// GetKeys returns previously fetched keys +func (s *inlineKMProvider) GetKeys(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + return s.keys, nil, nil +} + +func (s *inlineKMProvider) IsRefreshable() bool { + return false +} diff --git a/pkg/keymanagementprovider/inline/provider_test.go b/pkg/keymanagementprovider/inline/provider_test.go index 9b7f93fcf..7cd56f67c 100644 --- a/pkg/keymanagementprovider/inline/provider_test.go +++ b/pkg/keymanagementprovider/inline/provider_test.go @@ -19,8 +19,8 @@ import ( "context" "testing" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/keymanagementprovider/config" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" "github.com/stretchr/testify/assert" ) @@ -116,3 +116,40 @@ func TestGetCertificates(t *testing.T) { }) } } + +func TestGetKeys(t *testing.T) { + factory := &inlineKMProviderFactory{} + config := config.KeyManagementProviderConfig{ + "type": "inline", + "contentType": "key", + "value": `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEozC27QupU+1GvAL0tqR7bT3Vpyyf +OSeWVmPjy6J5x8+6OIpmTs8PKQB1vTF0gErwa1gS/QaOElLaxDKy0GS9Jg== +-----END PUBLIC KEY-----`, + } + + provider, err := factory.Create("v1.0", config, "") + if err != nil { + t.Fatalf("failed to create provider: %v", err) + } + keys, _, _ := provider.GetKeys(context.TODO()) + if len(keys) != 1 { + t.Fatalf("expected 1 key, got %d", len(keys)) + } +} + +func TestIsRefreshable(t *testing.T) { + config := config.KeyManagementProviderConfig{ + "type": "inline", + "contentType": "certificate", + "value": "-----BEGIN CERTIFICATE-----\nMIIEeDCCAmCgAwIBAgIUbztxbi/gSPMZGN53oZQZW1h/lbAwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25k\nMRMwEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMM\nDmNhLmV4YW1wbGUuY29tMB4XDTIzMDIwMTIyNTMxMFoXDTI0MDIwMTIyNTMxMFow\nbTEZMBcGA1UEAxMQbGVhZi5leGFtcGxlLmNvbTEPMA0GA1UECxMGTXkgT3JnMRMw\nEQYDVQQKEwpNeSBDb21wYW55MRAwDgYDVQQHEwdSZWRtb25kMQswCQYDVQQIEwJX\nQTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs\nMixf+WT34edYXs2c80zOg4Z/cxOVHU05gywjuISeaP+KS+Joc3emgbub1t5dPclk\nieIwrj3Olk3tvkrPiarOJIcrNR2zfBmQAufR4AUjoc4n1GQSp/voGgw1Hvh0wTkO\nYjhzLomrF242Ond8WTVO3Vq6/tfApfZMFM59eK9LMBkuvwTV4NeLnEnPvpLAoAvV\n9ZvCu7FuQ849R93Aoag2bZc3Tc3UCbahoJs9rTE/rnAqOhJWMGv2J1Y2Wu2eIvkD\n2uCmcVlY+7owG3TwLHTuIOBFl/5MXMvfR+B7yp1OkG23rTwwuSEBlMhYRzJFvssv\n8FX0sea7zhIg5dtoRjIlAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG\n9w0BAQsFAAOCAgEANRUu+9aBAuRf3OsdUWJflMAvuyzREp3skWSOUs4dw0MhcB6x\nb7BSyNdrBgPImLBpqYzNU6IT2eIlLXrYnKLehvPyZQx7LHvIeompA09aKMFFesqi\ngVoW5GRtp3qL3oNuuZJ80r/uKlB6Cj51TWqUbLcctBGHX7TWxFeWmFRnN0Bki00U\nJW/ElaTsr4GB+ltgZM+5USUqSNQqTa8t3d+vH6oVikyV1oYunM41xAfiRZtID04z\no15sLSkWTjavfmZ3+NjllipXFY2tnLqymCcObgdKtJHmTMFSDRngDjY+3+RVj4EY\npNaCCCepvtmXz1C5f06tlgY4ofaautJuAL7K93p/Q9ZcsIhmYWkCUZ0dkWq+eMdT\n9/lB9rQHbrDTaRxEQNIUezFMQEBxR9eC5JQfpw98LobAgA3r4vizQjQPsN0UZ54h\ncAHiyoo1VeckkotXaToRsoLjixPO9Fmss4H3urJTLpcU0drbVoG3emNh4K289vgR\nrjV11TenqvvR3+jJ2AX2ewSsF25m0afheZbrq2ZtyITPAbOqwMwTbTOvJT3HUztt\nhUP3qwsKNPR/hF3FSqZewiYOSqJi5Dk28Vd6mUEQzZa/Ma9RpBR+BAmfgH3Z9gX5\n0TqmAVQn1P8yh+bhEjiNa20bTJ+y5vQ9OrA7fiQ+6vpZCio4NFiEbYK4UBI=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFxzCCA6+gAwIBAgIUY1fnGcYJFIGNk8fevKdGtOuZ5vcwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25k\nMRMwEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMM\nDmNhLmV4YW1wbGUuY29tMB4XDTIzMDIwMTIyNTIwN1oXDTMzMDEyOTIyNTIwN1ow\nazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25kMRMw\nEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMMDmNh\nLmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtQXd\n+qySVVMHx7iGz9xRdpDKb+zATK3asMFnMXWBn0MCkcOMJvhjajA8yrCpfMudjiW0\nReQ6xcjEfnJqkzwxrK7tPE9cQ6JzQGCxsmscRzKf1NoJL2ske5xBteKuJhSfZ9la\ncIZn/EU2F6eMAl9U4Y+ncIIrl4UoB5H/AJJj62WMl5QAvzXXwCBwlLHQe4/T3Axu\n9xmD3HTC7iQExOUFLdJx86fK3ym0futi1RgOUgD+OrnyDEIkD8mGxffPYPgszS71\nUuJX+NTsLZ/JW3ER/PMAPnBsMsMTTxEIGnrp1CXf2RnwQnDHVsxZFMgkLTS6dksT\nTGevnulTNtVvSKsZ7MpzE01j7zDie4V4dQJzBJMktbeVq9KRoPIEX0WcKpg8bGS8\nd5p5pr+Lu/NOv6ur+av8M649qCPwJAv5i2P6ggT4YMNtY0wMD2kjcHJ9/l2gYpZj\n3DG0Hy3Xo8uKUmTSC7iGhLsSjleNhJkKyh3RCsuMKB9juE4qeXPoaoPWBIuarbkq\neVVZJu2PlgN8UcxM/ym+9GNJIfNJ18WGWm+5P3IDvfBbJ39yvzZlG2czju4BUzYn\nlPOHA/Z18TxZhlPrPVnyKSVbeg7sW17yMUI2LCeaFIOYdIFvM09RaCyLIGrQwhpe\nkLh56xXk702oNHaLxyh/v5kz8EyxnpXIlDntis0CAwEAAaNjMGEwHQYDVR0OBBYE\nFCESfoahHMx7GyBdTBARen7mc37nMB8GA1UdIwQYMBaAFCESfoahHMx7GyBdTBAR\nen7mc37nMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\nDQEBCwUAA4ICAQBst1MbRJd3FLSY36qaNuDhGncvcXIcYqP2A/SXVhjuAhVOTsrE\nejDYBSkjfxCgoA3LZnQLTcbcPwRL5dTBEvqBqzbyArOjc+G3kbjOeuZYv09M8kUU\nYG1bVnxXV17GMk7zcBUnr1iwnp4E0pzB9gTv+Jv1oV+EtAHe5QOTOmW9mm2SUXbH\nmIya+KlzkIgVJJ8kiGOyXsr8i4wpyDXZf720tqTzPQFTf6rUXo9PhYzOWrrj8c7V\n+bmJurV3XkgvdOiwNase17wXUG9Ad8FhVYpUicq3Csfx5M87IXUIlx51AxaOQK3G\n3skyJyAm8R8pHzhcsEuVV7bGZlbFPZeWAHpbIEwpKHlLoN6qMINk2kEbcVahL2Mu\nXBUcvJdO2LbmEvfS+1imr32YbJ5Ufetru+G4mwAp6a0P6u5FU8lbE1ZoFHhflsVq\nErvOcRKhKjAim4iwIVGS55Xyx1IpF7YSTYpL89vlMmaJsssEoCQAkf+lxmC3eEuy\n2kBu3QB3cUHZXJK+01krC5MqeHcEVuc/fbNcTsCBp+RYXNRjYsp6IzGjqltUfInE\n3Tywg3P0ZLPO06WLNjNeAFZw6T8yV5gLcTJ1xc5pEf4UiY1uCf4NmDpeWhT+vvto\n2AxC/+7x7EkEfZnYiD6tcyHyY+iuroptws8lc5wRis859kydnq3vtxbXPQ==\n-----END CERTIFICATE-----\n", + } + factory := &inlineKMProviderFactory{} + provider, err := factory.Create("v1.0", config, "") + if err != nil { + t.Fatalf("failed to create provider: %v", err) + } + if provider.IsRefreshable() != false { + t.Fatalf("expected false") + } +} diff --git a/pkg/keymanagementprovider/keymanagementprovider.go b/pkg/keymanagementprovider/keymanagementprovider.go index 8478480ab..584f50828 100644 --- a/pkg/keymanagementprovider/keymanagementprovider.go +++ b/pkg/keymanagementprovider/keymanagementprovider.go @@ -17,11 +17,18 @@ package keymanagementprovider import ( "context" + "crypto" "crypto/x509" "encoding/pem" + "fmt" + "strings" "sync" - "github.com/deislabs/ratify/errors" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/constants" + ctxUtils "github.com/ratify-project/ratify/internal/context" + vu "github.com/ratify-project/ratify/pkg/verifier/utils" + "github.com/sigstore/sigstore/pkg/cryptoutils" ) // This is a map of properties for fetched certificates/keys @@ -36,15 +43,49 @@ type KMPMapKey struct { Version string } +type PublicKey struct { + Key crypto.PublicKey + ProviderType string +} + // KeyManagementProvider is an interface that defines methods to be implemented by a each key management provider provider type KeyManagementProvider interface { // Returns an array of certificates and the provider specific cert attributes GetCertificates(ctx context.Context) (map[KMPMapKey][]*x509.Certificate, KeyManagementProviderStatus, error) + // Returns an array of keys and the provider specific key attributes + GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error) + // Returns if the provider supports refreshing of certificates/keys + IsRefreshable() bool } -// static concurreny-safe map to store certificates fetched from key management provider +// static concurrency-safe map to store certificates fetched from key management provider +// layout: +// +// map["/"] = map[KMPMapKey][]*x509.Certificate +// where KMPMapKey is dimensioned by the name and version of the certificate. +// Array of x509 Certificates for certificate chain scenarios var certificatesMap sync.Map +// static concurrency-safe map to store keys fetched from key management provider +// layout: +// +// map["/"] = map[KMPMapKey]PublicKey +// where KMPMapKey is dimensioned by the name and version of the public key +// where PublicKey is a struct containing the public key and the provider type +var keyMap sync.Map + +// static concurrency-safe map to store errors while fetching certificates from key management provider. +// layout: +// +// map["/"] = error +var certificateErrMap sync.Map + +// static concurrency-safe map to store errors while fetching keys from key management provider. +// layout: +// +// map["/"] = error +var keyErrMap sync.Map + // DecodeCertificates decodes PEM-encoded bytes into an x509.Certificate chain. func DecodeCertificates(value []byte) ([]*x509.Certificate, error) { var certs []*x509.Certificate @@ -67,35 +108,111 @@ func DecodeCertificates(value []byte) ([]*x509.Certificate, error) { } } + if len(certs) == 0 { + return nil, errors.ErrorCodeCertInvalid.WithComponentType(errors.CertProvider).WithDetail("no certificates found in the pem block") + } + return certs, nil } -// SetCertificatesInMap sets the certificates in the map +// DecodeKey takes in a PEM encoded byte array and returns a public key +// PEM encoded byte array is expected to be a single public key. If multiple +// are provided, the first one is returned +func DecodeKey(value []byte) (crypto.PublicKey, error) { + pk, err := cryptoutils.UnmarshalPEMToPublicKey(value) + if err != nil { + return nil, errors.ErrorCodeKeyInvalid.WithComponentType(errors.KeyManagementProvider).WithDetail("error parsing public key").WithError(err) + } + return pk, nil +} + +// setCertificatesInMap sets the certificates in the map // it is concurrency-safe -func SetCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { +func setCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certificate) { certificatesMap.Store(resource, certs) + certificateErrMap.Delete(resource) } -// GetCertificatesFromMap gets the certificates from the map and returns an empty map of certificate arrays if not found -func GetCertificatesFromMap(resource string) map[KMPMapKey][]*x509.Certificate { - certs, ok := certificatesMap.Load(resource) - if !ok { - return map[KMPMapKey][]*x509.Certificate{} +// GetCertificatesFromMap gets the certificates from the map and returns an empty map of certificate arrays if not found or an error happened. +func GetCertificatesFromMap(ctx context.Context, resource string) (map[KMPMapKey][]*x509.Certificate, error) { + if !hasAccessToProvider(ctx, resource) { + return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("Resources in namespace [%s] do not have access to key management provider [%s]", ctxUtils.GetNamespace(ctx), resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) + } + if err, ok := certificateErrMap.Load(resource); ok && err != nil { + return map[KMPMapKey][]*x509.Certificate{}, err.(error) } - return certs.(map[KMPMapKey][]*x509.Certificate) + if certs, ok := certificatesMap.Load(resource); ok { + return certs.(map[KMPMapKey][]*x509.Certificate), nil + } + return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("The key management provider [%s] does not exist", resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) } -// DeleteCertificatesFromMap deletes the certificates from the map +// DeleteResourceFromMap deletes the certificates, keys and errors from the map // it is concurrency-safe -func DeleteCertificatesFromMap(resource string) { +func DeleteResourceFromMap(resource string) { certificatesMap.Delete(resource) + keyMap.Delete(resource) + certificateErrMap.Delete(resource) + keyErrMap.Delete(resource) } // FlattenKMPMap flattens the map of certificates fetched for a single key management provider resource and returns a single array func FlattenKMPMap(certMap map[KMPMapKey][]*x509.Certificate) []*x509.Certificate { - var certs []*x509.Certificate - for _, v := range certMap { - certs = append(certs, v...) + var items []*x509.Certificate + for _, val := range certMap { + items = append(items, val...) + } + return items +} + +// setKeysInMap sets the keys in the map +func setKeysInMap(resource string, providerType string, keys map[KMPMapKey]crypto.PublicKey) { + typedMap := make(map[KMPMapKey]PublicKey) + for key, value := range keys { + typedMap[key] = PublicKey{Key: value, ProviderType: providerType} + } + keyMap.Store(resource, typedMap) + keyErrMap.Delete(resource) +} + +// SaveSecrets saves the keys and certificates in the map. +func SaveSecrets(resource, providerType string, keys map[KMPMapKey]crypto.PublicKey, certs map[KMPMapKey][]*x509.Certificate) { + setKeysInMap(resource, providerType, keys) + setCertificatesInMap(resource, certs) +} + +// GetKeysFromMap gets the keys from the map and returns an empty map if not found or an error happened. +func GetKeysFromMap(ctx context.Context, resource string) (map[KMPMapKey]PublicKey, error) { + // A cluster-wide operation can access cluster-wide provider + // A namespaced operation can only fetch the provider in the same namespace or cluster-wide provider. + if !hasAccessToProvider(ctx, resource) { + return map[KMPMapKey]PublicKey{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("The resources in namespace [%s] do not have access to key management provider [%s]", ctxUtils.GetNamespace(ctx), resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider [%s] is created in the namespace [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) + } + if err, ok := keyErrMap.Load(resource); ok && err != nil { + return map[KMPMapKey]PublicKey{}, err.(error) + } + if keys, ok := keyMap.Load(resource); ok { + return keys.(map[KMPMapKey]PublicKey), nil + } + return map[KMPMapKey]PublicKey{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("The key management provider [%s] does not exist", resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) +} + +// SetCertificateError sets the error while fetching certificates from key management provider. +func SetCertificateError(resource string, err error) { + certificateErrMap.Store(resource, err) +} + +// SetKeyError sets the error while fetching keys from key management provider. +func SetKeyError(resource string, err error) { + keyErrMap.Store(resource, err) +} + +// A namespaced verification request could access KMP in the same namespace or cluster-wide KMP. +// A cluster-wide (context namespace is "") verification request could only access cluster-wide KMP. +func hasAccessToProvider(ctx context.Context, provider string) bool { + namespace := ctxUtils.GetNamespace(ctx) + if namespace == constants.EmptyNamespace { + return !vu.IsNamespacedNamed(provider) } - return certs + return strings.HasPrefix(provider, namespace+constants.NamespaceSeperator) || !vu.IsNamespacedNamed(provider) } diff --git a/pkg/keymanagementprovider/keymanagementprovider_test.go b/pkg/keymanagementprovider/keymanagementprovider_test.go index 97b300772..044156220 100644 --- a/pkg/keymanagementprovider/keymanagementprovider_test.go +++ b/pkg/keymanagementprovider/keymanagementprovider_test.go @@ -16,11 +16,15 @@ limitations under the License. package keymanagementprovider import ( + "context" + "crypto" + "crypto/rsa" "crypto/x509" "errors" "testing" - ratifyerrors "github.com/deislabs/ratify/errors" + ratifyerrors "github.com/ratify-project/ratify/errors" + ctxUtils "github.com/ratify-project/ratify/internal/context" "github.com/stretchr/testify/assert" ) @@ -34,13 +38,18 @@ func TestDecodeCertificates(t *testing.T) { }{ { desc: "empty string", - expectedErr: false, + expectedErr: true, }, { desc: "invalid certificate", pemString: "-----BEGIN CERTIFICATE-----\nbaddata\n-----END CERTIFICATE-----\n", expectedErr: true, }, + { + desc: "invalid certificate", + pemString: "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAweAc4xikYT4ZszXVdF5mrgP0zKVYi4Ces0py9dw8XZfh/Hlxb5xWMs4DzTcKwmLatgKNSrvNyOaxkBD90PvcYNaTCwzwQ09kZ5dYtVOV4sdzeyOj8UDtf4MF5eJgJj/wWCQJnWrX/4n6nSdNTXSJEFAZkDv0BKVkZekJHn3fh+pOuv8UtvOrY1NjNK/TLWxB+8xpwugeB9oZ+VgV/gHZBLprxYkmUDsfngYy3+r6RZ+hInalZc5uAbtRUoB8+nVhXXOe3iVcVWFoWPMJ2fuPHz/8cDjv02MNWa/MeAt+ItW3N+VFZNkwbu5en3FepsxzRl04rhZzr1DSX6V6CVX43wIDAQAB-----END PUBLIC KEY-----", + expectedErr: true, + }, { desc: "single certificate", pemString: "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n", @@ -130,7 +139,7 @@ func TestDecodeCertificates_FailedX509ParseError(t *testing.T) { // TestSetCertificatesInMap checks if certificates are set in the map func TestSetCertificatesInMap(t *testing.T) { certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + setCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) if _, ok := certificatesMap.Load("test"); !ok { t.Fatalf("certificatesMap should have been set for key") } @@ -138,9 +147,9 @@ func TestSetCertificatesInMap(t *testing.T) { // TestGetCertificatesFromMap checks if certificates are fetched from the map func TestGetCertificatesFromMap(t *testing.T) { - certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - certs := GetCertificatesFromMap("test") + DeleteResourceFromMap("test") + setCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + certs, _ := GetCertificatesFromMap(context.Background(), "test") if len(certs) != 1 { t.Fatalf("certificates should have been fetched from the map") } @@ -148,18 +157,28 @@ func TestGetCertificatesFromMap(t *testing.T) { // TestGetCertificatesFromMap_FailedToFetch checks if certificates are fetched from the map func TestGetCertificatesFromMap_FailedToFetch(t *testing.T) { - certificatesMap.Delete("test") - certs := GetCertificatesFromMap("test") + DeleteResourceFromMap("test") + certs, _ := GetCertificatesFromMap(context.Background(), "test") if len(certs) != 0 { t.Fatalf("certificates should not have been fetched from the map") } } +// TestGetCertificatesFromMap_ErrorFromReconcile checks if error is returned from reconcile +func TestGetCertificatesFromMap_ErrorFromReconcile(t *testing.T) { + DeleteResourceFromMap("test") + SetCertificateError("test", errors.New("test error")) + if _, err := GetCertificatesFromMap(context.Background(), "test"); err == nil { + t.Fatalf("expected error, but got nil") + } + DeleteResourceFromMap("test") +} + // TestDeleteCertificatesFromMap checks if certificates are deleted from the map func TestDeleteCertificatesFromMap(t *testing.T) { certificatesMap.Delete("test") - SetCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) - DeleteCertificatesFromMap("test") + setCertificatesInMap("test", map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + DeleteResourceFromMap("test") if _, ok := certificatesMap.Load("test"); ok { t.Fatalf("certificatesMap should have been deleted for key") } @@ -172,3 +191,101 @@ func TestFlattenKMPMap(t *testing.T) { t.Fatalf("certificates should have been flattened") } } + +// TestSetKeysInMap checks if keys are set in the map +func TestSetKeysInMap(t *testing.T) { + keyMap.Delete("test") + setKeysInMap("test", "", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + if _, ok := keyMap.Load("test"); !ok { + t.Fatalf("keysMap should have been set for key") + } +} + +// TestSaveSecrets checks if secrets are saved in the map. +func TestSaveSecrets(t *testing.T) { + DeleteResourceFromMap("test") + + SaveSecrets("test", "", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}, map[KMPMapKey][]*x509.Certificate{{}: {{Raw: []byte("testcert")}}}) + if _, ok := certificatesMap.Load("test"); !ok { + t.Fatalf("certificatesMap should have been set for key") + } + if _, ok := certificatesMap.Load("test"); !ok { + t.Fatalf("certificatesMap should have been set for key") + } + + DeleteResourceFromMap("test") +} + +// TestGetKeysFromMap checks if keys are fetched from the map +func TestGetKeysFromMap(t *testing.T) { + DeleteResourceFromMap("test") + setKeysInMap("test", "", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + keys, _ := GetKeysFromMap(context.Background(), "test") + if len(keys) != 1 { + t.Fatalf("keys should have been fetched from the map") + } + + SetKeyError("test", errors.New("test error")) + if _, err := GetKeysFromMap(context.Background(), "test"); err == nil { + t.Fatalf("expected error, but got nil") + } + DeleteResourceFromMap("test") +} + +// TestGetKeysFromMap_FailedToFetch checks if keys fail to fetch from map +func TestGetKeysFromMap_FailedToFetch(t *testing.T) { + keyMap.Delete("test") + keys, _ := GetKeysFromMap(context.Background(), "test") + if len(keys) != 0 { + t.Fatalf("keys should not have been fetched from the map") + } +} + +func TestGetKeysFromMap_AccessDifferentNamespace_ReturnsFalse(t *testing.T) { + keyMap.Delete("test") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), "namespace1") + keys, _ := GetKeysFromMap(ctx, "namespace2/test") + if len(keys) != 0 { + t.Fatalf("keys should not have been fetched from the map") + } +} + +// TestDeleteKeysFromMap checks if key map entry is deleted from the map +func TestDeleteKeysFromMap(t *testing.T) { + keyMap.Delete("test") + setKeysInMap("test", "", map[KMPMapKey]crypto.PublicKey{{}: &rsa.PublicKey{}}) + DeleteResourceFromMap("test") + if _, ok := keyMap.Load("test"); ok { + t.Fatalf("keysMap should have been deleted for key") + } +} + +// TestDecodeKey checks if key is decoded from pem +func TestDecodeKey(t *testing.T) { + validKey := `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEozC27QupU+1GvAL0tqR7bT3Vpyyf +OSeWVmPjy6J5x8+6OIpmTs8PKQB1vTF0gErwa1gS/QaOElLaxDKy0GS9Jg== +-----END PUBLIC KEY-----` + cases := []struct { + desc string + pemString string + expectedErr bool + }{ + { + desc: "valid public key", + pemString: validKey, + expectedErr: false, + }, + { + desc: "invalid public key", + pemString: "foo", + expectedErr: true, + }, + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + _, err := DecodeKey([]byte(tc.pemString)) + assert.Equal(t, tc.expectedErr, err != nil) + }) + } +} diff --git a/pkg/keymanagementprovider/mocks/client.go b/pkg/keymanagementprovider/mocks/client.go new file mode 100644 index 000000000..4d651ce92 --- /dev/null +++ b/pkg/keymanagementprovider/mocks/client.go @@ -0,0 +1,67 @@ +/* +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 mocks + +import ( + "context" + "errors" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type TestClient struct { + client.Client + GetFunc func(ctx context.Context, key types.NamespacedName, obj client.Object) error + ListFunc func(ctx context.Context, list client.ObjectList) error +} + +func (m TestClient) Get(_ context.Context, key types.NamespacedName, obj client.Object, _ ...client.GetOption) error { + if m.GetFunc != nil { + return m.GetFunc(context.Background(), key, obj) + } + return nil +} + +func (m TestClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error { + if m.ListFunc != nil { + return m.ListFunc(context.Background(), list) + } + return nil +} + +type mockSubResourceWriter struct { + updateFailed bool +} + +func (m *TestClient) Status() client.StatusWriter { + return &mockSubResourceWriter{updateFailed: false} +} + +func (m *mockSubResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (m *mockSubResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +func (m *mockSubResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if m.updateFailed { + return errors.New("update failed") + } + return nil +} diff --git a/pkg/keymanagementprovider/mocks/factory.go b/pkg/keymanagementprovider/mocks/factory.go new file mode 100644 index 000000000..15a0e0d7d --- /dev/null +++ b/pkg/keymanagementprovider/mocks/factory.go @@ -0,0 +1,43 @@ +/* +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 mocks + +import ( + "context" + "crypto" + "crypto/x509" + + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" +) + +type TestKeyManagementProviderFactory struct { + GetCertsFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool +} + +func (f *TestKeyManagementProviderFactory) Create(_ string, _ config.KeyManagementProviderConfig, _ string) (keymanagementprovider.KeyManagementProvider, error) { + var certMap map[keymanagementprovider.KMPMapKey][]*x509.Certificate + var keyMap map[keymanagementprovider.KMPMapKey]crypto.PublicKey + return &TestKeyManagementProvider{ + certificates: certMap, + keys: keyMap, + GetCertificatesFunc: f.GetCertsFunc, + GetKeysFunc: f.GetKeysFunc, + IsRefreshableFunc: f.IsRefreshableFunc, + }, nil +} diff --git a/pkg/keymanagementprovider/mocks/types.go b/pkg/keymanagementprovider/mocks/types.go index 6bbf2dc80..5cfa21645 100644 --- a/pkg/keymanagementprovider/mocks/types.go +++ b/pkg/keymanagementprovider/mocks/types.go @@ -17,17 +17,39 @@ package mocks import ( "context" + "crypto" "crypto/x509" - "github.com/deislabs/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" ) type TestKeyManagementProvider struct { - certificates map[keymanagementprovider.KMPMapKey][]*x509.Certificate - status keymanagementprovider.KeyManagementProviderStatus - err error + certificates map[keymanagementprovider.KMPMapKey][]*x509.Certificate + keys map[keymanagementprovider.KMPMapKey]crypto.PublicKey + status keymanagementprovider.KeyManagementProviderStatus + err error + GetCertificatesFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(ctx context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool } func (c *TestKeyManagementProvider) GetCertificates(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { + if c.GetCertificatesFunc != nil { + return c.GetCertificatesFunc(context.Background()) + } return c.certificates, c.status, c.err } + +func (c *TestKeyManagementProvider) GetKeys(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + if c.GetKeysFunc != nil { + return c.GetKeysFunc(context.Background()) + } + return c.keys, c.status, c.err +} + +func (c *TestKeyManagementProvider) IsRefreshable() bool { + if c.IsRefreshableFunc != nil { + return c.IsRefreshableFunc() + } + return false +} diff --git a/pkg/keymanagementprovider/refresh/factory.go b/pkg/keymanagementprovider/refresh/factory.go new file mode 100644 index 000000000..eed41e119 --- /dev/null +++ b/pkg/keymanagementprovider/refresh/factory.go @@ -0,0 +1,47 @@ +/* +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 refresh + +import "fmt" + +var refresherFactories = make(map[string]RefresherFactory) + +type RefresherFactory interface { + // Create creates a new instance of the refresher using the provided configuration + // Create(config map[string]interface{}) (Refresher, error) + Create(config RefresherConfig) (Refresher, error) +} + +// Refresher is an interface that defines methods to be implemented by a each refresher +func Register(name string, factory RefresherFactory) { + if factory == nil { + panic("refresher factory cannot be nil") + } + _, registered := refresherFactories[name] + if registered { + panic(fmt.Sprintf("refresher factory named %s already registered", name)) + } + refresherFactories[name] = factory +} + +// CreateRefresherFromConfig creates a new instance of the refresher using the provided configuration +func CreateRefresherFromConfig(refresherConfig RefresherConfig) (Refresher, error) { + factory, ok := refresherFactories[refresherConfig.RefresherType] + if !ok { + return nil, fmt.Errorf("refresher factory with name %s not found", refresherConfig.RefresherType) + } + return factory.Create(refresherConfig) +} diff --git a/pkg/keymanagementprovider/refresh/factory_test.go b/pkg/keymanagementprovider/refresh/factory_test.go new file mode 100644 index 000000000..e8762a444 --- /dev/null +++ b/pkg/keymanagementprovider/refresh/factory_test.go @@ -0,0 +1,123 @@ +/* +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 refresh + +import ( + "context" + "testing" +) + +type MockRefresher struct{} + +func (f *MockRefresher) Create(_ RefresherConfig) (Refresher, error) { + return &MockRefresher{}, nil +} + +func (f *MockRefresher) Refresh(_ context.Context) error { + return nil +} + +func (f *MockRefresher) GetResult() interface{} { + return nil +} + +func (f *MockRefresher) GetStatus() interface{} { + return nil +} + +func TestRefreshFactory_Create(t *testing.T) { + Register("mockRefresher", &MockRefresher{}) + refresherConfig := RefresherConfig{ + RefresherType: "mockRefresher", + } + factory := refresherFactories["mockRefresher"] + refresher, err := factory.Create(refresherConfig) + // refresher, err := CreateRefresherFromConfig(refresherConfig) + if _, ok := refresher.(*MockRefresher); !ok { + t.Errorf("Expected refresher to be of type MockRefresher, got %v", refresher) + } + if err != nil { + t.Errorf("Expected no error, got %v", err) + } +} + +func TestRegister_InvalidFactory(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got nil") + } + }() + + Register("invalidRefresher", nil) +} + +func TestRegister_DuplicateFactory(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic, got nil") + } + }() + + Register("duplicateRefresher", &MockRefresher{}) + Register("duplicateRefresher", &MockRefresher{}) +} + +func TestRegister_ValidFactory(t *testing.T) { + refresherFactories = make(map[string]RefresherFactory) + Register("validRefresher", &MockRefresher{}) + if len(refresherFactories) != 1 { + t.Errorf("Expected 1 factory to be registered, got %d", len(refresherFactories)) + } +} + +func TestCreateRefresherFromConfig(t *testing.T) { + Register("mockRefresher", &MockRefresher{}) + tests := []struct { + name string + refresherType string + expectedError bool + }{ + { + name: "Valid Refresher Type", + refresherType: "mockRefresher", + expectedError: false, + }, + { + name: "Invalid Refresher Type", + refresherType: "invalidRefresher", + expectedError: true, + }, + { + name: "Empty Refresher Type", + refresherType: "", + expectedError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + refresherConfig := RefresherConfig{ + RefresherType: tt.refresherType, + } + _, err := CreateRefresherFromConfig(refresherConfig) + if tt.expectedError && err == nil { + t.Errorf("Expected error, got nil") + } + if !tt.expectedError && err != nil { + t.Errorf("Expected no error, got %v", err) + } + }) + } +} diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh.go b/pkg/keymanagementprovider/refresh/kubeRefresh.go new file mode 100644 index 000000000..895cb7d8d --- /dev/null +++ b/pkg/keymanagementprovider/refresh/kubeRefresh.go @@ -0,0 +1,113 @@ +/* +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 refresh + +import ( + "context" + "fmt" + "maps" + "time" + + re "github.com/ratify-project/ratify/errors" + kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/sirupsen/logrus" + ctrl "sigs.k8s.io/controller-runtime" +) + +type KubeRefresher struct { + Provider kmp.KeyManagementProvider + ProviderType string + ProviderRefreshInterval string + Resource string + Result ctrl.Result + Status kmp.KeyManagementProviderStatus +} + +// Register registers the kubeRefresher factory +func init() { + Register(KubeRefresherType, &KubeRefresher{}) +} + +// Refresh the certificates/keys for the key management provider by calling the GetCertificates and GetKeys methods +func (kr *KubeRefresher) Refresh(ctx context.Context) error { + logger := logrus.WithContext(ctx) + + // fetch certificates and store in map + certificates, certAttributes, err := kr.Provider.GetCertificates(ctx) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch certificates from key management provider [%s] of type [%s]", kr.Resource, kr.ProviderType)) + kmp.SetCertificateError(kr.Resource, err) + return kmpErr + } + + // fetch keys and store in map + keys, keyAttributes, err := kr.Provider.GetKeys(ctx) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to fetch keys from key management provider [%s] of type [%s]", kr.Resource, kr.ProviderType)) + kmp.SetKeyError(kr.Resource, err) + return kmpErr + } + + kmp.SaveSecrets(kr.Resource, kr.ProviderType, keys, certificates) + // merge certificates and keys status into one + maps.Copy(keyAttributes, certAttributes) + kr.Status = keyAttributes + + logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), kr.Resource) + + // Resource is not refreshable, returning empty result and no error to indicate we’ve successfully reconciled this object + // will not reconcile again unless resource is recreated + if !kr.Provider.IsRefreshable() { + return nil + } + + // if interval is not set, disable refresh + if kr.ProviderRefreshInterval == "" { + return nil + } + + // resource is refreshable, requeue after interval + intervalDuration, err := time.ParseDuration(kr.ProviderRefreshInterval) + if err != nil { + kmpErr := re.ErrorCodeKeyManagementProviderFailure.WithError(err).WithDetail(fmt.Sprintf("Unable to parse interval duration for key management provider [%s] of type [%s]", kr.Resource, kr.ProviderType)) + return kmpErr + } + + logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration) + kr.Result = ctrl.Result{RequeueAfter: intervalDuration} + + return nil +} + +// GetResult returns the result of the refresh as a ctrl.Result +func (kr *KubeRefresher) GetResult() interface{} { + return kr.Result +} + +func (kr *KubeRefresher) GetStatus() interface{} { + return kr.Status +} + +// Create creates a new KubeRefresher instance +func (kr *KubeRefresher) Create(config RefresherConfig) (Refresher, error) { + return &KubeRefresher{ + Provider: config.Provider, + ProviderType: config.ProviderType, + ProviderRefreshInterval: config.ProviderRefreshInterval, + Resource: config.Resource, + }, nil +} diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh_test.go b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go new file mode 100644 index 000000000..9875098b8 --- /dev/null +++ b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go @@ -0,0 +1,237 @@ +/* +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 refresh + +import ( + "context" + "crypto" + "crypto/x509" + "errors" + "reflect" + "testing" + "time" + + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/config" + _ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline" + mock "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" + ctrl "sigs.k8s.io/controller-runtime" +) + +func TestKubeRefresher_Refresh(t *testing.T) { + tests := []struct { + name string + providerRawParameters []byte + providerType string + providerRefreshInterval string + GetCertsFunc func(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) + GetKeysFunc func(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) + IsRefreshableFunc func() bool + expectedResult ctrl.Result + expectedError bool + }{ + { + name: "Non-refreshable", + providerRawParameters: []byte(`{"contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`), + providerType: "inline", + IsRefreshableFunc: func() bool { return false }, + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "Disabled", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{}, + expectedError: false, + }, + { + name: "Refreshable", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "1m", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{RequeueAfter: time.Minute}, + expectedError: false, + }, + { + name: "Invalid Interval", + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp", + providerRefreshInterval: "1mm", + IsRefreshableFunc: func() bool { return true }, + expectedResult: ctrl.Result{}, + expectedError: true, + }, + { + name: "Error Fetching Certificates", + GetCertsFunc: func(_ context.Context) (map[keymanagementprovider.KMPMapKey][]*x509.Certificate, keymanagementprovider.KeyManagementProviderStatus, error) { + // Example behavior: Return an error + return nil, nil, errors.New("test error") + }, + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp-error", + IsRefreshableFunc: func() bool { return true }, + expectedError: true, + }, + { + name: "Error Fetching Keys", + GetKeysFunc: func(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) { + // Example behavior: Return an error + return nil, nil, errors.New("test error") + }, + providerRawParameters: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), + providerType: "test-kmp-error", + IsRefreshableFunc: func() bool { return true }, + expectedError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var factory mock.TestKeyManagementProviderFactory + + if tt.GetCertsFunc != nil { + factory = mock.TestKeyManagementProviderFactory{ + GetCertsFunc: tt.GetCertsFunc, + IsRefreshableFunc: tt.IsRefreshableFunc, + } + } else if tt.GetKeysFunc != nil { + factory = mock.TestKeyManagementProviderFactory{ + GetKeysFunc: tt.GetKeysFunc, + IsRefreshableFunc: tt.IsRefreshableFunc, + } + } else { + factory = mock.TestKeyManagementProviderFactory{ + IsRefreshableFunc: tt.IsRefreshableFunc, + } + } + + provider, _ := factory.Create("", config.KeyManagementProviderConfig{}, "") + + kr := &KubeRefresher{ + Provider: provider, + ProviderType: tt.providerType, + ProviderRefreshInterval: tt.providerRefreshInterval, + Resource: "kmpname", + } + + err := kr.Refresh(context.Background()) + result := kr.GetResult() + if !reflect.DeepEqual(result, tt.expectedResult) { + t.Fatalf("Expected nil but got %v with error %v", result, err) + } + if tt.expectedError && err == nil { + t.Fatalf("Expected error but got nil") + } + }) + } +} + +func TestKubeRefresher_GetResult(t *testing.T) { + kr := &KubeRefresher{ + Result: ctrl.Result{RequeueAfter: time.Minute}, + } + + result := kr.GetResult() + expectedResult := ctrl.Result{RequeueAfter: time.Minute} + + if !reflect.DeepEqual(result, expectedResult) { + t.Fatalf("Expected result %v, but got %v", expectedResult, result) + } +} +func TestKubeRefresher_GetStatus(t *testing.T) { + kr := &KubeRefresher{ + Status: keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", + }, + } + + status := kr.GetStatus() + expectedStatus := keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", + } + + if !reflect.DeepEqual(status, expectedStatus) { + t.Fatalf("Expected status %v, but got %v", expectedStatus, status) + } +} +func TestKubeRefresher_Create(t *testing.T) { + tests := []struct { + name string + config RefresherConfig + expectedProviderType string + expectedRefreshInterval string + expectedResource string + }{ + { + name: "Valid Config", + config: RefresherConfig{ + Provider: &mock.TestKeyManagementProvider{}, + ProviderType: "test-kmp", + ProviderRefreshInterval: "1m", + Resource: "test-resource", + }, + expectedProviderType: "test-kmp", + expectedRefreshInterval: "1m", + expectedResource: "test-resource", + }, + { + name: "Empty Config", + config: RefresherConfig{ + Provider: nil, + ProviderType: "", + ProviderRefreshInterval: "", + Resource: "", + }, + expectedProviderType: "", + expectedRefreshInterval: "", + expectedResource: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kr := &KubeRefresher{} + refresher, err := kr.Create(tt.config) + if err != nil { + t.Fatalf("Expected no error, but got %v", err) + } + + kubeRefresher, ok := refresher.(*KubeRefresher) + if !ok { + t.Fatalf("Expected KubeRefresher type, but got %T", refresher) + } + + if kubeRefresher.ProviderType != tt.expectedProviderType { + t.Fatalf("Expected ProviderType %v, but got %v", tt.expectedProviderType, kubeRefresher.ProviderType) + } + + if kubeRefresher.ProviderRefreshInterval != tt.expectedRefreshInterval { + t.Fatalf("Expected ProviderRefreshInterval %v, but got %v", tt.expectedRefreshInterval, kubeRefresher.ProviderRefreshInterval) + } + + if kubeRefresher.Resource != tt.expectedResource { + t.Fatalf("Expected Resource %v, but got %v", tt.expectedResource, kubeRefresher.Resource) + } + }) + } +} diff --git a/pkg/keymanagementprovider/refresh/refresh.go b/pkg/keymanagementprovider/refresh/refresh.go new file mode 100644 index 000000000..36e05e243 --- /dev/null +++ b/pkg/keymanagementprovider/refresh/refresh.go @@ -0,0 +1,46 @@ +/* +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 refresh + +import ( + "context" + + "github.com/ratify-project/ratify/pkg/keymanagementprovider" +) + +const ( + KubeRefresherType = "kubeRefresher" +) + +// Refresher is an interface that defines methods to be implemented by a each refresher +type Refresher interface { + // Refresh is a method that refreshes the certificates/keys + Refresh(ctx context.Context) error + // GetResult is a method that returns the result of the refresh + GetResult() interface{} + // GetStatus is a method that returns the status of the refresh + GetStatus() interface{} +} + +// RefresherConfig is a struct that holds the configuration for a refresher +type RefresherConfig struct { + RefresherType string // RefresherType is the type of the refresher + Provider keymanagementprovider.KeyManagementProvider // Provider is the key management provider + ProviderType string // ProviderType is the type of the provider + ProviderRefreshInterval string // ProviderRefreshInterval is the refresh interval for the provider + Resource string // Resource is the resource to be refreshed +} diff --git a/pkg/keymanagementprovider/refresh/test_helper_test.go b/pkg/keymanagementprovider/refresh/test_helper_test.go new file mode 100644 index 000000000..0fd82a12a --- /dev/null +++ b/pkg/keymanagementprovider/refresh/test_helper_test.go @@ -0,0 +1,26 @@ +/* +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 refresh + +import ( + "github.com/ratify-project/ratify/pkg/keymanagementprovider/factory" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" +) + +func init() { + // Register the mock KeyManagementProviderFactory + factory.Register("test-kmp", &mocks.TestKeyManagementProviderFactory{}) +} diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 36cd2cd09..f284ceaea 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -27,16 +27,15 @@ import ( // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. - "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/httpserver" - "github.com/deislabs/ratify/pkg/featureflag" - "github.com/deislabs/ratify/pkg/policyprovider" - _ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" // register config policy provider - _ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy" // register rego policy provider - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" // register ORAS referrer store - "github.com/deislabs/ratify/pkg/utils" - _ "github.com/deislabs/ratify/pkg/verifier/notation" // register notation verifier "github.com/open-policy-agent/cert-controller/pkg/rotator" + "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/httpserver" + "github.com/ratify-project/ratify/pkg/featureflag" + _ "github.com/ratify-project/ratify/pkg/policyprovider/configpolicy" // register config policy provider + _ "github.com/ratify-project/ratify/pkg/policyprovider/regopolicy" // register rego policy provider + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" // register ORAS referrer store + "github.com/ratify-project/ratify/pkg/utils" + _ "github.com/ratify-project/ratify/pkg/verifier/notation" // register notation verifier "github.com/sirupsen/logrus" _ "k8s.io/client-go/plugin/pkg/client/auth" // import additional authentication methods @@ -47,12 +46,13 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" - configv1alpha1 "github.com/deislabs/ratify/api/v1alpha1" - configv1beta1 "github.com/deislabs/ratify/api/v1beta1" - "github.com/deislabs/ratify/pkg/controllers" - ef "github.com/deislabs/ratify/pkg/executor/core" - "github.com/deislabs/ratify/pkg/referrerstore" - vr "github.com/deislabs/ratify/pkg/verifier" + configv1alpha1 "github.com/ratify-project/ratify/api/v1alpha1" + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/controllers/clusterresource" + "github.com/ratify-project/ratify/pkg/controllers/namespaceresource" + ef "github.com/ratify-project/ratify/pkg/executor/core" //+kubebuilder:scaffold:imports ) @@ -84,28 +84,12 @@ func StartServer(httpServerAddress, configFilePath, certDirectory, caCertFile st } // initialize server - server, err := httpserver.NewServer(context.Background(), httpServerAddress, func() *ef.Executor { - var activeVerifiers []vr.ReferenceVerifier - var activeStores []referrerstore.ReferrerStore - var activePolicyEnforcer policyprovider.PolicyProvider + server, err := httpserver.NewServer(context.Background(), httpServerAddress, func(ctx context.Context) *ef.Executor { + namespace := ctxUtils.GetNamespace(ctx) - // check if there are active verifiers from crd controller - if len(controllers.VerifierMap) > 0 { - for _, value := range controllers.VerifierMap { - activeVerifiers = append(activeVerifiers, value) - } - } - - // check if there are active stores from crd controller - if len(controllers.StoreMap) > 0 { - for _, value := range controllers.StoreMap { - activeStores = append(activeStores, value) - } - } - - if !controllers.ActivePolicy.IsEmpty() { - activePolicyEnforcer = controllers.ActivePolicy.Enforcer - } + activeVerifiers := controllers.NamespacedVerifiers.GetVerifiers(namespace) + activePolicyEnforcer := controllers.NamespacedPolicies.GetPolicy(namespace) + activeStores := controllers.NamespacedStores.GetStores(namespace) // return executor with latest configuration executor := ef.Executor{ @@ -146,7 +130,7 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { Port: 9443, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, - LeaderElectionID: "1a306109.github.com/deislabs/ratify", + LeaderElectionID: "1a306109.github.com/ratify-project/ratify", // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly @@ -209,39 +193,67 @@ func StartManager(certRotatorReady chan struct{}, probeAddr string) { close(certRotatorReady) } - if err = (&controllers.VerifierReconciler{ + if err = (&clusterresource.VerifierReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Verifier") + setupLog.Error(err, "unable to create controller", "controller", "Cluster Verifier") os.Exit(1) } - if err = (&controllers.StoreReconciler{ + if err = (&namespaceresource.VerifierReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Verifier") + os.Exit(1) + } + if err = (&clusterresource.StoreReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Store") os.Exit(1) } - if err = (&controllers.CertificateStoreReconciler{ + if err = (&namespaceresource.StoreReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Certificate Store") + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Store") os.Exit(1) } - if err = (&controllers.PolicyReconciler{ + if err = (&namespaceresource.CertificateStoreReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Certificate Store") + os.Exit(1) + } + if err = (&namespaceresource.PolicyReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Policy") + os.Exit(1) + } + if err = (&clusterresource.PolicyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Policy") os.Exit(1) } - if err = (&controllers.KeyManagementProviderReconciler{ + if err = (&clusterresource.KeyManagementProviderReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Cluster Key Management Provider") + os.Exit(1) + } + if err = (&namespaceresource.KeyManagementProviderReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Key Management Provider") + setupLog.Error(err, "unable to create controller", "controller", "Namespaced Key Management Provider") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/pkg/metrics/stats_reporter.go b/pkg/metrics/stats_reporter.go index c9ec5d984..3a0f8ed44 100644 --- a/pkg/metrics/stats_reporter.go +++ b/pkg/metrics/stats_reporter.go @@ -18,12 +18,12 @@ package metrics import ( "context" + ctxUtils "github.com/ratify-project/ratify/internal/context" "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/attribute" instrument "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/sdk/instrumentation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/aggregation" ) var ( @@ -42,7 +42,7 @@ var ( ) const ( - scope = "github.com/deislabs/ratify" + scope = "github.com/ratify-project/ratify" // metric names metricNameVerificationDuration = "ratify_verification_request" @@ -71,7 +71,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 30, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1400, 1600, 1800, 2000, 2300, 2600, 4000, 4400, 4900}, }, }, @@ -82,7 +82,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 30, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1400, 1600, 1800}, }, }, @@ -93,7 +93,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 50, 100, 200, 300, 400, 600, 800, 1100, 1500, 2000}, }, }, @@ -104,7 +104,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200}, }, }, @@ -115,7 +115,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200}, }, }, @@ -126,7 +126,7 @@ func initStatsReporter() error { Scope: instrumentation.Scope{Name: scope}, }, sdkmetric.Stream{ - Aggregation: aggregation.ExplicitBucketHistogram{ + Aggregation: sdkmetric.AggregationExplicitBucketHistogram{ Boundaries: []float64{0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200}, }, }, @@ -202,6 +202,7 @@ func ReportMutationRequest(ctx context.Context, duration int64) { // subjectReference: the subject reference of the verification // success: whether the verification succeeded // isError: whether the verification failed due to an error +// workload_namespace: the namespace where workload is deployed func ReportVerifierDuration(ctx context.Context, duration int64, veriferName string, subjectReference string, success bool, isError bool) { if verifierDuration != nil { verifierDuration.Record(ctx, duration, instrument.WithAttributes( @@ -221,6 +222,10 @@ func ReportVerifierDuration(ctx context.Context, duration int64, veriferName str Key: "error", Value: attribute.BoolValue(isError), }, + attribute.KeyValue{ + Key: "workload_namespace", + Value: attribute.StringValue(ctxUtils.GetNamespace(ctx)), + }, )) } } @@ -228,9 +233,12 @@ func ReportVerifierDuration(ctx context.Context, duration int64, veriferName str // ReportSystemError reports a system error from the server handler // Attributes: // errorString: the error message +// workload_namespace: the namespace where workload is deployed func ReportSystemError(ctx context.Context, errorString string) { if systemErrorCount != nil { - systemErrorCount.Add(ctx, 1, instrument.WithAttributes(attribute.KeyValue{Key: "error", Value: attribute.StringValue(errorString)})) + systemErrorCount.Add(ctx, 1, instrument.WithAttributes( + attribute.KeyValue{Key: "error", Value: attribute.StringValue(errorString)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } @@ -238,44 +246,60 @@ func ReportSystemError(ctx context.Context, errorString string) { // Attributes: // statusCode: the status code of the request // registryHost: the host name of the registry +// workload_namespace: the namespace where workload is deployed func ReportRegistryRequestCount(ctx context.Context, statusCode int, registryHost string) { if registryRequestCount != nil { - registryRequestCount.Add(ctx, 1, instrument.WithAttributes(attribute.KeyValue{Key: "status_code", Value: attribute.IntValue(statusCode)}, attribute.KeyValue{Key: "registry_host", Value: attribute.StringValue(registryHost)})) + registryRequestCount.Add(ctx, 1, instrument.WithAttributes( + attribute.KeyValue{Key: "status_code", Value: attribute.IntValue(statusCode)}, + attribute.KeyValue{Key: "registry_host", Value: attribute.StringValue(registryHost)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } // ReportAADExchangeDuration reports the duration of an AAD exchange // Attributes: // resourceType: the scope of resource being exchanged (AKV or ACR) +// workload_namespace: the namespace where workload is deployed func ReportAADExchangeDuration(ctx context.Context, duration int64, resourceType string) { if aadExchangeDuration != nil { - aadExchangeDuration.Record(ctx, duration, instrument.WithAttributes(attribute.KeyValue{Key: "resource_type", Value: attribute.StringValue(resourceType)})) + aadExchangeDuration.Record(ctx, duration, instrument.WithAttributes( + attribute.KeyValue{Key: "resource_type", Value: attribute.StringValue(resourceType)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } // ReportACRExchangeDuration reports the duration of an ACR exchange (AAD token for ACR refresh token) // Attributes: // repository: the repository being accessed +// workload_namespace: the namespace where workload is deployed func ReportACRExchangeDuration(ctx context.Context, duration int64, repository string) { if acrExchangeDuration != nil { - acrExchangeDuration.Record(ctx, duration, instrument.WithAttributes(attribute.KeyValue{Key: "repository", Value: attribute.StringValue(repository)})) + acrExchangeDuration.Record(ctx, duration, instrument.WithAttributes( + attribute.KeyValue{Key: "repository", Value: attribute.StringValue(repository)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } // ReportAKVCertificateDuration reports the duration of an AKV certificate fetch // Attributes: // certificateName: the object name of the certificate +// workload_namespace: the namespace where workload is deployed func ReportAKVCertificateDuration(ctx context.Context, duration int64, certificateName string) { if akvCertificateDuration != nil { - akvCertificateDuration.Record(ctx, duration, instrument.WithAttributes(attribute.KeyValue{Key: "certificate_name", Value: attribute.StringValue(certificateName)})) + akvCertificateDuration.Record(ctx, duration, instrument.WithAttributes( + attribute.KeyValue{Key: "certificate_name", Value: attribute.StringValue(certificateName)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } // ReportBlobCacheCount reports a blob cache hit or miss // Attributes: // hit: whether the blob was found in the cache +// workload_namespace: the namespace where workload is deployed func ReportBlobCacheCount(ctx context.Context, hit bool) { if cacheBlobCount != nil { - cacheBlobCount.Add(ctx, 1, instrument.WithAttributes(attribute.KeyValue{Key: "hit", Value: attribute.BoolValue(hit)})) + cacheBlobCount.Add(ctx, 1, instrument.WithAttributes( + attribute.KeyValue{Key: "hit", Value: attribute.BoolValue(hit)}, + attribute.KeyValue{Key: "workload_namespace", Value: attribute.StringValue(ctxUtils.GetNamespace(ctx))})) } } diff --git a/pkg/metrics/stats_reporter_test.go b/pkg/metrics/stats_reporter_test.go index 7580b37e2..54dc1e362 100644 --- a/pkg/metrics/stats_reporter_test.go +++ b/pkg/metrics/stats_reporter_test.go @@ -20,10 +20,13 @@ import ( "fmt" "testing" + ctxUtils "github.com/ratify-project/ratify/internal/context" "go.opentelemetry.io/otel/attribute" instrument "go.opentelemetry.io/otel/metric" ) +const testNamespace = "testNamespace" + type MockInt64Histogram struct { instrument.Int64Histogram Value int64 @@ -95,11 +98,12 @@ func TestReportVerifierDuration(t *testing.T) { mockDuration := &MockInt64Histogram{Attributes: make(map[string]string)} verifierDuration = mockDuration - ReportVerifierDuration(context.Background(), 5, "test_verifier", "test_subject", true, true) + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportVerifierDuration(ctx, 5, "test_verifier", "test_subject", true, true) if mockDuration.Value != 5 { t.Fatalf("ReportVerifierDuration() mockDuration.Value = %v, expected %v", mockDuration.Value, 5) } - if len(mockDuration.Attributes) != 4 { + if len(mockDuration.Attributes) != 5 { t.Fatalf("ReportVerifierDuration() len(mockDuration.Attributes) = %v, expected %v", len(mockDuration.Attributes), 2) } if mockDuration.Attributes["verifier"] != "test_verifier" { @@ -111,6 +115,9 @@ func TestReportVerifierDuration(t *testing.T) { if mockDuration.Attributes["error"] != "true" { t.Fatalf("expected error attribute to be true but got %s", mockDuration.Attributes["error"]) } + if mockDuration.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockDuration.Attributes["workload_namespac"]) + } } func TestReportSystemError(t *testing.T) { @@ -120,16 +127,20 @@ func TestReportSystemError(t *testing.T) { mockCounter := &MockInt64Counter{Attributes: make(map[string]string)} systemErrorCount = mockCounter - ReportSystemError(context.Background(), "test_error") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportSystemError(ctx, "test_error") if mockCounter.Value != 1 { t.Fatalf("ReportSystemError() mockCounter.Value = %v, expected %v", mockCounter.Value, 1) } - if len(mockCounter.Attributes) != 1 { + if len(mockCounter.Attributes) != 2 { t.Fatalf("ReportSystemError() len(mockCounter.Attributes) = %v, expected %v", len(mockCounter.Attributes), 1) } if mockCounter.Attributes["error"] != "test_error" { t.Fatalf("expected error attributes to be test_error but got %s", mockCounter.Attributes["error"]) } + if mockCounter.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockCounter.Attributes["workload_namespac"]) + } } func TestReportRequestCount(t *testing.T) { @@ -139,11 +150,12 @@ func TestReportRequestCount(t *testing.T) { mockCounter := &MockInt64Counter{Attributes: make(map[string]string)} registryRequestCount = mockCounter - ReportRegistryRequestCount(context.Background(), 429, "test_registry_host") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportRegistryRequestCount(ctx, 429, "test_registry_host") if mockCounter.Value != 1 { t.Fatalf("ReportRequestCount() mockCounter.Value = %v, expected %v", mockCounter.Value, 1) } - if len(mockCounter.Attributes) != 2 { + if len(mockCounter.Attributes) != 3 { t.Fatalf("ReportRequestCount() len(mockCounter.Attributes) = %v, expected %v", len(mockCounter.Attributes), 2) } if mockCounter.Attributes["status_code"] != "429" { @@ -152,6 +164,9 @@ func TestReportRequestCount(t *testing.T) { if mockCounter.Attributes["registry_host"] != "test_registry_host" { t.Fatalf("expected registry_host attribute to be test_registry_host but got %s", mockCounter.Attributes["registry_host"]) } + if mockCounter.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockCounter.Attributes["workload_namespac"]) + } } func TestReportAADExchangeDuration(t *testing.T) { @@ -161,16 +176,20 @@ func TestReportAADExchangeDuration(t *testing.T) { mockDuration := &MockInt64Histogram{Attributes: make(map[string]string)} aadExchangeDuration = mockDuration - ReportAADExchangeDuration(context.Background(), 500, "test_scope") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportAADExchangeDuration(ctx, 500, "test_scope") if mockDuration.Value != 500 { t.Fatalf("ReportAADExchangeDuration() mockDuration.Value = %v, expected %v", mockDuration.Value, 500) } - if len(mockDuration.Attributes) != 1 { + if len(mockDuration.Attributes) != 2 { t.Fatalf("ReportAADExchangeDuration() len(mockDuration.Attributes) = %v, expected %v", len(mockDuration.Attributes), 1) } if mockDuration.Attributes["resource_type"] != "test_scope" { t.Fatalf("expected resource_type attribute to be test_scope but got %s", mockDuration.Attributes["resource_type"]) } + if mockDuration.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockDuration.Attributes["workload_namespac"]) + } } func TestReportACRExchangeDuration(t *testing.T) { @@ -180,16 +199,20 @@ func TestReportACRExchangeDuration(t *testing.T) { mockDuration := &MockInt64Histogram{Attributes: make(map[string]string)} acrExchangeDuration = mockDuration - ReportACRExchangeDuration(context.Background(), 500, "test_repo") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportACRExchangeDuration(ctx, 500, "test_repo") if mockDuration.Value != 500 { t.Fatalf("ReportACRExchangeDuration() mockDuration.Value = %v, expected %v", mockDuration.Value, 500) } - if len(mockDuration.Attributes) != 1 { + if len(mockDuration.Attributes) != 2 { t.Fatalf("ReportACRExchangeDuration() len(mockDuration.Attributes) = %v, expected %v", len(mockDuration.Attributes), 1) } if mockDuration.Attributes["repository"] != "test_repo" { t.Fatalf("expected repository attribute to be test_repo but got %s", mockDuration.Attributes["repository"]) } + if mockDuration.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockDuration.Attributes["workload_namespac"]) + } } func TestReportAKVCertificateDuration(t *testing.T) { @@ -199,16 +222,20 @@ func TestReportAKVCertificateDuration(t *testing.T) { mockDuration := &MockInt64Histogram{Attributes: make(map[string]string)} akvCertificateDuration = mockDuration - ReportAKVCertificateDuration(context.Background(), 500, "test_cert") + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportAKVCertificateDuration(ctx, 500, "test_cert") if mockDuration.Value != 500 { t.Fatalf("ReportAKVCertificateDuration() mockDuration.Value = %v, expected %v", mockDuration.Value, 500) } - if len(mockDuration.Attributes) != 1 { + if len(mockDuration.Attributes) != 2 { t.Fatalf("ReportAKVCertificateDuration() len(mockDuration.Attributes) = %v, expected %v", len(mockDuration.Attributes), 1) } if mockDuration.Attributes["certificate_name"] != "test_cert" { t.Fatalf("expected certificate_name attribute to be test_cert but got %s", mockDuration.Attributes["certificate_name"]) } + if mockDuration.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockDuration.Attributes["workload_namespac"]) + } } func TestReportBlobCacheCount(t *testing.T) { @@ -218,14 +245,18 @@ func TestReportBlobCacheCount(t *testing.T) { mockCounter := &MockInt64Counter{Attributes: make(map[string]string)} cacheBlobCount = mockCounter - ReportBlobCacheCount(context.Background(), true) + ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace) + ReportBlobCacheCount(ctx, true) if mockCounter.Value != 1 { t.Fatalf("ReportBlobCacheCount() mockCounter.Value = %v, expected %v", mockCounter.Value, 1) } - if len(mockCounter.Attributes) != 1 { + if len(mockCounter.Attributes) != 2 { t.Fatalf("ReportBlobCacheCount() len(mockCounter.Attributes) = %v, expected %v", len(mockCounter.Attributes), 1) } if mockCounter.Attributes["hit"] != "true" { t.Fatalf("expected hit attribute to be true but got %s", mockCounter.Attributes["hit"]) } + if mockCounter.Attributes["workload_namespace"] != testNamespace { + t.Fatalf("expected workload_namespace attribute to be %s but got %s", testNamespace, mockCounter.Attributes["workload_namespac"]) + } } diff --git a/pkg/policyprovider/api.go b/pkg/policyprovider/api.go index 6492ec742..f137abc88 100644 --- a/pkg/policyprovider/api.go +++ b/pkg/policyprovider/api.go @@ -18,9 +18,9 @@ package policyprovider import ( "context" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" ) // PolicyProvider is an interface with methods that represents policy decisions. diff --git a/pkg/policyprovider/configpolicy/configpolicy.go b/pkg/policyprovider/configpolicy/configpolicy.go index d8bfcf588..2ab93ca02 100644 --- a/pkg/policyprovider/configpolicy/configpolicy.go +++ b/pkg/policyprovider/configpolicy/configpolicy.go @@ -20,15 +20,15 @@ import ( "encoding/json" "fmt" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" - vt "github.com/deislabs/ratify/pkg/policyprovider/types" - "github.com/deislabs/ratify/pkg/verifier" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/policyprovider" + "github.com/ratify-project/ratify/pkg/policyprovider/config" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" + vt "github.com/ratify-project/ratify/pkg/policyprovider/types" + "github.com/ratify-project/ratify/pkg/verifier" ) // PolicyEnforcer describes different polices that are enforced during verification @@ -97,11 +97,8 @@ func (enforcer PolicyEnforcer) ContinueVerifyOnFailure(_ context.Context, _ comm // ErrorToVerifyResult converts an error to a properly formatted verify result func (enforcer PolicyEnforcer) ErrorToVerifyResult(_ context.Context, subjectRefString string, verifyError error) types.VerifyResult { - errorReport := verifier.VerifierResult{ - Subject: subjectRefString, - IsSuccess: false, - Message: fmt.Sprintf("verification failed: %v", verifyError), - } + verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("failed to verify artifact: %s", subjectRefString)).WithError(verifyError) + errorReport := verifier.NewVerifierResult(subjectRefString, "", "", "", false, &verifierErr, nil) var reports []interface{} reports = append(reports, errorReport) return types.VerifyResult{IsSuccess: false, VerifierReports: reports} diff --git a/pkg/policyprovider/configpolicy/configpolicy_test.go b/pkg/policyprovider/configpolicy/configpolicy_test.go index 596b561a2..565359ef3 100644 --- a/pkg/policyprovider/configpolicy/configpolicy_test.go +++ b/pkg/policyprovider/configpolicy/configpolicy_test.go @@ -19,14 +19,14 @@ import ( "context" "testing" - "github.com/deislabs/ratify/pkg/common" - vt "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - pc "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" - "github.com/deislabs/ratify/pkg/policyprovider/types" - vr "github.com/deislabs/ratify/pkg/verifier" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + vt "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + pc "github.com/ratify-project/ratify/pkg/policyprovider/config" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" + "github.com/ratify-project/ratify/pkg/policyprovider/types" + vr "github.com/ratify-project/ratify/pkg/verifier" ) func TestPolicyEnforcer_ContinueVerifyOnFailure(t *testing.T) { diff --git a/pkg/policyprovider/factory/factory.go b/pkg/policyprovider/factory/factory.go index dbf9ed541..048fb221a 100644 --- a/pkg/policyprovider/factory/factory.go +++ b/pkg/policyprovider/factory/factory.go @@ -19,10 +19,10 @@ import ( "fmt" "strings" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - "github.com/deislabs/ratify/pkg/verifier/types" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/policyprovider" + "github.com/ratify-project/ratify/pkg/policyprovider/config" + "github.com/ratify-project/ratify/pkg/verifier/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/policyprovider/factory/factory_test.go b/pkg/policyprovider/factory/factory_test.go index 157c6972e..dbc255b8c 100644 --- a/pkg/policyprovider/factory/factory_test.go +++ b/pkg/policyprovider/factory/factory_test.go @@ -18,9 +18,9 @@ package factory import ( "testing" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - "github.com/deislabs/ratify/pkg/policyprovider/mocks" + "github.com/ratify-project/ratify/pkg/policyprovider" + "github.com/ratify-project/ratify/pkg/policyprovider/config" + "github.com/ratify-project/ratify/pkg/policyprovider/mocks" ) type TestPolicyProviderFactory struct{} diff --git a/pkg/policyprovider/mocks/types.go b/pkg/policyprovider/mocks/types.go index 0903f019d..bf57b1870 100644 --- a/pkg/policyprovider/mocks/types.go +++ b/pkg/policyprovider/mocks/types.go @@ -18,10 +18,10 @@ package mocks import ( "context" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/verifier" ) type TestPolicyProvider struct{} diff --git a/pkg/policyprovider/policyengine/opaengine/opa_engine.go b/pkg/policyprovider/policyengine/opaengine/opa_engine.go index 1f1174d68..6eddfe9af 100644 --- a/pkg/policyprovider/policyengine/opaengine/opa_engine.go +++ b/pkg/policyprovider/policyengine/opaengine/opa_engine.go @@ -20,8 +20,8 @@ import ( "errors" "strings" - "github.com/deislabs/ratify/pkg/policyprovider/policyengine" - "github.com/deislabs/ratify/pkg/policyprovider/policyquery" + "github.com/ratify-project/ratify/pkg/policyprovider/policyengine" + "github.com/ratify-project/ratify/pkg/policyprovider/policyquery" ) const OPA = "opa" diff --git a/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go b/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go index 49160c3ba..9184155a3 100644 --- a/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go +++ b/pkg/policyprovider/policyengine/opaengine/opa_engine_test.go @@ -19,7 +19,7 @@ import ( "context" "testing" - query "github.com/deislabs/ratify/pkg/policyprovider/policyquery/rego" + query "github.com/ratify-project/ratify/pkg/policyprovider/policyquery/rego" ) const ( diff --git a/pkg/policyprovider/policyquery/rego/query.go b/pkg/policyprovider/policyquery/rego/query.go index f5832a855..3c7f60a85 100644 --- a/pkg/policyprovider/policyquery/rego/query.go +++ b/pkg/policyprovider/policyquery/rego/query.go @@ -19,9 +19,9 @@ import ( "context" "fmt" - "github.com/deislabs/ratify/pkg/policyprovider/policyquery" "github.com/open-policy-agent/opa/rego" "github.com/pkg/errors" + "github.com/ratify-project/ratify/pkg/policyprovider/policyquery" ) const ( diff --git a/pkg/policyprovider/regopolicy/regopolicy.go b/pkg/policyprovider/regopolicy/regopolicy.go index ad3d85057..f5251a0cd 100644 --- a/pkg/policyprovider/regopolicy/regopolicy.go +++ b/pkg/policyprovider/regopolicy/regopolicy.go @@ -21,18 +21,18 @@ import ( "fmt" "os" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/policyprovider" - "github.com/deislabs/ratify/pkg/policyprovider/config" - pf "github.com/deislabs/ratify/pkg/policyprovider/factory" - "github.com/deislabs/ratify/pkg/policyprovider/policyengine" - opa "github.com/deislabs/ratify/pkg/policyprovider/policyengine/opaengine" - query "github.com/deislabs/ratify/pkg/policyprovider/policyquery/rego" - policyTypes "github.com/deislabs/ratify/pkg/policyprovider/types" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/policyprovider" + "github.com/ratify-project/ratify/pkg/policyprovider/config" + pf "github.com/ratify-project/ratify/pkg/policyprovider/factory" + "github.com/ratify-project/ratify/pkg/policyprovider/policyengine" + opa "github.com/ratify-project/ratify/pkg/policyprovider/policyengine/opaengine" + query "github.com/ratify-project/ratify/pkg/policyprovider/policyquery/rego" + policyTypes "github.com/ratify-project/ratify/pkg/policyprovider/types" ) type policyEnforcer struct { diff --git a/pkg/policyprovider/regopolicy/regopolicy_test.go b/pkg/policyprovider/regopolicy/regopolicy_test.go index d8327943a..eb6ab05f8 100644 --- a/pkg/policyprovider/regopolicy/regopolicy_test.go +++ b/pkg/policyprovider/regopolicy/regopolicy_test.go @@ -21,10 +21,10 @@ import ( "reflect" "testing" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/policyprovider/config" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/policyprovider/config" ) const ( diff --git a/pkg/referrerstore/api.go b/pkg/referrerstore/api.go index ed2da7f53..39482be71 100644 --- a/pkg/referrerstore/api.go +++ b/pkg/referrerstore/api.go @@ -18,10 +18,10 @@ package referrerstore import ( "context" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore/config" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/config" ) // ListReferrersResult represents the result of ListReferrers API diff --git a/pkg/referrerstore/factory/factory.go b/pkg/referrerstore/factory/factory.go index fb80faa50..740ce5f15 100644 --- a/pkg/referrerstore/factory/factory.go +++ b/pkg/referrerstore/factory/factory.go @@ -21,13 +21,13 @@ import ( "path" "strings" - re "github.com/deislabs/ratify/errors" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/featureflag" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/plugin" - "github.com/deislabs/ratify/pkg/referrerstore/types" + re "github.com/ratify-project/ratify/errors" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/featureflag" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/plugin" + "github.com/ratify-project/ratify/pkg/referrerstore/types" "github.com/sirupsen/logrus" ) diff --git a/pkg/referrerstore/factory/factory_test.go b/pkg/referrerstore/factory/factory_test.go index 405603d35..8c42ab654 100644 --- a/pkg/referrerstore/factory/factory_test.go +++ b/pkg/referrerstore/factory/factory_test.go @@ -21,12 +21,12 @@ import ( "path" "testing" - "github.com/deislabs/ratify/pkg/featureflag" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/mocks" - "github.com/deislabs/ratify/pkg/referrerstore/plugin" - "github.com/deislabs/ratify/pkg/utils" + "github.com/ratify-project/ratify/pkg/featureflag" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/referrerstore/plugin" + "github.com/ratify-project/ratify/pkg/utils" ) const ( diff --git a/pkg/referrerstore/mocks/memory_store.go b/pkg/referrerstore/mocks/memory_store.go index 93bbf1749..9a0a07194 100644 --- a/pkg/referrerstore/mocks/memory_store.go +++ b/pkg/referrerstore/mocks/memory_store.go @@ -17,12 +17,12 @@ import ( "context" "fmt" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" ) type MemoryTestStore struct { @@ -53,14 +53,14 @@ func (store *MemoryTestStore) GetBlobContent(_ context.Context, _ common.Referen if item, ok := store.Blobs[digest]; ok { return item, nil } - return nil, nil + return nil, fmt.Errorf("blob not found") } func (store *MemoryTestStore) GetReferenceManifest(_ context.Context, _ common.Reference, desc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) { if item, ok := store.Manifests[desc.Digest]; ok { return item, nil } - return ocispecs.ReferenceManifest{}, nil + return ocispecs.ReferenceManifest{}, fmt.Errorf("manifest not found") } func (store *MemoryTestStore) GetConfig() *config.StoreConfig { diff --git a/pkg/referrerstore/mocks/types.go b/pkg/referrerstore/mocks/types.go index 546b92e67..1685f98e8 100644 --- a/pkg/referrerstore/mocks/types.go +++ b/pkg/referrerstore/mocks/types.go @@ -20,12 +20,12 @@ import ( "fmt" "time" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" ) type TestStore struct { diff --git a/pkg/referrerstore/oras/cache.go b/pkg/referrerstore/oras/cache.go index 3b983fa1e..d0c6c0608 100644 --- a/pkg/referrerstore/oras/cache.go +++ b/pkg/referrerstore/oras/cache.go @@ -21,12 +21,12 @@ import ( "fmt" "time" - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" ) const defaultTTL = 10 diff --git a/pkg/referrerstore/oras/cache_test.go b/pkg/referrerstore/oras/cache_test.go index 569c20e2f..77fa1c7d2 100644 --- a/pkg/referrerstore/oras/cache_test.go +++ b/pkg/referrerstore/oras/cache_test.go @@ -22,12 +22,12 @@ import ( "testing" "time" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" oci "github.com/opencontainers/image-spec/specs-go/v1" ) diff --git a/pkg/referrerstore/oras/cosign.go b/pkg/referrerstore/oras/cosign.go index 7d0d9a72b..4f6fc2457 100644 --- a/pkg/referrerstore/oras/cosign.go +++ b/pkg/referrerstore/oras/cosign.go @@ -21,10 +21,10 @@ import ( "fmt" "strings" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" oci "github.com/opencontainers/image-spec/specs-go/v1" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry" @@ -46,7 +46,7 @@ func getCosignReferences(ctx context.Context, subjectReference common.Reference, return nil, nil } evictOnError(ctx, err, subjectReference.Original) - return nil, re.ErrorCodeRepositoryOperationFailure.WithError(err).WithComponentType(re.ReferrerStore) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail(fmt.Sprintf("Failed to validate existence of Cosign signature of the artifact: %+v", subjectReference)).WithError(err) } references = append(references, ocispecs.ReferenceDescriptor{ @@ -64,7 +64,7 @@ func getCosignReferences(ctx context.Context, subjectReference common.Reference, func attachedImageTag(subjectReference common.Reference, tagSuffix string) (string, error) { // sha256:d34db33f -> sha256-d34db33f.suffix if subjectReference.Digest.String() == "" { - return "", re.ErrorCodeReferenceInvalid.WithComponentType(re.ReferrerStore).WithDetail("Cosign subject digest is empty") + return "", re.ErrorCodeReferenceInvalid.WithDetail("The digest of the artifact is empty") } tagStr := strings.ReplaceAll(subjectReference.Digest.String(), ":", "-") + tagSuffix return fmt.Sprintf("%s:%s", subjectReference.Path, tagStr), nil diff --git a/pkg/referrerstore/oras/cosign_test.go b/pkg/referrerstore/oras/cosign_test.go index 3e87c001e..32693fa13 100644 --- a/pkg/referrerstore/oras/cosign_test.go +++ b/pkg/referrerstore/oras/cosign_test.go @@ -23,14 +23,14 @@ import ( "reflect" "testing" - ratifyerrors "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/cache" - _ "github.com/deislabs/ratify/pkg/cache/ristretto" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore/oras/mocks" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" + ratifyerrors "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/cache" + _ "github.com/ratify-project/ratify/pkg/cache/ristretto" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/oras/mocks" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote/errcode" ) diff --git a/pkg/referrerstore/oras/mocks/oras_storage.go b/pkg/referrerstore/oras/mocks/oras_storage.go index a02e6cc3f..5567a318f 100644 --- a/pkg/referrerstore/oras/mocks/oras_storage.go +++ b/pkg/referrerstore/oras/mocks/oras_storage.go @@ -28,9 +28,15 @@ import ( type TestStorage struct { content.Storage ExistsMap map[digest.Digest]io.Reader + ExistsErr error + FetchErr error + PushErr error } func (s TestStorage) Exists(_ context.Context, target oci.Descriptor) (bool, error) { + if s.ExistsErr != nil { + return false, s.ExistsErr + } if _, ok := s.ExistsMap[target.Digest]; ok { return true, nil } @@ -38,11 +44,17 @@ func (s TestStorage) Exists(_ context.Context, target oci.Descriptor) (bool, err } func (s TestStorage) Push(_ context.Context, expected oci.Descriptor, content io.Reader) error { + if s.PushErr != nil { + return s.PushErr + } s.ExistsMap[expected.Digest] = content return nil } func (s TestStorage) Fetch(_ context.Context, target oci.Descriptor) (io.ReadCloser, error) { + if s.FetchErr != nil { + return nil, s.FetchErr + } if reader, ok := s.ExistsMap[target.Digest]; ok { return io.NopCloser(reader), nil } diff --git a/pkg/referrerstore/oras/oras.go b/pkg/referrerstore/oras/oras.go index 45bfc999a..64679dd0a 100644 --- a/pkg/referrerstore/oras/oras.go +++ b/pkg/referrerstore/oras/oras.go @@ -37,23 +37,23 @@ import ( "oras.land/oras-go/v2/registry/remote/errcode" "oras.land/oras-go/v2/registry/remote/retry" - ratifyconfig "github.com/deislabs/ratify/config" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/internal/version" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/common/oras/authprovider" - _ "github.com/deislabs/ratify/pkg/common/oras/authprovider/aws" // register aws auth provider - _ "github.com/deislabs/ratify/pkg/common/oras/authprovider/azure" // register azure auth provider - commonutils "github.com/deislabs/ratify/pkg/common/utils" - "github.com/deislabs/ratify/pkg/homedir" - "github.com/deislabs/ratify/pkg/metrics" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/factory" "github.com/opencontainers/go-digest" + ratifyconfig "github.com/ratify-project/ratify/config" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/internal/version" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/common/oras/authprovider" + _ "github.com/ratify-project/ratify/pkg/common/oras/authprovider/aws" // register aws auth provider + _ "github.com/ratify-project/ratify/pkg/common/oras/authprovider/azure" // register azure auth provider + commonutils "github.com/ratify-project/ratify/pkg/common/utils" + "github.com/ratify-project/ratify/pkg/homedir" + "github.com/ratify-project/ratify/pkg/metrics" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/factory" ) const ( @@ -183,7 +183,7 @@ func createBaseStore(version string, storeConfig config.StorePluginConfig) (*ora insecureTransport.MaxIdleConnsPerHost = HTTPMaxIdleConnsPerHost // #nosec G402 insecureTransport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: true, //nolint:gosec } insecureRetryTransport := retry.NewTransport(insecureTransport) insecureRetryTransport.Policy = customRetryPolicy @@ -208,7 +208,7 @@ func (store *orasStore) GetConfig() *config.StoreConfig { func (store *orasStore) ListReferrers(ctx context.Context, subjectReference common.Reference, _ []string, _ string, subjectDesc *ocispecs.SubjectDescriptor) (referrerstore.ListReferrersResult, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return referrerstore.ListReferrersResult{}, re.ErrorCodeCreateRepositoryFailure.WithError(err).WithComponentType(re.ReferrerStore) + return referrerstore.ListReferrersResult{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } // resolve subject descriptor if not provided @@ -256,9 +256,11 @@ func (store *orasStore) ListReferrers(ctx context.Context, subjectReference comm func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error) { var err error + var blobContent []byte + repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return nil, err + return nil, re.ErrorCodeGetBlobContentFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } // create a dummy Descriptor to check the local store cache @@ -270,10 +272,18 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com // check if blob exists in local ORAS cache isCached, err := store.localCache.Exists(ctx, blobDescriptor) if err != nil { - return nil, err + logger.GetLogger(ctx, logOpt).Warnf("failed to check if blob [%s] exists in cache: %v", blobDescriptor.Digest.String(), err) } metrics.ReportBlobCacheCount(ctx, isCached) + if isCached { + blobContent, err = store.getRawContentFromCache(ctx, blobDescriptor) + if err != nil { + isCached = false + logger.GetLogger(ctx, logOpt).Warnf("failed to get blob [%s] from cache: %v", blobDescriptor.Digest.String(), err) + } + } + if !isCached { // generate the reference path with digest ref := fmt.Sprintf("%s@%s", subjectReference.Path, digest) @@ -282,56 +292,65 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com blobDesc, rc, err := repository.Blobs().FetchReference(ctx, ref) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return nil, err + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to fetch the artifact metadata from the registry").WithError(err) + } + if blobContent, err = io.ReadAll(rc); err != nil { + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to parse the artifact metadata").WithError(err) } // push fetched content to local ORAS cache + // If multiple goroutines try to push the same blob to the cache, oras-go + // may return `ErrAlreadyExists` error. This is expected and can be ignored. orasExistsExpectedError := fmt.Errorf("%s: %s: %w", blobDesc.Digest, blobDesc.MediaType, errdef.ErrAlreadyExists) - err = store.localCache.Push(ctx, blobDesc, rc) - if err != nil && err.Error() != orasExistsExpectedError.Error() { - return nil, err + if err = store.localCache.Push(ctx, blobDesc, bytes.NewReader(blobContent)); err != nil && err.Error() != orasExistsExpectedError.Error() { + logger.GetLogger(ctx, logOpt).Warnf("failed to save blob [%s] in cache: %v", blobDesc.Digest, err) } } - return store.getRawContentFromCache(ctx, blobDescriptor) + return blobContent, nil } func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReference common.Reference, referenceDesc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeCreateRepositoryFailure.NewError(re.ReferrerStore, storeName, re.EmptyLink, err, nil, re.HideStackTrace) + return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } var manifestBytes []byte // check if manifest exists in local ORAS cache isCached, err := store.localCache.Exists(ctx, referenceDesc.Descriptor) if err != nil { - return ocispecs.ReferenceManifest{}, err + logger.GetLogger(ctx, logOpt).Warnf("failed to check if manifest [%s] exists in cache: %v", referenceDesc.Descriptor.Digest, err) } metrics.ReportBlobCacheCount(ctx, isCached) + if isCached { + manifestBytes, err = store.getRawContentFromCache(ctx, referenceDesc.Descriptor) + if err != nil { + isCached = false + logger.GetLogger(ctx, logOpt).Warnf("failed to get manifest [%s] from cache: %v", referenceDesc.Descriptor.Digest, err) + } + } + if !isCached { // fetch manifest content from repository manifestReader, err := repository.Fetch(ctx, referenceDesc.Descriptor) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.NewError(re.ReferrerStore, storeName, re.EmptyLink, err, nil, re.HideStackTrace) + return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to fetch the artifact metadata from the registry").WithError(err) } manifestBytes, err = io.ReadAll(manifestReader) if err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeManifestInvalid.WithError(err).WithPluginName(storeName).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeManifestInvalid.WithDetail("Failed to parse the artifact metadata").WithError(err) } // push fetched manifest to local ORAS cache + // If multiple goroutines try to push the same manifest to the cache, oras-go + // may return `ErrAlreadyExists` error. This is expected and can be ignored. orasExistsExpectedError := fmt.Errorf("%s: %s: %w", referenceDesc.Descriptor.Digest, referenceDesc.Descriptor.MediaType, errdef.ErrAlreadyExists) err = store.localCache.Push(ctx, referenceDesc.Descriptor, bytes.NewReader(manifestBytes)) if err != nil && err.Error() != orasExistsExpectedError.Error() { - return ocispecs.ReferenceManifest{}, err - } - } else { - manifestBytes, err = store.getRawContentFromCache(ctx, referenceDesc.Descriptor) - if err != nil { - return ocispecs.ReferenceManifest{}, err + logger.GetLogger(ctx, logOpt).Warnf("failed to save manifest [%s] in cache: %v", referenceDesc.Descriptor.Digest, err) } } @@ -341,15 +360,15 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen if referenceDesc.Descriptor.MediaType == oci.MediaTypeImageManifest { var imageManifest oci.Manifest if err := json.Unmarshal(manifestBytes, &imageManifest); err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithError(err).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithDetail("Failed to parse artifact metadata of mediatype `application/vnd.oci.image.manifest.v1+json`").WithError(err).WithRemediation("Please check if the artifact metadata was created correctly.") } referenceManifest = commonutils.OciManifestToReferenceManifest(imageManifest) } else if referenceDesc.Descriptor.MediaType == ocispecs.MediaTypeArtifactManifest { if err := json.Unmarshal(manifestBytes, &referenceManifest); err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithError(err).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithDetail("Failed to parse artifact metadata of mediatype `application/vnd.oci.artifact.manifest.v1+json`").WithError(err).WithRemediation("Please check if the artifact metadata was created correctly.") } } else { - return ocispecs.ReferenceManifest{}, fmt.Errorf("unsupported manifest media type: %s", referenceDesc.Descriptor.MediaType) + return ocispecs.ReferenceManifest{}, re.ErrorCodeGetReferenceManifestFailure.WithDetail(fmt.Sprintf("Unsupported artifact metadata of media type %s", referenceDesc.Descriptor.MediaType)).WithRemediation("Please check if the artifact metadata was created correctly.") } return referenceManifest, nil @@ -358,13 +377,13 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen func (store *orasStore) GetSubjectDescriptor(ctx context.Context, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return nil, re.ErrorCodeCreateRepositoryFailure.WithError(err).WithComponentType(re.ReferrerStore).WithPluginName(storeName) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to remote registry").WithError(err) } desc, err := repository.Resolve(ctx, subjectReference.Original) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return nil, re.ErrorCodeRepositoryOperationFailure.WithError(err).WithPluginName(storeName) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail(fmt.Sprintf("Unable to resolve the reference: %s", subjectReference.Original)).WithError(err) } return &ocispecs.SubjectDescriptor{Descriptor: desc}, nil @@ -437,7 +456,7 @@ func createDefaultRepository(ctx context.Context, store *orasStore, targetRef co } // set the provider to return the resolved credentials - credentialProvider := func(ctx context.Context, registry string) (auth.Credential, error) { + credentialProvider := func(_ context.Context, _ string) (auth.Credential, error) { if authConfig.Username != "" || authConfig.Password != "" || authConfig.IdentityToken != "" { return auth.Credential{ Username: authConfig.Username, diff --git a/pkg/referrerstore/oras/oras_test.go b/pkg/referrerstore/oras/oras_test.go index b561e5009..33eface56 100644 --- a/pkg/referrerstore/oras/oras_test.go +++ b/pkg/referrerstore/oras/oras_test.go @@ -29,18 +29,67 @@ import ( "testing" "time" - "github.com/deislabs/ratify/pkg/cache" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/oras/mocks" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/cache" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/oras/mocks" + "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote/errcode" ) -const inputOriginalPath = "localhost:5000/net-monitor:v0" +const ( + inputOriginalPath = "localhost:5000/net-monitor:v0" + wrongReferenceMediatype = "application/vnd.oci.image.manifest.wrong.v1+json" + validReferenceMediatype = "application/vnd.oci.image.manifest.right.v1+json" +) + +var ( + artifactDigestNotCached = digest.FromString("testArtifactDigestNotCached") + artifactDigest = digest.FromString("testArtifactDigest") + invalidManifestBytes = []byte("invalid manifest") + blobDigest = digest.FromString("testBlobDigest") + firstDigest = digest.FromString("testDigest") + manifestNotCachedBytes []byte + manifestCachedBytesWithWrongType []byte + manifestCachedBytes []byte +) + +func init() { + manifestNotCached := oci.Manifest{ + MediaType: validReferenceMediatype, + Config: oci.Descriptor{}, + Layers: []oci.Descriptor{}, + } + manifestNotCachedBytes, _ = json.Marshal(manifestNotCached) + + manifestCachedWithWrongType := oci.Manifest{ + MediaType: wrongReferenceMediatype, + Config: oci.Descriptor{}, + Layers: []oci.Descriptor{}, + } + manifestCachedBytesWithWrongType, _ = json.Marshal(manifestCachedWithWrongType) + + manifestCached := oci.Manifest{ + MediaType: validReferenceMediatype, + Config: oci.Descriptor{}, + Layers: []oci.Descriptor{}, + } + manifestCachedBytes, _ = json.Marshal(manifestCached) +} + +type errorReader struct{} + +func (r *errorReader) Read(_ []byte) (int, error) { + return 0, errors.New("error reading") +} + +func (r *errorReader) Close() error { + return nil +} // TestORASName tests the Name method of the oras store. func TestORASName(t *testing.T) { @@ -101,7 +150,7 @@ func TestORASListReferrers_SubjectDesc(t *testing.T) { }, }, } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { + store.createRepository = func(_ context.Context, _ *orasStore, _ common.Reference) (registry.Repository, error) { return testRepo, nil } inputRef := common.Reference{ @@ -158,7 +207,7 @@ func TestORASListReferrers_NoSubjectDesc(t *testing.T) { }, }, } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { + store.createRepository = func(_ context.Context, _ *orasStore, _ common.Reference) (registry.Repository, error) { return testRepo, nil } inputRef := common.Reference{ @@ -189,222 +238,522 @@ func TestORASListReferrers_NoSubjectDesc(t *testing.T) { // TODO: add cosign test for List Referrers -// TestORASGetReferenceManifest_CachedDesc tests that the reference manifest is returned from the cache if it exists -func TestORASGetReferenceManifest_CachedDesc(t *testing.T) { - conf := config.StorePluginConfig{ - "name": "oras", - } - ctx := context.Background() - firstDigest := digest.FromString("testDigest") - artifactDigest := digest.FromString("testArtifactDigest") - expectedReferenceMediatype := "application/vnd.oci.image.manifest.right.v1+json" - wrongReferenceMediatype := "application/vnd.oci.image.manifest.wrong.v1+json" - store, err := createBaseStore("1.0.0", conf) - if err != nil { - t.Fatalf("failed to create oras store: %v", err) - } - manifestCached := oci.Manifest{ - MediaType: expectedReferenceMediatype, - Config: oci.Descriptor{}, - Layers: []oci.Descriptor{}, - } - manifestCachedBytes, err := json.Marshal(manifestCached) - if err != nil { - t.Fatalf("failed to marshal cached manifest: %v", err) - } - manifestNotCached := oci.Manifest{ - MediaType: wrongReferenceMediatype, - Config: oci.Descriptor{}, - Layers: []oci.Descriptor{}, - } - manifestNotCachedBytes, err := json.Marshal(manifestNotCached) - if err != nil { - t.Fatalf("failed to marshal not cached manifest: %v", err) - } - testRepo := mocks.TestRepository{ - FetchMap: map[digest.Digest]io.ReadCloser{ - artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), +func TestORASGetReferenceManifest(t *testing.T) { + tests := []struct { + name string + inputRef common.Reference + referenceDesc ocispecs.ReferenceDescriptor + repo registry.Repository + repoCreateErr error + localCache content.Storage + expectedErr bool + expectedMediaType string + }{ + { + name: "cache exists failure", + inputRef: common.Reference{ + Original: "inputOriginalPath", + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{}, + }, + localCache: mocks.TestStorage{ + ExistsErr: errors.New("cache exists error"), + }, + expectedErr: true, }, - } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { - return testRepo, nil - } - store.localCache = mocks.TestStorage{ - ExistsMap: map[digest.Digest]io.Reader{ - artifactDigest: bytes.NewReader(manifestCachedBytes), + { + name: "cache fetch manifest failure", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigest: bytes.NewReader(manifestCachedBytes), + }, + FetchErr: errors.New("cache fetch error"), + }, + expectedErr: false, + expectedMediaType: validReferenceMediatype, }, - } - inputRef := common.Reference{ - Original: inputOriginalPath, - Digest: firstDigest, - } - manifest, err := store.GetReferenceManifest(ctx, inputRef, ocispecs.ReferenceDescriptor{ - Descriptor: oci.Descriptor{ - MediaType: ocispecs.MediaTypeArtifactManifest, - Digest: artifactDigest, + { + name: "not cached desc and fetch failed", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{}, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + }, + expectedErr: true, }, - }) - if err != nil { - t.Fatalf("failed to get reference manifest: %v", err) - } - if manifest.MediaType != expectedReferenceMediatype { - t.Fatalf("expected media type %s, got %s", expectedReferenceMediatype, manifest.MediaType) - } -} - -// TestORASGetReferenceManifest_NotCachedDesc tests that the reference manifest is fetched from the registry if it is not cached -func TestORASGetReferenceManifest_NotCachedDesc(t *testing.T) { - conf := config.StorePluginConfig{ - "name": "oras", - } - ctx := context.Background() - firstDigest := digest.FromString("testDigest") - artifactDigest := digest.FromString("testArtifactDigest") - artifactDigestNotCached := digest.FromString("testArtifactDigestNotCached") - expectedReferenceMediatype := "application/vnd.oci.image.manifest.right.v1+json" - wrongReferenceMediatype := "application/vnd.oci.image.manifest.wrong.v1+json" - store, err := createBaseStore("1.0.0", conf) - if err != nil { - t.Fatalf("failed to create oras store: %v", err) - } - manifestCached := oci.Manifest{ - MediaType: wrongReferenceMediatype, - Config: oci.Descriptor{}, - Layers: []oci.Descriptor{}, - } - manifestCachedBytes, err := json.Marshal(manifestCached) - if err != nil { - t.Fatalf("failed to marshal cached manifest: %v", err) - } - manifestNotCached := oci.Manifest{ - MediaType: expectedReferenceMediatype, - Config: oci.Descriptor{}, - Layers: []oci.Descriptor{}, - } - manifestNotCachedBytes, err := json.Marshal(manifestNotCached) - if err != nil { - t.Fatalf("failed to marshal not cached manifest: %v", err) - } - testRepo := mocks.TestRepository{ - FetchMap: map[digest.Digest]io.ReadCloser{ - artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), + { + name: "create repository failed", + inputRef: common.Reference{}, + referenceDesc: ocispecs.ReferenceDescriptor{}, + repo: mocks.TestRepository{}, + repoCreateErr: errors.New("create repository error"), + expectedErr: true, }, - } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { - return testRepo, nil - } - store.localCache = mocks.TestStorage{ - ExistsMap: map[digest.Digest]io.Reader{ - artifactDigestNotCached: bytes.NewReader(manifestCachedBytes), + { + name: "reference manifest is returned from the cache if it exists", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigest: bytes.NewReader(manifestCachedBytes), + }, + }, + expectedErr: false, + expectedMediaType: validReferenceMediatype, }, - } - inputRef := common.Reference{ - Original: inputOriginalPath, - Digest: firstDigest, - } - manifest, err := store.GetReferenceManifest(ctx, inputRef, ocispecs.ReferenceDescriptor{ - Descriptor: oci.Descriptor{ - MediaType: ocispecs.MediaTypeArtifactManifest, - Digest: artifactDigest, + { + name: "reference manifest is fetched from the registry if it is not cached", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + }, + expectedErr: false, + expectedMediaType: validReferenceMediatype, + }, + { + name: "descriptor not cached and fail during io.ReadAll from manifest", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: &errorReader{}, + }, + }, + localCache: mocks.TestStorage{ + FetchErr: errors.New("cache fetch error"), + }, + expectedErr: true, + }, + { + name: "failed to unmarshal to oci manifest", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: oci.MediaTypeImageManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(invalidManifestBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + }, + expectedErr: true, + }, + { + name: "failed to unmarshal to artifact manifest", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(invalidManifestBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + }, + expectedErr: true, + }, + { + name: "unsupported manifest media type", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: "unsupported media type", + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(invalidManifestBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + }, + expectedErr: true, + }, + { + name: "failed to push manifest to cache", + inputRef: common.Reference{ + Original: inputOriginalPath, + Digest: firstDigest, + }, + referenceDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + MediaType: ocispecs.MediaTypeArtifactManifest, + Digest: artifactDigest, + }, + }, + repo: mocks.TestRepository{ + FetchMap: map[digest.Digest]io.ReadCloser{ + artifactDigest: io.NopCloser(bytes.NewReader(manifestNotCachedBytes)), + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + artifactDigestNotCached: bytes.NewReader(manifestCachedBytesWithWrongType), + }, + PushErr: errors.New("push content error"), + }, + expectedErr: false, + expectedMediaType: validReferenceMediatype, }, - }) - if err != nil { - t.Fatalf("failed to get reference manifest: %v", err) } - if manifest.MediaType != expectedReferenceMediatype { - t.Fatalf("expected media type %s, got %s", expectedReferenceMediatype, manifest.MediaType) + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + conf := config.StorePluginConfig{ + "name": "oras", + } + store, err := createBaseStore("1.0.0", conf) + if err != nil { + t.Fatalf("failed to create oras store: %v", err) + } + store.createRepository = func(_ context.Context, _ *orasStore, _ common.Reference) (registry.Repository, error) { + return tc.repo, tc.repoCreateErr + } + store.localCache = tc.localCache + + manifest, err := store.GetReferenceManifest(context.Background(), tc.inputRef, tc.referenceDesc) + if tc.expectedErr { + if err == nil { + t.Fatalf("expected error fetching reference manifest") + } + } else { + if err != nil { + t.Fatalf("failed to get reference manifest: %v", err) + } + if manifest.MediaType != tc.expectedMediaType { + t.Fatalf("expected media type %s, got %s", tc.expectedMediaType, manifest.MediaType) + } + } + }) } } -// TestORASGetBlobContent_CachedDesc tests that the blob content is fetched from the cache if it is cached -func TestORASGetBlobContent_CachedDesc(t *testing.T) { - conf := config.StorePluginConfig{ - "name": "oras", - } - ctx := context.Background() - firstDigest := digest.FromString("testDigest") - blobDigest := digest.FromString("testBlobDigest") - expectedContent := []byte("test content") - inputRef := common.Reference{ - Original: inputOriginalPath, - Path: inputOriginalPath, - Digest: firstDigest, - } - store, err := createBaseStore("1.0.0", conf) - if err != nil { - t.Fatalf("failed to create oras store: %v", err) - } - testRepo := mocks.TestRepository{ - BlobStoreTest: mocks.TestBlobStore{ - BlobMap: map[string]mocks.BlobPair{ - fmt.Sprintf("%s@%s", inputRef.Path, blobDigest.String()): { - Descriptor: oci.Descriptor{ - Digest: blobDigest, +func TestORASGetBlobContent(t *testing.T) { + tests := []struct { + name string + repo registry.Repository + localCache content.Storage + repoCreateErr error + subjectReference common.Reference + digest digest.Digest + expectedContent []byte + expectedErr bool + }{ + { + name: "fail to create repository", + repo: mocks.TestRepository{}, + repoCreateErr: errors.New("create repository error"), + expectedErr: true, + }, + { + name: "fail to check blob existence", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: io.NopCloser(bytes.NewReader([]byte("test content"))), + }, }, - Reader: io.NopCloser(bytes.NewReader(expectedContent)), }, }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{}, + ExistsErr: errors.New("check blob existence error"), + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedContent: []byte("test content"), + expectedErr: false, }, - } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { - return testRepo, nil - } - store.localCache = mocks.TestStorage{ - ExistsMap: map[digest.Digest]io.Reader{ - blobDigest: bytes.NewReader(expectedContent), + { + name: "fail to get raw content from cache", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: io.NopCloser(bytes.NewReader([]byte("test content"))), + }, + }, + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + blobDigest: bytes.NewReader([]byte("test content")), + }, + FetchErr: errors.New("fetch blob error"), + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedContent: []byte("test content"), + expectedErr: false, }, - } - content, err := store.GetBlobContent(ctx, inputRef, blobDigest) - if err != nil { - t.Fatalf("failed to get blob content: %v", err) - } - if !bytes.Equal(content, expectedContent) { - t.Fatalf("expected content %s, got %s", expectedContent, content) - } -} - -// TestORASGetBlobContent_NotCachedDesc tests that the blob content is fetched from the registry if it is not cached -func TestORASGetBlobContent_NotCachedDesc(t *testing.T) { - conf := config.StorePluginConfig{ - "name": "oras", - } - ctx := context.Background() - firstDigest := digest.FromString("testDigest") - blobDigest := digest.FromString("testBlobDigest") - expectedContent := []byte("test content") - inputRef := common.Reference{ - Original: inputOriginalPath, - Path: inputOriginalPath, - Digest: firstDigest, - } - store, err := createBaseStore("1.0.0", conf) - if err != nil { - t.Fatalf("failed to create oras store: %v", err) - } - testRepo := mocks.TestRepository{ - BlobStoreTest: mocks.TestBlobStore{ - BlobMap: map[string]mocks.BlobPair{ - fmt.Sprintf("%s@%s", inputRef.Path, blobDigest.String()): { - Descriptor: oci.Descriptor{ - Digest: blobDigest, + { + name: "fail to fetch blob from repository", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{}, + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{}, + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedErr: true, + }, + { + name: "fail to read fetched blob", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: &errorReader{}, + }, }, - Reader: io.NopCloser(bytes.NewReader(expectedContent)), }, }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{}, + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedErr: true, + }, + { + name: "fail to push content to local cache", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: io.NopCloser(bytes.NewReader([]byte("test content"))), + }, + }, + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{}, + PushErr: errors.New("push content error"), + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedContent: []byte("test content"), + expectedErr: false, + }, + { + name: "blob content is fetched from the cache if it is cached", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: io.NopCloser(bytes.NewReader([]byte("test content"))), + }, + }, + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{ + blobDigest: bytes.NewReader([]byte("test content")), + }, + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedContent: []byte("test content"), + expectedErr: false, + }, + { + name: "blob content is fetched from the registry if it is not cached", + repo: mocks.TestRepository{ + BlobStoreTest: mocks.TestBlobStore{ + BlobMap: map[string]mocks.BlobPair{ + fmt.Sprintf("%s@%s", inputOriginalPath, blobDigest.String()): { + Descriptor: oci.Descriptor{ + Digest: blobDigest, + }, + Reader: io.NopCloser(bytes.NewReader([]byte("test content"))), + }, + }, + }, + }, + localCache: mocks.TestStorage{ + ExistsMap: map[digest.Digest]io.Reader{}, + }, + subjectReference: common.Reference{ + Original: inputOriginalPath, + Path: inputOriginalPath, + Digest: firstDigest, + }, + digest: blobDigest, + expectedContent: []byte("test content"), + expectedErr: false, }, } - store.createRepository = func(ctx context.Context, store *orasStore, targetRef common.Reference) (registry.Repository, error) { - return testRepo, nil - } - store.localCache = mocks.TestStorage{ - ExistsMap: map[digest.Digest]io.Reader{}, - } - content, err := store.GetBlobContent(ctx, inputRef, blobDigest) - if err != nil { - t.Fatalf("failed to get blob content: %v", err) - } - if !bytes.Equal(content, expectedContent) { - t.Fatalf("expected content %s, got %s", expectedContent, content) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conf := config.StorePluginConfig{ + "name": "oras", + } + store, err := createBaseStore("1.0.0", conf) + if err != nil { + t.Fatalf("failed to create oras store: %v", err) + } + store.createRepository = func(_ context.Context, _ *orasStore, _ common.Reference) (registry.Repository, error) { + return tt.repo, tt.repoCreateErr + } + store.localCache = tt.localCache + content, err := store.GetBlobContent(context.Background(), tt.subjectReference, tt.digest) + if tt.expectedErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + } else { + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if !bytes.Equal(content, tt.expectedContent) { + t.Fatalf("expected content %s, got %s", tt.expectedContent, content) + } + } + }) } } diff --git a/pkg/referrerstore/oras/utils.go b/pkg/referrerstore/oras/utils.go index 396044a87..1965b463b 100644 --- a/pkg/referrerstore/oras/utils.go +++ b/pkg/referrerstore/oras/utils.go @@ -19,8 +19,8 @@ import ( "regexp" "strings" - "github.com/deislabs/ratify/pkg/ocispecs" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/ocispecs" ) // Detect the loopback IP (127.0.0.1) diff --git a/pkg/referrerstore/oras/utils_test.go b/pkg/referrerstore/oras/utils_test.go index c54046e4a..74d14358b 100644 --- a/pkg/referrerstore/oras/utils_test.go +++ b/pkg/referrerstore/oras/utils_test.go @@ -18,9 +18,9 @@ package oras import ( "testing" - "github.com/deislabs/ratify/pkg/ocispecs" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/ocispecs" ) func TestIsInsecureRegistry(t *testing.T) { diff --git a/pkg/referrerstore/plugin/args.go b/pkg/referrerstore/plugin/args.go index 9bdaa04d6..18dfc68fa 100644 --- a/pkg/referrerstore/plugin/args.go +++ b/pkg/referrerstore/plugin/args.go @@ -19,7 +19,7 @@ import ( "fmt" "os" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" ) // ReferrerStorePluginArgs describes all arguments that are passed when a store plugin is invoked diff --git a/pkg/referrerstore/plugin/plugin.go b/pkg/referrerstore/plugin/plugin.go index 8f9e995b7..8f76590f8 100644 --- a/pkg/referrerstore/plugin/plugin.go +++ b/pkg/referrerstore/plugin/plugin.go @@ -22,13 +22,13 @@ import ( "os" "strings" - "github.com/deislabs/ratify/pkg/common" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/types" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/common" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/types" ) // StorePlugin describes a store that is implemented by invoking the plugins diff --git a/pkg/referrerstore/plugin/plugin_test.go b/pkg/referrerstore/plugin/plugin_test.go index 40223e52b..1c92df988 100644 --- a/pkg/referrerstore/plugin/plugin_test.go +++ b/pkg/referrerstore/plugin/plugin_test.go @@ -22,9 +22,9 @@ import ( "strings" "testing" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" ) const ( @@ -46,10 +46,10 @@ func (e *TestExecutor) FindInPaths(plugin string, paths []string) (string, error } func TestPluginMain_GetBlobContent_InvokeExpected(t *testing.T) { testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } @@ -124,10 +124,10 @@ func TestPluginMain_GetBlobContent_InvokeExpected(t *testing.T) { func TestPluginMain_GetReferenceManifest_InvokeExpected(t *testing.T) { testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } @@ -207,10 +207,10 @@ func TestPluginMain_GetReferenceManifest_InvokeExpected(t *testing.T) { func TestPluginMain_ListReferrers_InvokeExpected(t *testing.T) { testPlugin := "test-plugin" testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } @@ -289,10 +289,10 @@ func TestPluginMain_GetSubjectDescriptor_InvokeExpected(t *testing.T) { testPlugin := "test-plugin" testDigest := digest.FromString("test") testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } diff --git a/pkg/referrerstore/plugin/skel/skel.go b/pkg/referrerstore/plugin/skel/skel.go index 345b2d2ff..39df7f14e 100644 --- a/pkg/referrerstore/plugin/skel/skel.go +++ b/pkg/referrerstore/plugin/skel/skel.go @@ -23,14 +23,14 @@ import ( "os" "strings" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - sp "github.com/deislabs/ratify/pkg/referrerstore/plugin" - "github.com/deislabs/ratify/pkg/referrerstore/types" - "github.com/deislabs/ratify/pkg/utils" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + sp "github.com/ratify-project/ratify/pkg/referrerstore/plugin" + "github.com/ratify-project/ratify/pkg/referrerstore/types" + "github.com/ratify-project/ratify/pkg/utils" ) type pcontext struct { diff --git a/pkg/referrerstore/plugin/skel/skel_test.go b/pkg/referrerstore/plugin/skel/skel_test.go index 3a1e03ed3..29c0d61d0 100644 --- a/pkg/referrerstore/plugin/skel/skel_test.go +++ b/pkg/referrerstore/plugin/skel/skel_test.go @@ -22,14 +22,14 @@ import ( "strings" "testing" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/plugin" - "github.com/deislabs/ratify/pkg/referrerstore/types" - "github.com/deislabs/ratify/pkg/utils" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/plugin" + "github.com/ratify-project/ratify/pkg/referrerstore/types" + "github.com/ratify-project/ratify/pkg/utils" ) const skelPluginName = "skel-test-case" @@ -54,7 +54,7 @@ func teardown() { } func TestPluginMain_GetBlobContent_ReturnsExpected(t *testing.T) { - getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) { + getBlobContent := func(_ *CmdArgs, _ common.Reference, digest digest.Digest) ([]byte, error) { return []byte(digest.String()), nil } environment := map[string]string{ @@ -85,7 +85,7 @@ func TestPluginMain_GetBlobContent_ReturnsExpected(t *testing.T) { } func TestPluginMain_GetReferenceManifest_ReturnsExpected(t *testing.T) { - getReferenceManifest := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) (ocispecs.ReferenceManifest, error) { + getReferenceManifest := func(_ *CmdArgs, _ common.Reference, _ digest.Digest) (ocispecs.ReferenceManifest, error) { return ocispecs.ReferenceManifest{ ArtifactType: "test-type", }, nil @@ -120,7 +120,7 @@ func TestPluginMain_GetReferenceManifest_ReturnsExpected(t *testing.T) { } func TestPluginMain_ListReferrers_ReturnsExpected(t *testing.T) { - listReferrers := func(args *CmdArgs, subjectReference common.Reference, artifactTypes []string, nextToken string, subjectDesc *ocispecs.SubjectDescriptor) (*referrerstore.ListReferrersResult, error) { + listReferrers := func(_ *CmdArgs, _ common.Reference, _ []string, _ string, _ *ocispecs.SubjectDescriptor) (*referrerstore.ListReferrersResult, error) { return &referrerstore.ListReferrersResult{ NextToken: "next-token", Referrers: []ocispecs.ReferenceDescriptor{ @@ -161,7 +161,7 @@ func TestPluginMain_ListReferrers_ReturnsExpected(t *testing.T) { func TestPluginMain_GetSubjectDesc_ReturnsExpected(t *testing.T) { testDigest := digest.FromString("test") - getSubjectDesc := func(args *CmdArgs, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) { + getSubjectDesc := func(_ *CmdArgs, _ common.Reference) (*ocispecs.SubjectDescriptor, error) { return &ocispecs.SubjectDescriptor{Descriptor: v1.Descriptor{Digest: testDigest}}, nil } @@ -193,7 +193,7 @@ func TestPluginMain_GetSubjectDesc_ReturnsExpected(t *testing.T) { } func TestPluginMain_ErrorCases(t *testing.T) { - getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) { + getBlobContent := func(_ *CmdArgs, _ common.Reference, _ digest.Digest) ([]byte, error) { return nil, fmt.Errorf("simulated error") } environment := map[string]string{ @@ -266,7 +266,7 @@ func TestPluginMain_ErrorCases(t *testing.T) { } func TestPluginMain_GetBlobContent_ErrorCases(t *testing.T) { - getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) { + getBlobContent := func(_ *CmdArgs, _ common.Reference, digest digest.Digest) ([]byte, error) { return []byte(digest.String()), nil } environment := map[string]string{ @@ -301,7 +301,7 @@ func TestPluginMain_GetBlobContent_ErrorCases(t *testing.T) { } func TestPluginMain_ListReferrers_ErrorCases(t *testing.T) { - listReferrers := func(args *CmdArgs, subjectReference common.Reference, artifactTypes []string, nextToken string, subjectDesc *ocispecs.SubjectDescriptor) (*referrerstore.ListReferrersResult, error) { + listReferrers := func(_ *CmdArgs, _ common.Reference, _ []string, _ string, _ *ocispecs.SubjectDescriptor) (*referrerstore.ListReferrersResult, error) { return &referrerstore.ListReferrersResult{ NextToken: "next-token", Referrers: []ocispecs.ReferenceDescriptor{ diff --git a/pkg/referrerstore/types/types.go b/pkg/referrerstore/types/types.go index e5ea55b71..3953176d9 100644 --- a/pkg/referrerstore/types/types.go +++ b/pkg/referrerstore/types/types.go @@ -19,8 +19,8 @@ import ( "encoding/json" "io" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" ) const ( diff --git a/pkg/referrerstore/utils/utils.go b/pkg/referrerstore/utils/utils.go index 9afd5ed67..dac30f2db 100644 --- a/pkg/referrerstore/utils/utils.go +++ b/pkg/referrerstore/utils/utils.go @@ -18,11 +18,11 @@ package utils import ( "context" - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" ) var logOpt = logger.Option{ diff --git a/pkg/referrerstore/utils/utils_test.go b/pkg/referrerstore/utils/utils_test.go index dc46e0f5b..55fdfa796 100644 --- a/pkg/referrerstore/utils/utils_test.go +++ b/pkg/referrerstore/utils/utils_test.go @@ -19,10 +19,10 @@ import ( "context" "testing" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/mocks" - "github.com/deislabs/ratify/pkg/utils" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/utils" ) func TestResolveSubjectDescriptor_Success(t *testing.T) { diff --git a/pkg/utils/azureauth/authenticationUtils.go b/pkg/utils/azureauth/authenticationUtils.go index 96156577f..e24fb1488 100644 --- a/pkg/utils/azureauth/authenticationUtils.go +++ b/pkg/utils/azureauth/authenticationUtils.go @@ -22,7 +22,7 @@ import ( "time" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential" - "github.com/deislabs/ratify/pkg/metrics" + "github.com/ratify-project/ratify/pkg/metrics" ) // Source: https://github.com/Azure/azure-workload-identity/blob/d126293e3c7c669378b225ad1b1f29cf6af4e56d/examples/msal-go/token_credential.go#L25 diff --git a/pkg/utils/certificateUtil_test.go b/pkg/utils/certificateUtil_test.go index 9db4fa983..aec3d79f6 100644 --- a/pkg/utils/certificateUtil_test.go +++ b/pkg/utils/certificateUtil_test.go @@ -18,7 +18,7 @@ import ( "os" "testing" - "github.com/deislabs/ratify/pkg/homedir" + "github.com/ratify-project/ratify/pkg/homedir" ) const ( diff --git a/pkg/utils/certificateUtils.go b/pkg/utils/certificateUtils.go index df0ed41df..f4945301b 100644 --- a/pkg/utils/certificateUtils.go +++ b/pkg/utils/certificateUtils.go @@ -23,9 +23,9 @@ import ( "path/filepath" - "github.com/deislabs/ratify/pkg/homedir" notationx509 "github.com/notaryproject/notation-core-go/x509" "github.com/pkg/errors" + "github.com/ratify-project/ratify/pkg/homedir" "github.com/sirupsen/logrus" ) diff --git a/pkg/utils/test_utils.go b/pkg/utils/test_utils.go index 4cb400dc9..4ef0fd5ef 100644 --- a/pkg/utils/test_utils.go +++ b/pkg/utils/test_utils.go @@ -16,10 +16,66 @@ limitations under the License. package utils import ( + "context" + "errors" "os" "path/filepath" + + configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" ) +type MockResourceWriter struct { + updateFailed bool +} + +func (w MockResourceWriter) Create(_ context.Context, _ client.Object, _ client.Object, _ ...client.SubResourceCreateOption) error { + return nil +} + +func (w MockResourceWriter) Update(_ context.Context, _ client.Object, _ ...client.SubResourceUpdateOption) error { + if w.updateFailed { + return errors.New("update failed") + } + return nil +} + +func (w MockResourceWriter) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.SubResourcePatchOption) error { + return nil +} + +type MockStatusClient struct { + UpdateFailed bool +} + +func (c MockStatusClient) Status() client.SubResourceWriter { + writer := MockResourceWriter{} + writer.updateFailed = c.UpdateFailed + return writer +} + +func CreateScheme() (*runtime.Scheme, error) { + scheme := runtime.NewScheme() + + b := runtime.SchemeBuilder{ + clientgoscheme.AddToScheme, + configv1beta1.AddToScheme, + } + + if err := b.AddToScheme(scheme); err != nil { + return nil, err + } + + return scheme, nil +} + +func KeyFor(obj client.Object) types.NamespacedName { + return client.ObjectKeyFromObject(obj) +} + func CreatePlugin(pluginName string) (string, error) { tempDir, err := os.MkdirTemp("", "directory") if err != nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 578837f7d..6b90ea07a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -22,10 +22,10 @@ import ( _ "crypto/sha256" // required package for digest.Parse - "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common" "github.com/distribution/reference" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" ) const ( diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 859bb5876..cfba6a8ea 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -20,8 +20,8 @@ import ( "strings" "testing" - "github.com/deislabs/ratify/pkg/common" "github.com/opencontainers/go-digest" + "github.com/ratify-project/ratify/pkg/common" ) const ( diff --git a/pkg/verifier/api.go b/pkg/verifier/api.go index 00f505de6..d24246664 100644 --- a/pkg/verifier/api.go +++ b/pkg/verifier/api.go @@ -18,23 +18,11 @@ package verifier import ( "context" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" ) -// VerifierResult describes the result of verifying a reference manifest for a subject -type VerifierResult struct { //nolint:revive // ignore linter to have unique type name - Subject string `json:"subject,omitempty"` - IsSuccess bool `json:"isSuccess"` - Name string `json:"name,omitempty"` - Type string `json:"type,omitempty"` - Message string `json:"message,omitempty"` - Extensions interface{} `json:"extensions,omitempty"` - NestedResults []VerifierResult `json:"nestedResults,omitempty"` - ArtifactType string `json:"artifactType,omitempty"` -} - // ReferenceVerifier is an interface that defines methods to verify a reference // for a subject by a verifier. type ReferenceVerifier interface { diff --git a/pkg/verifier/config/config.go b/pkg/verifier/config/config.go index 8205d7bec..634ef1fd6 100644 --- a/pkg/verifier/config/config.go +++ b/pkg/verifier/config/config.go @@ -16,8 +16,8 @@ limitations under the License. package config import ( - "github.com/deislabs/ratify/pkg/ocispecs" - rc "github.com/deislabs/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/ocispecs" + rc "github.com/ratify-project/ratify/pkg/referrerstore/config" ) type VerifierConfig map[string]interface{} diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go index 957c2323f..86442859a 100644 --- a/pkg/verifier/cosign/cosign.go +++ b/pkg/verifier/cosign/cosign.go @@ -18,28 +18,37 @@ package cosign import ( "context" "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" "crypto/x509" + "encoding/base64" "encoding/json" "fmt" + "math/big" "os" "path/filepath" "strings" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/utils" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - "github.com/deislabs/ratify/pkg/verifier/factory" - "github.com/deislabs/ratify/pkg/verifier/types" - - re "github.com/deislabs/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/utils" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/factory" + "github.com/ratify-project/ratify/pkg/verifier/types" + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/cryptobyte/asn1" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/opencontainers/go-digest" imgspec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + re "github.com/ratify-project/ratify/errors" "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v2/pkg/cosign" @@ -50,23 +59,46 @@ import ( ) type PluginConfig struct { - Name string `json:"name"` - Type string `json:"type,omitempty"` - ArtifactTypes string `json:"artifactTypes"` - KeyRef string `json:"key,omitempty"` - RekorURL string `json:"rekorURL,omitempty"` - NestedReferences []string `json:"nestedArtifactTypes,omitempty"` + Name string `json:"name"` + Type string `json:"type,omitempty"` + ArtifactTypes string `json:"artifactTypes"` + KeyRef string `json:"key,omitempty"` + RekorURL string `json:"rekorURL,omitempty"` + NestedReferences []string `json:"nestedArtifactTypes,omitempty"` + TrustPolicies []TrustPolicyConfig `json:"trustPolicies,omitempty"` } -type Extension struct { +// LegacyExtension is the structure for the verifier result extensions +// used for backwards compatibility with the legacy cosign verifier +type LegacyExtension struct { SignatureExtension []cosignExtension `json:"signatures,omitempty"` } +// Extension is the structure for the verifier result extensions +// contains a list of signature verification results +// where each entry corresponds to a single signature verified +type Extension struct { + SignatureExtension []cosignExtensionList `json:"signatures,omitempty"` + TrustPolicy string `json:"trustPolicy,omitempty"` +} + +// cosignExtensionList is the structure verifications performed +// per signature found in the image manifest +type cosignExtensionList struct { + Signature string `json:"signature"` + Verifications []cosignExtension `json:"verifications"` +} + +// cosignExtension is the structure for the verification result +// of a single signature found in the image manifest for a +// single public key type cosignExtension struct { - SignatureDigest digest.Digest `json:"signatureDigest"` + SignatureDigest digest.Digest `json:"signatureDigest,omitempty"` IsSuccess bool `json:"isSuccess"` BundleVerified bool `json:"bundleVerified"` Err string `json:"error,omitempty"` + KeyInformation PKKey `json:"keyInformation,omitempty"` + Summary []string `json:"summary,omitempty"` } type cosignVerifier struct { @@ -75,6 +107,9 @@ type cosignVerifier struct { artifactTypes []string nestedReferences []string config *PluginConfig + isLegacy bool + trustPolicies *TrustPolicies + namespace string } type cosignVerifierFactory struct{} @@ -83,7 +118,20 @@ var logOpt = logger.Option{ ComponentType: logger.Verifier, } -const verifierType string = "cosign" +// used for mocking purposes +var getKeyMapOpts = getKeyMapOptsDefault + +const ( + verifierType string = "cosign" + // messages for verificationPerformedMessage. source: https://github.com/sigstore/cosign/blob/d275a272ec0cdf5a4c22d01b891a4d7e20164d71/cmd/cosign/cli/verify/verify.go#L318 + annotationMessage string = "The specified annotations were verified." // TODO: check if message has been updated by upstream cosign cli + claimsMessage string = "The cosign claims were validated." // TODO: check if message has been updated by upstream cosign cli + offlineBundleMessage string = "Existence of the claims in the transparency log was verified offline." // TODO: check if message has been updated by upstream cosign cli + rekorClaimsMessage string = "The claims were present in the transparency log." // TODO: check if message has been updated by upstream cosign cli + rekorSigMessage string = "The signatures were integrated into the transparency log when the certificate was valid." // TODO: check if message has been updated by upstream cosign cli + sigVerifierMessage string = "The signatures were verified against the specified public key." // TODO: check if message has been updated by upstream cosign cli + certVerifierMessage string = "The code-signing certificate was verified using trusted certificate authority certificates." // TODO: check if message has been updated by upstream cosign cli +) // init() registers the cosign verifier with the factory func init() { @@ -95,12 +143,29 @@ func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierC logger.GetLogger(context.Background(), logOpt).Debugf("creating cosign verifier with config %v, namespace '%v'", verifierConfig, namespace) verifierName, hasName := verifierConfig[types.Name].(string) if !hasName { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("missing name in verifier config") + return nil, re.ErrorCodeConfigInvalid.WithDetail("The name field is required in the Cosign Verifier configuration") } config, err := parseVerifierConfig(verifierConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName) + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to create the Cosign Verifier").WithError(err) + } + + // if key or rekorURL is provided, trustPolicies should not be provided + if (config.KeyRef != "" || config.RekorURL != "") && len(config.TrustPolicies) > 0 { + return nil, re.ErrorCodeConfigInvalid.WithDetail("'key' and 'rekorURL' are part of Cosign legacy configuration and cannot be used with `trustPolicies` parameter") + } + + var trustPolicies *TrustPolicies + legacy := true + // if trustPolicies are provided and non-legacy, create the trust policies + if config.KeyRef == "" && config.RekorURL == "" && len(config.TrustPolicies) > 0 { + logger.GetLogger(context.Background(), logOpt).Debugf("legacy cosign verifier configuration not found, creating trust policies") + trustPolicies, err = CreateTrustPolicies(config.TrustPolicies, verifierName) + if err != nil { + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Cosign Verifier").WithError(err) + } + legacy = false } return &cosignVerifier{ @@ -109,6 +174,9 @@ func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierC artifactTypes: strings.Split(config.ArtifactTypes, ","), nestedReferences: config.NestedReferences, config: config, + isLegacy: legacy, + trustPolicies: trustPolicies, + namespace: namespace, }, nil } @@ -132,8 +200,108 @@ func (v *cosignVerifier) CanVerify(_ context.Context, referenceDescriptor ocispe return false } -// Verify verifies the subject reference using the cosign verifier func (v *cosignVerifier) Verify(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (verifier.VerifierResult, error) { + if v.isLegacy { + return v.verifyLegacy(ctx, subjectReference, referenceDescriptor, referrerStore) + } + return v.verifyInternal(ctx, subjectReference, referenceDescriptor, referrerStore) +} + +func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (verifier.VerifierResult, error) { + // get the trust policy for the reference + trustPolicy, err := v.trustPolicies.GetScopedPolicy(subjectReference.Original) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, err), nil + } + logger.GetLogger(ctx, logOpt).Debugf("selected trust policy %s for reference %s", trustPolicy.GetName(), subjectReference.Original) + + // get the map of keys and relevant cosign options for that reference + keysMap, cosignOpts, err := getKeyMapOpts(ctx, trustPolicy, v.namespace) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, err), nil + } + + // get the reference manifest (cosign oci image) + referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to get Cosign signature metadata for %s", referenceDescriptor.Digest)).WithError(err)), nil + } + + // manifest must be an OCI Image + if referenceManifest.MediaType != imgspec.MediaTypeImageManifest { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("The artifact metadata is not an OCI image")), nil + } + + // get the subject image descriptor + subjectDesc, err := referrerStore.GetSubjectDescriptor(ctx, subjectReference) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Cosign signature of the artifact: %+v", subjectReference)).WithError(err)), nil + } + + // create the hash of the subject image descriptor (used as the hashed payload) + subjectDescHash := v1.Hash{ + Algorithm: subjectDesc.Digest.Algorithm().String(), + Hex: subjectDesc.Digest.Hex(), + } + + sigExtensions := make([]cosignExtensionList, 0) + hasValidSignature := false + // check each signature found + for _, blob := range referenceManifest.Blobs { + extensionListEntry := cosignExtensionList{ + Signature: blob.Annotations[static.SignatureAnnotationKey], + Verifications: make([]cosignExtension, 0), + } + // fetch the blob content of the signature from the referrer store + blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeGetBlobContentFailure.WithDetail(fmt.Sprintf("Failed to get Cosign signature with digest %s", blob.Digest)).WithError(err)), nil + } + // convert the blob to a static signature + staticOpts, err := staticLayerOpts(blob) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to parse Cosign signature with digest %s", blob.Digest)).WithError(err)), nil + } + sig, err := static.NewSignature(blobBytes, blob.Annotations[static.SignatureAnnotationKey], staticOpts...) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature").WithError(err)), nil + } + if len(keysMap) > 0 { + // if keys are found, perform verification with keys + var verifications []cosignExtension + verifications, hasValidSignature, err = verifyWithKeys(ctx, keysMap, sig, blob.Annotations[static.SignatureAnnotationKey], blobBytes, staticOpts, &cosignOpts, subjectDescHash) + if err != nil { + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature with keys").WithError(err)), nil + } + extensionListEntry.Verifications = append(extensionListEntry.Verifications, verifications...) + } else { + // if no keys are found, perform keyless verification + var extension cosignExtension + extension, hasValidSignature = verifyKeyless(ctx, sig, &cosignOpts, subjectDescHash) + extensionListEntry.Verifications = append(extensionListEntry.Verifications, extension) + } + sigExtensions = append(sigExtensions, extensionListEntry) + } + + if hasValidSignature { + return verifier.NewVerifierResult( + "", + v.name, + v.verifierType, + "Verification success. Valid signatures found. Please refer to extensions field for verifications performed.", + true, + nil, + Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()}, + ), nil + } + + errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid Cosign signatures found")) + errorResult.Extensions = Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()} + return errorResult, nil +} + +// **LEGACY** This implementation will be removed in Ratify v2.0.0. Verify verifies the subject reference using the cosign verifier. +func (v *cosignVerifier) verifyLegacy(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (verifier.VerifierResult, error) { cosignOpts := &cosign.CheckOpts{ ClaimVerifier: cosign.SimpleClaimVerifier, } @@ -229,17 +397,19 @@ func (v *cosignVerifier) Verify(ctx context.Context, subjectReference common.Ref } if len(signatures) > 0 { - return verifier.VerifierResult{ - Name: v.name, - Type: v.verifierType, - IsSuccess: true, - Message: "cosign verification success. valid signatures found", - Extensions: Extension{SignatureExtension: sigExtensions}, - }, nil + return verifier.NewVerifierResult( + "", + v.name, + v.verifierType, + "Verification success. Valid signatures found", + true, + nil, + LegacyExtension{SignatureExtension: sigExtensions}, + ), nil } errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid signatures found")) - errorResult.Extensions = Extension{SignatureExtension: sigExtensions} + errorResult.Extensions = LegacyExtension{SignatureExtension: sigExtensions} return errorResult, nil } @@ -315,10 +485,198 @@ func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) { // ErrorToVerifyResult returns a verifier result with the error message and isSuccess set to false func errorToVerifyResult(name string, verifierType string, err error) verifier.VerifierResult { - return verifier.VerifierResult{ - IsSuccess: false, - Name: name, - Type: verifierType, - Message: errors.Wrap(err, "cosign verification failed").Error(), + verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail("Failed to validate the Cosign signature").WithError(err) + return verifier.NewVerifierResult( + "", + name, + verifierType, + "", + false, + &verifierErr, + nil, + ) +} + +// decodeASN1Signature decodes the ASN.1 signature to raw signature bytes +func decodeASN1Signature(sig []byte) ([]byte, error) { + // Convert the ASN.1 Sequence to a concatenated r||s byte string + // This logic is based from https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/crypto/ecdsa/ecdsa.go;l=339 + var ( + r, s = &big.Int{}, &big.Int{} + inner cryptobyte.String + ) + + rawSigBytes := sig + input := cryptobyte.String(sig) + if input.ReadASN1(&inner, asn1.SEQUENCE) { + // if ASN.1 sequence is found, parse r and s + if !inner.ReadASN1Integer(r) { + return nil, fmt.Errorf("failed parsing r") + } + if !inner.ReadASN1Integer(s) { + return nil, fmt.Errorf("failed parsing s") + } + if !inner.Empty() { + return nil, fmt.Errorf("failed parsing signature") + } + rawSigBytes = []byte{} + rawSigBytes = append(rawSigBytes, r.Bytes()...) + rawSigBytes = append(rawSigBytes, s.Bytes()...) + } + + return rawSigBytes, nil +} + +// verifyWithKeys verifies the signature with the keys map and returns the verification results +func verifyWithKeys(ctx context.Context, keysMap map[PKKey]keymanagementprovider.PublicKey, sig oci.Signature, sigEncoded string, payload []byte, staticOpts []static.Option, cosignOpts *cosign.CheckOpts, subjectDescHash v1.Hash) ([]cosignExtension, bool, error) { + // check each key in the map of keys returned by the trust policy + var err error + verifications := make([]cosignExtension, 0) + hasValidSignature := false + for mapKey, pubKey := range keysMap { + hashType := crypto.SHA256 + // default hash type is SHA256 but for AKV scenarios, the hash type is determined by the key size + // TODO: investigate if it's possible to extract hash type from sig directly. This is a workaround for now + if pubKey.ProviderType == azurekeyvault.ProviderName { + hashType, sig, err = processAKVSignature(sigEncoded, sig, pubKey.Key, payload, staticOpts) + if err != nil { + return verifications, false, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature generated by Azure Key Vault").WithError(err) + } + } + + // return the correct verifier based on public key type and bytes + verifier, err := signature.LoadVerifier(pubKey.Key, hashType) + if err != nil { + return verifications, false, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to load public key from provider [%s] name [%s] version [%s]", mapKey.Provider, mapKey.Name, mapKey.Version)).WithError(err) + } + cosignOpts.SigVerifier = verifier + // verify signature with cosign options + perform bundle verification + bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, cosignOpts) + extension := cosignExtension{ + IsSuccess: true, + BundleVerified: bundleVerified, + KeyInformation: mapKey, + } + if err != nil { + extension.IsSuccess = false + extension.Err = err.Error() + } else { + extension.Summary = verificationPerformedMessage(bundleVerified, cosignOpts) + hasValidSignature = true + } + verifications = append(verifications, extension) + } + return verifications, hasValidSignature, nil +} + +// verifyKeyless performs keyless verification and returns the verification results +func verifyKeyless(ctx context.Context, sig oci.Signature, cosignOpts *cosign.CheckOpts, subjectDescHash v1.Hash) (cosignExtension, bool) { + // verify signature with cosign options + perform bundle verification + hasValidSignature := false + bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, cosignOpts) + extension := cosignExtension{ + IsSuccess: true, + BundleVerified: bundleVerified, + } + if err != nil { + extension.IsSuccess = false + extension.Err = err.Error() + } else { + extension.Summary = verificationPerformedMessage(bundleVerified, cosignOpts) + hasValidSignature = true + } + return extension, hasValidSignature +} + +// getKeyMapOptsDefault returns the map of keys and cosign options for the reference +func getKeyMapOptsDefault(ctx context.Context, trustPolicy TrustPolicy, namespace string) (map[PKKey]keymanagementprovider.PublicKey, cosign.CheckOpts, error) { + // get the map of keys for that reference + keysMap, err := trustPolicy.GetKeys(ctx, namespace) + if err != nil { + return nil, cosign.CheckOpts{}, err + } + + // get the cosign options for that trust policy + cosignOpts, err := trustPolicy.GetCosignOpts(ctx) + if err != nil { + return nil, cosign.CheckOpts{}, err + } + + return keysMap, cosignOpts, nil +} + +// processAKVSignature processes the AKV signature and returns the hash type, signature and error +func processAKVSignature(sigEncoded string, staticSig oci.Signature, publicKey crypto.PublicKey, payloadBytes []byte, staticOpts []static.Option) (crypto.Hash, oci.Signature, error) { + var hashType crypto.Hash + switch keyType := publicKey.(type) { + case *rsa.PublicKey: + switch keyType.Size() { + case 256: + hashType = crypto.SHA256 + case 384: + hashType = crypto.SHA384 + case 512: + hashType = crypto.SHA512 + default: + return crypto.SHA256, nil, fmt.Errorf("RSA key check: unsupported key size: %d", keyType.Size()) + } + + // TODO: remove section after fix for bug in cosign azure key vault implementation + // tracking issue: https://github.com/sigstore/sigstore/issues/1384 + // summary: azure keyvault implementation ASN.1 encodes sig after online signing with keyvault + // EC verifiers in cosign have built in ASN.1 decoding, but RSA verifiers do not + base64DecodedBytes, err := base64.StdEncoding.DecodeString(sigEncoded) + if err != nil { + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to decode base64 signature").WithError(err) + } + // decode ASN.1 signature to raw signature if it is ASN.1 encoded + decodedSigBytes, err := decodeASN1Signature(base64DecodedBytes) + if err != nil { + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to decode ASN.1 signature").WithError(err) + } + encodedBase64SigBytes := base64.StdEncoding.EncodeToString(decodedSigBytes) + staticSig, err = static.NewSignature(payloadBytes, encodedBase64SigBytes, staticOpts...) + if err != nil { + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to generate static signature").WithError(err) + } + case *ecdsa.PublicKey: + switch keyType.Curve { + case elliptic.P256(): + hashType = crypto.SHA256 + case elliptic.P384(): + hashType = crypto.SHA384 + case elliptic.P521(): + hashType = crypto.SHA512 + default: + return crypto.SHA256, nil, fmt.Errorf("ECDSA key check: unsupported key curve [%s]", keyType.Params().Name) + } + default: + return crypto.SHA256, nil, fmt.Errorf("unsupported public key type [%T]", publicKey) + } + return hashType, staticSig, nil +} + +// verificationPerformedMessage returns a string list of all verifications performed +// based on https://github.com/sigstore/cosign/blob/5ae2e31c30ee87e035cc57ebbbe2ecf3b6549ff5/cmd/cosign/cli/verify/verify.go#L318 +func verificationPerformedMessage(bundleVerified bool, co *cosign.CheckOpts) []string { + var messages []string + if co.ClaimVerifier != nil { + if co.Annotations != nil { + messages = append(messages, annotationMessage) + } + messages = append(messages, claimsMessage) + } + if bundleVerified { + messages = append(messages, offlineBundleMessage) + } else if co.RekorClient != nil { + messages = append(messages, rekorClaimsMessage) + messages = append(messages, rekorSigMessage) + } + // if no SigVerifier is provided, fulcio root certs are assumed to be used (keyless) + if co.SigVerifier != nil { + messages = append(messages, sigVerifierMessage) + } else { + messages = append(messages, certVerifierMessage) } + return messages } diff --git a/pkg/verifier/cosign/cosign_test.go b/pkg/verifier/cosign/cosign_test.go index 5cb6903a2..aea1cf5a7 100644 --- a/pkg/verifier/cosign/cosign_test.go +++ b/pkg/verifier/cosign/cosign_test.go @@ -17,15 +17,50 @@ package cosign import ( "context" + "crypto" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/base64" "fmt" + "io" + "slices" + "strings" "testing" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/verifier/config" + "github.com/opencontainers/go-digest" imgspec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci/static" + "github.com/sigstore/rekor/pkg/generated/client" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" ) +const ( + ratifySampleImageRef string = "ghcr.io/ratify-project/ratify:v1" + testIdentity string = "sozercan@gmail.com" + testIssuer string = "https://github.com/login/oauth" +) + +type mockNoOpVerifier struct{} + +func (m *mockNoOpVerifier) PublicKey(_ ...signature.PublicKeyOption) (crypto.PublicKey, error) { + return nil, nil +} + +func (m *mockNoOpVerifier) VerifySignature(_, _ io.Reader, _ ...signature.VerifyOption) error { + return nil +} + // TestCreate tests the Create function of the cosign verifier func TestCreate(t *testing.T) { tests := []struct { @@ -38,6 +73,22 @@ func TestCreate(t *testing.T) { config: config.VerifierConfig{ "name": "test", "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, + }, + wantErr: false, + }, + { + name: "valid legacy config", + config: config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "key": "testkey_path", }, wantErr: false, }, @@ -55,6 +106,51 @@ func TestCreate(t *testing.T) { }, wantErr: true, }, + { + name: "valid trust policies config with no legacy config or trust policies", + config: config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{}, + }, + wantErr: false, + }, + { + name: "duplicate trust policies in config", + config: config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, + }, + wantErr: true, + }, + { + name: "invalid config with legacy and trust policies", + config: config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, + "key": "testkey_path", + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -75,6 +171,13 @@ func TestName(t *testing.T) { validConfig := config.VerifierConfig{ "name": name, "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, } cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace") if err != nil { @@ -92,6 +195,13 @@ func TestType(t *testing.T) { validConfig := config.VerifierConfig{ "name": "test", "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, } cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace") if err != nil { @@ -138,6 +248,13 @@ func TestCanVerify(t *testing.T) { validConfig := config.VerifierConfig{ "name": "test", "artifactTypes": tt.artifactTypes, + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, } cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace") if err != nil { @@ -155,8 +272,15 @@ func TestCanVerify(t *testing.T) { func TestGetNestedReferences(t *testing.T) { verifierFactory := cosignVerifierFactory{} validConfig := config.VerifierConfig{ - "name": "test", - "artifactTypes": "testtype", + "name": "test", + "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, "nestedArtifactTypes": []string{"nested-artifact-type"}, } cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace") @@ -304,7 +428,725 @@ func TestErrorToVerifyResult(t *testing.T) { if verifierResult.Type != "cosign" { t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Type, "cosign") } - if verifierResult.Message != "cosign verification failed: test error" { - t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "cosign verification failed: test error") + if verifierResult.Message != "Failed to validate the Cosign signature" { + t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "Failed to validate the Cosign signature") + } + if verifierResult.ErrorReason != "test error" { + t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.ErrorReason, "test error") + } +} + +// TestDecodeASN1Signature tests the decodeASN1Signature function +func TestDecodeASN1Signature(t *testing.T) { + tc := []struct { + name string + sigBytes []byte + expectedSigBytes []byte + wantErr bool + }{ + { + name: "invalid not asn1", + sigBytes: []byte("test"), + expectedSigBytes: []byte("test"), + wantErr: false, + }, + { + name: "valid asn1", + sigBytes: []byte("0E\x02!\x00\xb4\xd7R\xf0\xee\x11ձ\x9f\rtsog\x99\xa1\x86L=\x04\x92\u07b8\xb7\xa1\x94Mj\xfe\xd2\xda\x02\x02 \x027|~q\xcb2\xaf\xd1\xddx;\x04\xed\r\x9a\x9a\x03\xa9\x84\x8cu\xba\x1a\x9eFb\xc2h\x7fk\xc3"), + expectedSigBytes: []byte("\xb4\xd7R\xf0\xee\x11ձ\x9f\rtsog\x99\xa1\x86L=\x04\x92\u07b8\xb7\xa1\x94Mj\xfe\xd2\xda\x02\x027|~q\xcb2\xaf\xd1\xddx;\x04\xed\r\x9a\x9a\x03\xa9\x84\x8cu\xba\x1a\x9eFb\xc2h\x7fk\xc3"), + wantErr: false, + }, + { + name: "invalid r", + sigBytes: []byte("0E\x03!\x00\xb4\xd7R\xf0\xee\x11ձ\x9f\rtsog\x99\xa1\x86L=\x04\x92\u07b8\xb7\xa1\x94Mj\xfe\xd2\xda\x02\x02 \x027|~q\xcb2\xaf\xd1\xddx;\x04\xed\r\x9a\x9a\x03\xa9\x84\x8cu\xba\x1a\x9eFb\xc2h\x7fk\xc3"), + expectedSigBytes: nil, + wantErr: true, + }, + { + name: "invalid s", + sigBytes: []byte("0E\x02!\x00\xb4\xd7R\xf0\xee\x11ձ\x9f\rtsog\x99\xa1\x86L=\x04\x92\u07b8\xb7\xa1\x94Mj\xfe\xd2\xda\x02\x03 \x027|~q\xcb2\xaf\xd1\xddx;\x04\xed\r\x9a\x9a\x03\xa9\x84\x8cu\xba\x1a\x9eFb\xc2h\x7fk\xc3"), + expectedSigBytes: nil, + wantErr: true, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + sigBytes, err := decodeASN1Signature(tt.sigBytes) + if (err != nil) != tt.wantErr { + t.Errorf("decodeASN1Signature() error = %v, wantErr %v", err, tt.wantErr) + } + if sigBytes != nil && !slices.Equal(tt.expectedSigBytes, sigBytes) { + t.Errorf("decodeASN1Signature() = %v, want %v", sigBytes, tt.expectedSigBytes) + } + }) + } +} + +func TestGetKeysMaps_Success(t *testing.T) { + trustPolicy := &mockTrustPolicy{} + _, _, err := getKeyMapOptsDefault(context.Background(), trustPolicy, "gatekeeper-system") + if err != nil { + t.Errorf("getKeysMaps() error = %v, wantErr %v", err, false) + } +} + +func TestGetKeysMaps_FailingCosignOpts(t *testing.T) { + trustPolicy := &mockTrustPolicy{shouldErrCosignOpts: true} + _, _, err := getKeyMapOptsDefault(context.Background(), trustPolicy, "gatekeeper-system") + if err == nil { + t.Errorf("getKeysMaps() error = %v, wantErr %v", err, true) + } +} + +func TestGetKeysMaps_FailingGetKeys(t *testing.T) { + trustPolicy := &mockTrustPolicy{shouldErrKeys: true} + _, _, err := getKeyMapOptsDefault(context.Background(), trustPolicy, "gatekeeper-system") + if err == nil { + t.Errorf("getKeysMaps() error = %v, wantErr %v", err, true) + } +} + +// TestVerifyInternal tests the verifyInternal function of the cosign verifier +// it also tests the processAKVSignature function implicitly +func TestVerifyInternal(t *testing.T) { + cosignMediaType := "application/vnd.dev.cosign.simplesigning.v1+json" + validSignatureBlob := []byte("test") + subjectDigest := digest.Digest("sha256:5678") + testRefDigest := digest.Digest("sha256:1234") + blobDigest := digest.Digest("valid blob") + //nolint:gosec // this is a test key + invalidRsaPrivKey, _ := rsa.GenerateKey(rand.Reader, 1024) + invalidRsaPubKey := invalidRsaPrivKey.Public() + invalidECPrivKey, _ := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + invalidECPubKey := invalidECPrivKey.Public() + + validRSA256PubKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(`-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtHGXFzi1W93vQ88EwzmI +IhTXMYpcffQmmHYLgjkeLWL4SQ7DJEyq4j+Yz994lq0B4nCT9EaLkXYSMZhfYuHg +y+2kMkh+1eNUtjGVJBkHc5iz7YR9OaIDlY36TnKlk0HfyBrjNrlwyodD6no/2LCf +6FmGT6mVIaE/fyrxN3ZCHzfcw5LaGgHRt+91NJa5PnQCxjXUfyabHbHehgNLjjpn +kwCW3GGs56cOMQowHsLrlwQnXAq5wvAueRz3Ommz+DPnVXUSV+vfYDt56oggX386 +LOe8VCiwi4T9IIuWlKIi+AuIm8aQ+11o9LjpvDqFD1rJU/KMFhczA4Kj0fRM7Ulg +ewIDAQAB +-----END PUBLIC KEY-----`)) + if err != nil { + t.Fatalf("error creating RSA public key: %v", err) + } + + validRSA384PubKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(`-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArccnwmvTOS0iqaxiCRsD +4vixdUy2az39vL3iABjLr6Ht2NLA8dyO0NeEBb6+lfoqOl8RX29rJ0LnCaQL/wV8 +BQ3idfzdeX/rzdhRoegDiZ7MDgd01ZDeocGSfOAKJ3E1Kr0+etB4UuOF2T7dcVNn +lAtZxEH6wNtW2HFoLg6bnlUuSj+9RVaP2Z0D55Bk4Un1jinB6Et81SCIuvDcMbKt +aW3Xu17mdHiscLQBOnmX86mKRP8R4Ij9TtNyEW/9WLNXHV1iJhm9TVONZkX2hRjy +o3+pPYvsZAAjyIk4AHF4BROCMA+WmyqkjnyVdEcJBi6f8NptjnS8A5jtTXIrndEq +OE1VTu44z8hcQqrIypdyF86rMsJm91F8x68clvSYyvYn15lzv720LOglFF2NrD8S +0SmxbyPB4bnRhEiyxh9ocAbGVXu+FyjrLPjTCTTnIpnTzgm/XtSqjA6104Zz3Seu +TvvdnkTLbUxqHzoFYXSksJHvOiH2U7JAay8S4CZ4KrGvAgMBAAE= +-----END PUBLIC KEY-----`)) + if err != nil { + t.Fatalf("error creating RSA public key: %v", err) + } + + validECDSAP256PubKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(`-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1ljPT4AO3Ny57S2B1a5P2LSrru5l +ewt8iyi46g8SRrasghTR8xliLiZJl3GTM3UOdUAZCiruwPC7hihLD5JcqQ== +-----END PUBLIC KEY-----`)) + if err != nil { + t.Fatalf("error creating RSA public key: %v", err) + } + + validECDSAP384PubKey, err := cryptoutils.UnmarshalPEMToPublicKey([]byte(`-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFbJSMiBAtIiydUeqhMGpZBDRkZhYFu5r +zg5rpyR7WJVgDPH8++2IY9Zg1HYKB0aGqyuLK5i8bG3C8avDLXg2+Dmf35wV2Mgh +mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry +-----END PUBLIC KEY----- + `)) + if err != nil { + t.Fatalf("error creating RSA public key: %v", err) + } + + subjectRef := common.Reference{ + Digest: subjectDigest, + Original: ratifySampleImageRef, + Tag: "v1", + } + refDescriptor := ocispecs.ReferenceDescriptor{ + ArtifactType: "testtype", + Descriptor: imgspec.Descriptor{ + Digest: testRefDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + } + tc := []struct { + name string + keys map[PKKey]keymanagementprovider.PublicKey + getKeysError bool + cosignOpts cosign.CheckOpts + store *mocks.MemoryTestStore + expectedResultMessagePrefix string + expectedErrorReason string + defaultCosignOpts bool + }{ + { + name: "get keys error", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + getKeysError: true, + store: &mocks.MemoryTestStore{}, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "error", + }, + { + name: "manifest fetch error", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + getKeysError: false, + store: &mocks.MemoryTestStore{}, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "manifest not found", + }, + { + name: "incorrect reference manifest media type error", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: "invalid", + }, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "The artifact metadata is not an OCI image", + }, + { + name: "failed subject descriptor fetch", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + }, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "subject not found for sha256:5678", + }, + { + name: "failed to fetch blob", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: digest.Digest("nonexistent blob hash"), + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "blob not found", + }, + { + name: "invalid key type for AKV", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: &ecdh.PublicKey{}, ProviderType: azurekeyvault.ProviderName}, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: blobDigest, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + blobDigest: validSignatureBlob, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "unsupported public key type [*ecdh.PublicKey]", + }, + { + name: "invalid RSA key size for AKV", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: invalidRsaPubKey, ProviderType: azurekeyvault.ProviderName}, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: blobDigest, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + blobDigest: validSignatureBlob, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "RSA key check: unsupported key size: 128", + }, + { + name: "invalid ECDSA curve type for AKV", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: invalidECPubKey, ProviderType: azurekeyvault.ProviderName}, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: blobDigest, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + blobDigest: validSignatureBlob, + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "ECDSA key check: unsupported key curve [P-224]", + }, + { + name: "valid hash: 256 keysize: 2048 RSA key AKV", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: validRSA256PubKey, ProviderType: azurekeyvault.ProviderName}, + }, + cosignOpts: cosign.CheckOpts{ + IgnoreSCT: true, + IgnoreTlog: true, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Size: 267, + Digest: "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70", + MediaType: cosignMediaType, + Annotations: map[string]string{ + static.SignatureAnnotationKey: "j6VNQ+Z3BqLeM75WM8WKnJqtwR7Kv21BwURHLmK6S05gCV/JntSbVthNVKoNY3906NMqmfZDlP/QuUOQt7Fxq2ivixw1xKa1KlE+ydW951GyMysaZx36U08Wmfyqt6dbgXMU6/nQE8oxG855rfywvE+MAmIJ+u1ktPbU+HoXEPP8yNUyK4gY/JAopQVEcktGAqFAbT49LzlE3FTJQNE6WryCQy5GiaM/1qdKzQi9GQb2g20Vxg6+e4AuxogAs+bzexoA4J5bUkDAkE/PDIXNz2EgjB0o7zK1NQEDiLNRq7fafTY5G56vXtltuMWOzCGnLMXbk4f3K9wKXF++7h4I3w==", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Verification success", + }, + { + name: "valid hash: 256 keysize: 3072 RSA key", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: validRSA384PubKey}, + }, + cosignOpts: cosign.CheckOpts{ + IgnoreSCT: true, + IgnoreTlog: true, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Size: 267, + Digest: "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70", + MediaType: cosignMediaType, + Annotations: map[string]string{ + static.SignatureAnnotationKey: "fP5+FQcc59WjqDAcvcgfHBZbu/FfQYh+ZjgwuEwLj/y0ku2S+rFbk8XE2gPZ4mcgT9Bceu+UMY/pYLqNI7ngkXMamYg1gzsTPrAG5DpEbApGMDiQyOlCcEFqgJbxqFOmg+HD9eSOMmibFbUh8XMt4LuyZIjmcCqJ22i8B49y8LFo6QiE64/jjhNLlRK4LvDTSUGDJ4VXW+c9y/PxbpZxtHIVyIYK82qL8P2/BuRxQ9ZVKJE1eFdz3Suz0ZIQmhkimLqQdOOxoGFcO4syjHYzfneBNvySWNxVXJCjw86DJqsDl5se+mY2Zww13DihfQX0cKSGGVfRoMgvIQOeaMNyFaCad2BQFfraqVUU5p7v0FqO6r0FU9z0ixRj81xVKJA3GPUZdF1ImcwOE4cOuQYARE6aiw78t2vrW5PRGtRPWpu+JY1+2v5m61w60G9HAozpnucWG3u9agdhwwD6VLJzPduVdnZr8t1WN8BpZs5NA3n4wkrlmRpnYtw7MqupaJQ2", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Verification success", + }, + { + name: "valid hash: 256 curve: P256 ECDSA key AKV", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: validECDSAP256PubKey, ProviderType: azurekeyvault.ProviderName}, + }, + cosignOpts: cosign.CheckOpts{ + IgnoreSCT: true, + IgnoreTlog: true, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Size: 267, + Digest: "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70", + MediaType: cosignMediaType, + Annotations: map[string]string{ + static.SignatureAnnotationKey: "MEYCIQDCMOtZXzsgZknsOhcv1VC7cN72xuBr16GU98bT0tXWdQIhAJp9X9jh4sIG1xhmoaYwGGkl1/8EQW7zqFUpMkEoi3s1", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Verification success", + }, + { + name: "valid hash: 256 curve: P384 ECDSA key", + keys: map[PKKey]keymanagementprovider.PublicKey{ + {Provider: "test"}: {Key: validECDSAP384PubKey}, + }, + cosignOpts: cosign.CheckOpts{ + IgnoreSCT: true, + IgnoreTlog: true, + }, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Size: 267, + Digest: "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70", + MediaType: cosignMediaType, + Annotations: map[string]string{ + static.SignatureAnnotationKey: "MGUCMQC6Z7RgD2uxG5IiqKoOmrjTRVqBn+XqSjHU5oSI/RNAl9FBrM5HuzZm6cMmlp40jIoCMHKeH42xtJBTOPzbkG/z9aWaNagjn8jEFKWB28w4hjufN6NG1QReF2ai7befjTnRmg==", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: subjectDigest, + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Verification success", + }, + { + name: "successful keyless verification", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + defaultCosignOpts: true, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: digest.NewDigestFromEncoded(digest.SHA256, "d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6"), + Annotations: map[string]string{ + "dev.cosignproject.cosign/signature": "MEUCIFBlKbxxg1Ni++g99jeWO8Of3g5L0Xd+qMzdqCZySQ8DAiEA3lcOJPJ1FQOahtWaRU0hG0XxFEsbcVx6SIyzYQMMR0A=", + "dev.sigstore.cosign/bundle": "{\"SignedEntryTimestamp\":\"MEUCIAIZfWhm9x2F7wil5dkWX+0+njT+FWXFr8AskDkiHpzoAiEApDk9STKcBJTkQ4qy9/8gn6ea2wduh3UjbLRnzZQa9gU=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkMTIyNmUzNmJjODUwMjk3ODMyNGNiMmNiMjExNmM2YWE0OGVkYjJlYThmMTViMWM2ZjZmMjU2ZWQ0MzM4OGY2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRkJsS2J4eGcxTmkrK2c5OWplV084T2YzZzVMMFhkK3FNemRxQ1p5U1E4REFpRUEzbGNPSlBKMUZRT2FodFdhUlUwaEcwWHhGRXNiY1Z4NlNJeXpZUU1NUjBBPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnZSRU5EUVdsaFowRjNTVUpCWjBsVlVsWjFTa3A2T1VneWJGUldWMDgyTjFCdWMyZDBUWFJUYld4TmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDFxUlRKTlJGVjVUWHBCTUZkb1kwNU5hazEzVFdwRk1rMUVWWHBOZWtFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVUzWlV0MU9IQnJOMmN5TDBsU2FGSjVNbEF2TWtoamMxTkNZMWcyUWxwb1QwTkpjMndLU0RBMVFWaHhTelZsUzBKR1R6QmxUU3RvU0hGeGFXbHRZVFJVYm5kNll6RnpUMjkwT0hSVVJuYzVlVVJFYlhod1RrdFBRMEZWVlhkblowWkNUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZNYVZWRUNub3hSRzV2YURsVlRXZHBiMDh4ZEZsdU1IYzFTVUpWZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBsQldVUldVakJTUVZGSUwwSkNXWGRHU1VWVFl6STVObHBZU21wWlZ6VkJXakl4YUdGWGQzVlpNamwwVFVOM1IwTnBjMGRCVVZGQ1p6YzRkd3BCVVVWRlNHMW9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWMySXlaSEJpYVRsMldWaFdNR0ZFUTBKcFVWbExTM2RaUWtKQlNGZGxVVWxGQ2tGblVqZENTR3RCWkhkQ01VRk9NRGxOUjNKSGVIaEZlVmw0YTJWSVNteHVUbmRMYVZOc05qUXphbmwwTHpSbFMyTnZRWFpMWlRaUFFVRkJRbWhzYVhRS1IweFZRVUZCVVVSQlJWbDNVa0ZKWjA5T2EyUndTSGx1Ykc4eWRFOXZZbkJ1Y2tSWFQwSTJTM2x3Y1d0V2RuUlZiVVpLSzFKVFZVZ3JTREJEU1VWTlNBcDBURFp0Y25nemVUTmxWV3R3ZGpJM2JsRk1VbFJhZDFkeVJuSTROR2QxUXpCNFVYZHdkVmxxVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5WjBGTlIxVkRDazFCYTB4eVRuaHJWMlUwVHpGV2JFNDFPRTlqTkcxMlpGQjRjRFJhYUZGMFYwdFNNM0pGUmxCS2FXOXFOMWM1YkV3d1VIYzFiVlp5T1VaQ2VrZzJjMW9LY0dkSmVFRlFhamhKVUZaUFZWVlRVM1JUV0dnM1VsZHFkQ3RKVkVsNVYzQjNTWG8zVUd0MWFVOUZNSEJEUnpaSWRrZERkbXdyWmxScE1FMVFkbkpUVUFwb2NuSmxaV2M5UFFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19\",\"integratedTime\":1676524985,\"logIndex\":13452680,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}", + "dev.sigstore.cosign/certificate": "-----BEGIN CERTIFICATE-----\nMIICoDCCAiagAwIBAgIURVuJJz9H2lTVWO67PnsgtMtSmlMwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjMwMjE2MDUyMzA0WhcNMjMwMjE2MDUzMzA0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE7eKu8pk7g2/IRhRy2P/2HcsSBcX6BZhOCIsl\nH05AXqK5eKBFO0eM+hHqqiima4Tnwzc1sOot8tTFw9yDDmxpNKOCAUUwggFBMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQULiUD\nz1Dnoh9UMgioO1tYn0w5IBUwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wIAYDVR0RAQH/BBYwFIESc296ZXJjYW5AZ21haWwuY29tMCwGCisGAQQBg78w\nAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBiQYKKwYBBAHWeQIE\nAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABhlit\nGLUAAAQDAEYwRAIgONkdpHynlo2tOobpnrDWOB6KypqkVvtUmFJ+RSUH+H0CIEMH\ntL6mrx3y3eUkpv27nQLRTZwWrFr84guC0xQwpuYjMAoGCCqGSM49BAMDA2gAMGUC\nMAkLrNxkWe4O1VlN58Oc4mvdPxp4ZhQtWKR3rEFPJioj7W9lL0Pw5mVr9FBzH6sZ\npgIxAPj8IPVOUUSStSXh7RWjt+ITIyWpwIz7PkuiOE0pCG6HvGCvl+fTi0MPvrSP\nhrreeg==\n-----END CERTIFICATE-----\n", + "dev.sigstore.cosign/chain": "-----BEGIN CERTIFICATE-----\nMIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C\nAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7\n7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS\n0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB\nBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp\nKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI\nzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR\nnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP\nmygUY7Ii2zbdCdliiow=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7\nXeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex\nX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j\nYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY\nwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ\nKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM\nWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9\nTNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\n-----END CERTIFICATE-----", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: digest.NewDigestFromEncoded(digest.SHA256, "623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"), + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6": []byte(`{"critical":{"identity":{"docker-reference":"wabbitnetworks.azurecr.io/test/cosign-image"},"image":{"docker-manifest-digest":"sha256:623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Verification success", + }, + { + name: "failed keyless verification", + keys: map[PKKey]keymanagementprovider.PublicKey{}, + defaultCosignOpts: true, + getKeysError: false, + store: &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{ + testRefDigest: { + MediaType: refDescriptor.MediaType, + Blobs: []imgspec.Descriptor{ + { + Digest: digest.NewDigestFromEncoded(digest.SHA256, "d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6"), + Annotations: map[string]string{ + "dev.cosignproject.cosign/signature": "MEUCIFBlKbxxg1Ni++g99jeWO8Of3g5L0Xd+qMzdqCZySQ8DAiEA3lcOJPJ1FQOahtWaRU0hG0XxFEsbcVx6SIyzYQMMR0A=", + "dev.sigstore.cosign/bundle": "{\"SignedEntryTimestamp\":\"AIZfWhm9x2F7wil5dkWX+0+njT+FWXFr8AskDkiHpzoAiEApDk9STKcBJTkQ4qy9/8gn6ea2wduh3UjbLRnzZQa9gU=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJkMTIyNmUzNmJjODUwMjk3ODMyNGNiMmNiMjExNmM2YWE0OGVkYjJlYThmMTViMWM2ZjZmMjU2ZWQ0MzM4OGY2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRkJsS2J4eGcxTmkrK2c5OWplV084T2YzZzVMMFhkK3FNemRxQ1p5U1E4REFpRUEzbGNPSlBKMUZRT2FodFdhUlUwaEcwWHhGRXNiY1Z4NlNJeXpZUU1NUjBBPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTnZSRU5EUVdsaFowRjNTVUpCWjBsVlVsWjFTa3A2T1VneWJGUldWMDgyTjFCdWMyZDBUWFJUYld4TmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDFxUlRKTlJGVjVUWHBCTUZkb1kwNU5hazEzVFdwRk1rMUVWWHBOZWtFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVUzWlV0MU9IQnJOMmN5TDBsU2FGSjVNbEF2TWtoamMxTkNZMWcyUWxwb1QwTkpjMndLU0RBMVFWaHhTelZsUzBKR1R6QmxUU3RvU0hGeGFXbHRZVFJVYm5kNll6RnpUMjkwT0hSVVJuYzVlVVJFYlhod1RrdFBRMEZWVlhkblowWkNUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZNYVZWRUNub3hSRzV2YURsVlRXZHBiMDh4ZEZsdU1IYzFTVUpWZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBsQldVUldVakJTUVZGSUwwSkNXWGRHU1VWVFl6STVObHBZU21wWlZ6VkJXakl4YUdGWGQzVlpNamwwVFVOM1IwTnBjMGRCVVZGQ1p6YzRkd3BCVVVWRlNHMW9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWMySXlaSEJpYVRsMldWaFdNR0ZFUTBKcFVWbExTM2RaUWtKQlNGZGxVVWxGQ2tGblVqZENTR3RCWkhkQ01VRk9NRGxOUjNKSGVIaEZlVmw0YTJWSVNteHVUbmRMYVZOc05qUXphbmwwTHpSbFMyTnZRWFpMWlRaUFFVRkJRbWhzYVhRS1IweFZRVUZCVVVSQlJWbDNVa0ZKWjA5T2EyUndTSGx1Ykc4eWRFOXZZbkJ1Y2tSWFQwSTJTM2x3Y1d0V2RuUlZiVVpLSzFKVFZVZ3JTREJEU1VWTlNBcDBURFp0Y25nemVUTmxWV3R3ZGpJM2JsRk1VbFJhZDFkeVJuSTROR2QxUXpCNFVYZHdkVmxxVFVGdlIwTkRjVWRUVFRRNVFrRk5SRUV5WjBGTlIxVkRDazFCYTB4eVRuaHJWMlUwVHpGV2JFNDFPRTlqTkcxMlpGQjRjRFJhYUZGMFYwdFNNM0pGUmxCS2FXOXFOMWM1YkV3d1VIYzFiVlp5T1VaQ2VrZzJjMW9LY0dkSmVFRlFhamhKVUZaUFZWVlRVM1JUV0dnM1VsZHFkQ3RKVkVsNVYzQjNTWG8zVUd0MWFVOUZNSEJEUnpaSWRrZERkbXdyWmxScE1FMVFkbkpUVUFwb2NuSmxaV2M5UFFvdExTMHRMVVZPUkNCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2c9PSJ9fX19\",\"integratedTime\":1676524985,\"logIndex\":13452680,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}", + "dev.sigstore.cosign/certificate": "-----BEGIN CERTIFICATE-----\nMIICoDCCAiagAwIBAgIURVuJJz9H2lTVWO67PnsgtMtSmlMwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjMwMjE2MDUyMzA0WhcNMjMwMjE2MDUzMzA0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE7eKu8pk7g2/IRhRy2P/2HcsSBcX6BZhOCIsl\nH05AXqK5eKBFO0eM+hHqqiima4Tnwzc1sOot8tTFw9yDDmxpNKOCAUUwggFBMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQULiUD\nz1Dnoh9UMgioO1tYn0w5IBUwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wIAYDVR0RAQH/BBYwFIESc296ZXJjYW5AZ21haWwuY29tMCwGCisGAQQBg78w\nAQEEHmh0dHBzOi8vZ2l0aHViLmNvbS9sb2dpbi9vYXV0aDCBiQYKKwYBBAHWeQIE\nAgR7BHkAdwB1AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABhlit\nGLUAAAQDAEYwRAIgONkdpHynlo2tOobpnrDWOB6KypqkVvtUmFJ+RSUH+H0CIEMH\ntL6mrx3y3eUkpv27nQLRTZwWrFr84guC0xQwpuYjMAoGCCqGSM49BAMDA2gAMGUC\nMAkLrNxkWe4O1VlN58Oc4mvdPxp4ZhQtWKR3rEFPJioj7W9lL0Pw5mVr9FBzH6sZ\npgIxAPj8IPVOUUSStSXh7RWjt+ITIyWpwIz7PkuiOE0pCG6HvGCvl+fTi0MPvrSP\nhrreeg==\n-----END CERTIFICATE-----\n", + "dev.sigstore.cosign/chain": "-----BEGIN CERTIFICATE-----\nMIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C\nAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7\n7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS\n0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB\nBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp\nKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI\nzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR\nnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP\nmygUY7Ii2zbdCdliiow=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7\nXeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex\nX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j\nYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY\nwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ\nKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM\nWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9\nTNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\n-----END CERTIFICATE-----", + }, + }, + }, + }, + }, + Subjects: map[digest.Digest]*ocispecs.SubjectDescriptor{ + subjectDigest: { + Descriptor: imgspec.Descriptor{ + Digest: digest.NewDigestFromEncoded(digest.SHA256, "623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"), + MediaType: imgspec.MediaTypeImageManifest, + }, + }, + }, + Blobs: map[digest.Digest][]byte{ + "sha256:d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6": []byte(`{"critical":{"identity":{"docker-reference":"wabbitnetworks.azurecr.io/test/cosign-image"},"image":{"docker-manifest-digest":"sha256:623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"},"type":"cosign container image signature"},"optional":null}`), + }, + }, + expectedResultMessagePrefix: "Failed to validate the Cosign signature:", + expectedErrorReason: "failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91", + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + getKeyMapOpts = func(ctx context.Context, trustPolicy TrustPolicy, _ string) (map[PKKey]keymanagementprovider.PublicKey, cosign.CheckOpts, error) { + co := tt.cosignOpts + if tt.getKeysError { + return nil, cosign.CheckOpts{}, fmt.Errorf("error") + } + + if tt.defaultCosignOpts { + co, _ = trustPolicy.GetCosignOpts(ctx) + } + + return tt.keys, co, nil + } + verifierFactory := cosignVerifierFactory{} + trustPoliciesConfig := []TrustPolicyConfig{ + { + Name: "test-policy", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + } + validConfig := config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "type": "cosign", + "trustPolicies": trustPoliciesConfig, + } + cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace") + if err != nil { + t.Fatalf("Create() error = %v", err) + } + result, _ := cosignVerifier.Verify(context.Background(), subjectRef, refDescriptor, tt.store) + if !strings.HasPrefix(result.Message, tt.expectedResultMessagePrefix) { + t.Errorf("result.Message = %v, want %v", result.Message, tt.expectedResultMessagePrefix) + } + if result.ErrorReason != tt.expectedErrorReason { + t.Fatalf("expected error reason: %s, but got: %s", tt.expectedErrorReason, result.ErrorReason) + } + }) + } +} + +// TestVerificationMessage tests the verificationMessage function +func TestVerificationMessage(t *testing.T) { + tc := []struct { + name string + expectedMessages []string + bundleVerified bool + checkOpts cosign.CheckOpts + }{ + { + name: "keyed, offline bundle, claims with annotations", + expectedMessages: []string{annotationMessage, claimsMessage, offlineBundleMessage, sigVerifierMessage}, + bundleVerified: true, + checkOpts: cosign.CheckOpts{ + ClaimVerifier: cosign.SimpleClaimVerifier, + Annotations: map[string]interface{}{ + "test": "test", + }, + SigVerifier: &mockNoOpVerifier{}, + }, + }, + { + name: "keyless, rekor, fulcio", + expectedMessages: []string{rekorClaimsMessage, rekorSigMessage, certVerifierMessage}, + bundleVerified: false, + checkOpts: cosign.CheckOpts{ + RekorClient: &client.Rekor{}, + }, + }, + } + for i, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + result := verificationPerformedMessage(tt.bundleVerified, &tc[i].checkOpts) + if !slices.Equal(result, tt.expectedMessages) { + t.Errorf("verificationMessage() = %v, want %v", result, tt.expectedMessages) + } + }) + } +} + +func TestProcessAKVSignature_RSAKey(t *testing.T) { + tests := []struct { + name string + keySize int + encodedSig string + expectedErr bool + expectedHashType crypto.Hash + expectedSigOut bool + }{ + { + name: "RSA 2048 bits", + keySize: 256, + expectedErr: false, + expectedHashType: crypto.SHA256, + expectedSigOut: true, + }, + { + name: "RSA 3072 bits", + keySize: 384, + expectedErr: false, + expectedHashType: crypto.SHA384, + expectedSigOut: true, + }, + { + name: "RSA 4096 bits", + keySize: 512, + expectedErr: false, + expectedHashType: crypto.SHA512, + expectedSigOut: true, + }, + { + name: "Unsupported key size", + keySize: 128, + expectedErr: true, + }, + { + name: "Invalid base64 encoded signature", + keySize: 256, + encodedSig: "ThisIsNot@ValidBase64%String!", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock RSA public key + privateKey, err := rsa.GenerateKey(rand.Reader, tt.keySize*8) + if err != nil { + t.Fatalf("Failed to generate RSA key: %v", err) + } + rsaPublicKey := &privateKey.PublicKey + + // Mock the signature as base64 encoded string + sig := "dummy_signature" + encodedSig := base64.StdEncoding.EncodeToString([]byte(sig)) + if tt.encodedSig != "" { + encodedSig = tt.encodedSig + } + + // Process the signature + hashType, sigOut, err := processAKVSignature(encodedSig, nil, rsaPublicKey, []byte("test payload"), []static.Option{}) + + if tt.expectedErr { + if err == nil { + t.Fatalf("Expected error but got nil") + } + } else { + if hashType != tt.expectedHashType { + t.Fatalf("Expected hash type %v but got %v", tt.expectedHashType, hashType) + } + if tt.expectedSigOut != (sigOut != nil) { + t.Fatalf("Expected signature output to be %v but got %v", tt.expectedSigOut, sigOut) + } + } + }) } } diff --git a/pkg/verifier/cosign/trustpolicies.go b/pkg/verifier/cosign/trustpolicies.go new file mode 100644 index 000000000..6ba5521ed --- /dev/null +++ b/pkg/verifier/cosign/trustpolicies.go @@ -0,0 +1,154 @@ +/* +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 cosign + +import ( + "fmt" + "regexp" + "slices" + "strings" + + re "github.com/ratify-project/ratify/errors" +) + +type TrustPolicies struct { + policies []TrustPolicy +} + +const GlobalWildcardCharacter = '*' + +var validScopeRegex = regexp.MustCompile(`^[a-z0-9\.\-:@\/]*\*?$`) + +// CreateTrustPolicies creates a set of trust policies from the given configuration +func CreateTrustPolicies(configs []TrustPolicyConfig, verifierName string) (*TrustPolicies, error) { + if len(configs) == 0 { + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to create trust policies: policy configuration not found").WithRemediation("Ensure that the trust policy configuration is correct.") + } + + policies := make([]TrustPolicy, 0, len(configs)) + names := make(map[string]struct{}) + for _, policyConfig := range configs { + if _, ok := names[policyConfig.Name]; ok { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: duplicate policy name %s", policyConfig.Name)).WithRemediation("Ensure that trust policy names are unique.") + } + names[policyConfig.Name] = struct{}{} + policy, err := CreateTrustPolicy(policyConfig, verifierName) + if err != nil { + return nil, err + } + policies = append(policies, policy) + } + + if err := validateScopes(policies); err != nil { + return nil, err + } + + return &TrustPolicies{ + policies: policies, + }, nil +} + +// GetScopedPolicy returns the policy that applies to the given reference +// TODO: add link to scopes docs when published +func (tps *TrustPolicies) GetScopedPolicy(reference string) (TrustPolicy, error) { + var globalPolicy TrustPolicy + for _, policy := range tps.policies { + scopes := policy.GetScopes() + for _, scope := range scopes { + if scope == string(GlobalWildcardCharacter) { + // if global wildcard character is used, save the policy and continue + globalPolicy = policy + continue + } + if strings.HasSuffix(scope, string(GlobalWildcardCharacter)) { + if strings.HasPrefix(reference, strings.TrimSuffix(scope, string(GlobalWildcardCharacter))) { + return policy, nil + } + } else if reference == scope { + return policy, nil + } + } + } + // if no scoped policy is found, return the global policy if it exists + if globalPolicy != nil { + return globalPolicy, nil + } + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("No policy found for the artifact %s", reference)) +} + +// validateScopes validates the scopes in the trust policies +func validateScopes(policies []TrustPolicy) error { + scopesMap := make(map[string]struct{}) + hasGlobalWildcard := false + for _, policy := range policies { + policyName := policy.GetName() + scopes := policy.GetScopes() + if len(scopes) == 0 { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: scope parameter is required for trust policy %s", policyName)) + } + // check for global wildcard character along with other scopes in the same policy + if len(scopes) > 1 && slices.Contains(scopes, string(GlobalWildcardCharacter)) { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: global wildcard character %c cannot be used with other scopes within the same trust policy %s", GlobalWildcardCharacter, policyName)) + } + // check for duplicate global wildcard characters across policies + if slices.Contains(scopes, string(GlobalWildcardCharacter)) { + if hasGlobalWildcard { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: global wildcard character %c can only be used once", GlobalWildcardCharacter)) + } + hasGlobalWildcard = true + continue + } + for _, scope := range scopes { + // check for empty scope + if scope == "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: scope value cannot be empty in trust policy %s", policyName)) + } + // check scope is formatted correctly + if !validScopeRegex.MatchString(scope) { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: invalid scope %s for trust policy %s", scope, policyName)) + } + // check for duplicate scopes + if _, ok := scopesMap[scope]; ok { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: duplicate scope %s for trust policy %s", scope, policyName)) + } + // check wildcard overlaps + for existingScope := range scopesMap { + isConflict := false + trimmedScope := strings.TrimSuffix(scope, string(GlobalWildcardCharacter)) + trimmedExistingScope := strings.TrimSuffix(existingScope, string(GlobalWildcardCharacter)) + if existingScope[len(existingScope)-1] == GlobalWildcardCharacter && scope[len(scope)-1] == GlobalWildcardCharacter { + // if both scopes have wildcard characters, check if they overlap + if len(scope) < len(existingScope) { + isConflict = strings.HasPrefix(trimmedExistingScope, trimmedScope) + } else { + isConflict = strings.HasPrefix(trimmedScope, trimmedExistingScope) + } + } else if existingScope[len(existingScope)-1] == GlobalWildcardCharacter { + // if existing scope has wildcard character, check if it overlaps with the new absolute scope + isConflict = strings.HasPrefix(scope, trimmedExistingScope) + } else if scope[len(scope)-1] == GlobalWildcardCharacter { + // if new scope has wildcard character, check if it overlaps with the existing absolute scope + isConflict = strings.HasPrefix(existingScope, trimmedScope) + } + if isConflict { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: overlapping scopes %s and %s for trust policy %s", scope, existingScope, policyName)) + } + } + scopesMap[scope] = struct{}{} + } + } + return nil +} diff --git a/pkg/verifier/cosign/trustpolicies_test.go b/pkg/verifier/cosign/trustpolicies_test.go new file mode 100644 index 000000000..6b915a07e --- /dev/null +++ b/pkg/verifier/cosign/trustpolicies_test.go @@ -0,0 +1,449 @@ +/* +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 cosign + +import ( + "testing" +) + +// TestCreateTrustPolicies tests the CreateTrustPolicies function +func TestCreateTrustPolicies(t *testing.T) { + tc := []struct { + name string + policyConfigs []TrustPolicyConfig + wantErr bool + }{ + { + name: "valid policy", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "nil policy", + policyConfigs: nil, + wantErr: true, + }, + { + name: "empty policy", + policyConfigs: []TrustPolicyConfig{}, + wantErr: true, + }, + { + name: "valid multiple policies", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:v2"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "invalid policy scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid policy duplicate names", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid policy invalid trust policy config", + policyConfigs: []TrustPolicyConfig{ + { + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + _, err := CreateTrustPolicies(tt.policyConfigs, "test-verifier") + if (err != nil) != tt.wantErr { + t.Errorf("CreateTrustPolicies() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// TestGetScopedPolicy tests the GetScopedPolicy functions +func TestGetScopedPolicy(t *testing.T) { + tc := []struct { + name string + policyConfigs []TrustPolicyConfig + reference string + wantErr bool + wantPolicyName string + }{ + { + name: "valid policy", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:v2"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + reference: "ghcr.io/ratify-project/ratify:v1", + wantErr: false, + wantPolicyName: "test", + }, + { + name: "valid policy wildcards", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify2:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + reference: "ghcr.io/ratify-project/ratify:v1", + wantErr: false, + wantPolicyName: "test", + }, + { + name: "no matching policy", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify2:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + reference: "ghcr.io/ratify-project/ratify3:v1", + wantErr: true, + wantPolicyName: "", + }, + { + name: "default to global wildcard policy if exists", + policyConfigs: []TrustPolicyConfig{ + { + Name: "global", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify2:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + reference: "ghcr.io/ratify-project/ratify3:v1", + wantErr: false, + wantPolicyName: "global", + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + policies, err := CreateTrustPolicies(tt.policyConfigs, "test-verifier") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + policy, err := policies.GetScopedPolicy(tt.reference) + if (err != nil) != tt.wantErr { + t.Errorf("GetScopedPolicy() error = %v, wantErr %v", err, tt.wantErr) + } + if err == nil && policy.GetName() != tt.wantPolicyName { + t.Errorf("GetScopedPolicy() policy name = %v, want %v", policy.GetName(), tt.wantPolicyName) + } + }) + } +} + +// TestValidateScopes tests the validateScopes function +func TestValidateScopes(t *testing.T) { + tc := []struct { + name string + policyConfigs []TrustPolicyConfig + wantErr bool + }{ + { + name: "valid absolute scope", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "multiple valid absolute scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1", "ghcr.io/ratify-project/ratify:v2"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "valid wild card scope", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "multiple valid wild card scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "valid global wild card scope", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "invalid global wild card scope with other scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"*", "somescope"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid absolute scope duplicate", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1", "ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid absolute scope duplicate across policies", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid global wildcard scope with duplicate wild card scope", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid wild card scope duplicate", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:*", "ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid wild card scope prefix", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"*.azurecr.io"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid wild card character middle of scope", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/*/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid wildcard overlap scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/*", "ghcr.io/ratify-project/*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "valid wildcard no overlap wildcard scopes", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/test/*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: false, + }, + { + name: "invalid wildcard and absolute overlap", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + { + name: "invalid wildcard and absolute overlap reverse order", + policyConfigs: []TrustPolicyConfig{ + { + Name: "test", + Scopes: []string{"ghcr.io/ratify-project/ratify:v1"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + { + Name: "test-2", + Scopes: []string{"ghcr.io/ratify-project/ratify:*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + policies := make([]TrustPolicy, 0, len(tt.policyConfigs)) + for _, policyConfig := range tt.policyConfigs { + policy, err := CreateTrustPolicy(policyConfig, "test-verifier") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + policies = append(policies, policy) + } + err := validateScopes(policies) + if (err != nil) != tt.wantErr { + t.Errorf("validateScopes() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/verifier/cosign/trustpolicy.go b/pkg/verifier/cosign/trustpolicy.go new file mode 100644 index 000000000..8d20a7320 --- /dev/null +++ b/pkg/verifier/cosign/trustpolicy.go @@ -0,0 +1,312 @@ +/* +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 cosign + +import ( + "context" + "crypto" + "fmt" + "os" + "slices" + + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/utils" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" + "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/sigstore/sigstore/pkg/cryptoutils" +) + +type KeyConfig struct { + Provider string `json:"provider,omitempty"` + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + File string `json:"file,omitempty"` +} + +type KeylessConfig struct { + CTLogVerify *bool `json:"ctLogVerify,omitempty"` + CertificateIdentity string `json:"certificateIdentity,omitempty"` + CertificateIdentityRegExp string `json:"certificateIdentityRegExp,omitempty"` + CertificateOIDCIssuer string `json:"certificateOIDCIssuer,omitempty"` + CertificateOIDCIssuerRegExp string `json:"certificateOIDCIssuerRegExp,omitempty"` +} + +type TrustPolicyConfig struct { + Version string `json:"version"` + Name string `json:"name"` + Scopes []string `json:"scopes"` + Keys []KeyConfig `json:"keys,omitempty"` + Keyless KeylessConfig `json:"keyless,omitempty"` + TLogVerify *bool `json:"tLogVerify,omitempty"` + RekorURL string `json:"rekorURL,omitempty"` +} + +type PKKey struct { + Provider string `json:"provider,omitempty"` + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` +} + +type trustPolicy struct { + scopes []string + localKeys map[PKKey]keymanagementprovider.PublicKey + config TrustPolicyConfig + verifierName string + isKeyless bool +} + +type TrustPolicy interface { + GetName() string + GetKeys(ctx context.Context, namespace string) (map[PKKey]keymanagementprovider.PublicKey, error) + GetScopes() []string + GetCosignOpts(context.Context) (cosign.CheckOpts, error) +} + +const ( + fileProviderName string = "file" + DefaultRekorURL string = "https://rekor.sigstore.dev" + DefaultTLogVerify bool = true + DefaultCTLogVerify bool = true + DefaultTrustPolicyConfigVersion string = "1.0.0" +) + +var SupportedTrustPolicyConfigVersions = []string{DefaultTrustPolicyConfigVersion} + +// CreateTrustPolicy creates a trust policy from the given configuration +// returns an error if the configuration is invalid +// reads the public keys from the file path +func CreateTrustPolicy(config TrustPolicyConfig, verifierName string) (TrustPolicy, error) { + // set the default trust policy version if not provided + // currently only one version is supported + if config.Version == "" { + config.Version = DefaultTrustPolicyConfigVersion + } + + if err := validate(config); err != nil { + return nil, err + } + + keyMap := make(map[PKKey]keymanagementprovider.PublicKey) + for _, keyConfig := range config.Keys { + // check if the key is defined by file path or by key management provider + if keyConfig.File != "" { + pubKey, err := loadKeyFromPath(keyConfig.File) + if err != nil { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: failed to load the key from file %s", config.Name, keyConfig.File)).WithError(err).WithRemediation("Ensure that the key file path is correct and public key is correctly saved.") + } + keyMap[PKKey{Provider: fileProviderName, Name: keyConfig.File}] = keymanagementprovider.PublicKey{Key: pubKey, ProviderType: fileProviderName} + } + } + + if config.RekorURL == "" { + config.RekorURL = DefaultRekorURL + } + + if config.TLogVerify == nil { + config.TLogVerify = utils.MakePtr(DefaultTLogVerify) + } + + if config.Keyless != (KeylessConfig{}) && config.Keyless.CTLogVerify == nil { + config.Keyless.CTLogVerify = utils.MakePtr(DefaultCTLogVerify) + } + + return &trustPolicy{ + scopes: config.Scopes, + localKeys: keyMap, + config: config, + verifierName: verifierName, + isKeyless: config.Keyless != KeylessConfig{}, + }, nil +} + +// GetName returns the name of the trust policy +func (tp *trustPolicy) GetName() string { + return tp.config.Name +} + +// GetKeys returns the public keys defined in the trust policy +func (tp *trustPolicy) GetKeys(ctx context.Context, _ string) (map[PKKey]keymanagementprovider.PublicKey, error) { + keyMap := make(map[PKKey]keymanagementprovider.PublicKey) + // preload the local keys into the map of keys to be returned + for key, pubKey := range tp.localKeys { + keyMap[key] = pubKey + } + + for _, keyConfig := range tp.config.Keys { + // if the key is defined by file path, it has already been loaded into the key map + if keyConfig.File != "" { + continue + } + // get the key management provider resource which contains a map of keys + kmpResource, kmpErr := keymanagementprovider.GetKeysFromMap(ctx, keyConfig.Provider) + if kmpErr != nil { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: failed to access key management provider %s", tp.config.Name, keyConfig.Provider)).WithError(kmpErr) + } + // get a specific key from the key management provider resource + if keyConfig.Name != "" { + pubKey, exists := kmpResource[keymanagementprovider.KMPMapKey{Name: keyConfig.Name, Version: keyConfig.Version}] + if !exists { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: key %s with version %s not found in key management provider %s", tp.config.Name, keyConfig.Name, keyConfig.Version, keyConfig.Provider)) + } + keyMap[PKKey{Provider: keyConfig.Provider, Name: keyConfig.Name, Version: keyConfig.Version}] = pubKey + } else { + // get all public keys from the key management provider + for key, pubKey := range kmpResource { + keyMap[PKKey{Provider: keyConfig.Provider, Name: key.Name, Version: key.Version}] = pubKey + } + } + } + return keyMap, nil +} + +// GetScopes returns the scopes defined in the trust policy +func (tp *trustPolicy) GetScopes() []string { + return tp.scopes +} + +func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, error) { + cosignOpts := cosign.CheckOpts{} + var err error + // if tlog verification is enabled, set the rekor client and public keys + if tp.config.TLogVerify != nil && *tp.config.TLogVerify { + cosignOpts.IgnoreTlog = false + // create the rekor client + cosignOpts.RekorClient, err = rekor.NewClient(tp.config.RekorURL) + if err != nil { + return cosignOpts, re.ErrorCodeConfigInvalid.WithDetail(fmt.Errorf("Failed to create Rekor client from URL %s", tp.config.RekorURL)).WithRemediation("Ensure that the Rekor URL is valid.").WithError(err) + } + // Fetches the Rekor public keys from the Rekor server + cosignOpts.RekorPubKeys, err = cosign.GetRekorPubs(ctx) + if err != nil { + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to fetch Rekor public keys").WithRemediation(fmt.Sprintf("Please check if the Rekor server %s is available", tp.config.RekorURL)).WithError(err) + } + } else { + cosignOpts.IgnoreTlog = true + } + + // if keyless verification is enabled, set the root certificates, intermediate certificates, and certificate transparency log public keys + if tp.isKeyless { + roots, err := fulcio.GetRoots() + if err != nil || roots == nil { + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to get fulcio root").WithError(err).WithRemediation("Please check if Fulcio is available") + } + cosignOpts.RootCerts = roots + if tp.config.Keyless.CTLogVerify != nil && *tp.config.Keyless.CTLogVerify { + cosignOpts.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) + if err != nil { + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to fetch certificate transparency log public keys").WithError(err).WithRemediation("Please check if TUF root is available") + } + } else { + cosignOpts.IgnoreSCT = true + } + cosignOpts.IntermediateCerts, err = fulcio.GetIntermediates() + if err != nil { + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to get fulcio intermediate certificates").WithError(err).WithRemediation("Please check if Fulcio is available") + } + // Set the certificate identity and issuer for keyless verification + cosignOpts.Identities = []cosign.Identity{ + { + IssuerRegExp: tp.config.Keyless.CertificateOIDCIssuerRegExp, + Issuer: tp.config.Keyless.CertificateOIDCIssuer, + SubjectRegExp: tp.config.Keyless.CertificateIdentityRegExp, + Subject: tp.config.Keyless.CertificateIdentity, + }, + } + } + + return cosignOpts, nil +} + +// validate checks if the trust policy configuration is valid +// returns an error if the configuration is invalid +func validate(config TrustPolicyConfig) error { + // check if the trust policy version is supported + if !slices.Contains(SupportedTrustPolicyConfigVersions, config.Version) { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: unsupported version %s", config.Name, config.Version)).WithRemediation(fmt.Sprintf("Supported versions are: %v", SupportedTrustPolicyConfigVersions)) + } + + if config.Name == "" { + return re.ErrorCodeConfigInvalid.WithDetail("name parameter is required in trust policy configuration").WithRemediation("Please provide a name for the trust policy.") + } + + if len(config.Scopes) == 0 { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("scopes parameter is required in trust policy configuration %s", config.Name)).WithRemediation("Please provide at least one scope for the trust policy.") + } + + // keys or keyless must be defined + if len(config.Keys) == 0 && config.Keyless == (KeylessConfig{}) { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("keys or keyless parameter is required in trust policy configuration %s", config.Name)) + } + + // only one of keys or keyless can be defined + if len(config.Keys) > 0 && config.Keyless != (KeylessConfig{}) { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Only one of keys or keyless parameter is required in trust policy configuration %s", config.Name)) + } + + for _, keyConfig := range config.Keys { + // check if the key is defined by file path or by key management provider + if keyConfig.File == "" && keyConfig.Provider == "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: key management provider name is required when not using file path", config.Name)) + } + // both file path and key management provider cannot be defined together + if keyConfig.File != "" && keyConfig.Provider != "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: 'name' and 'file' cannot be configured together", config.Name)) + } + // key name is required when key version is defined + if keyConfig.Version != "" && keyConfig.Name == "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: key name is required when key version is defined", config.Name)) + } + } + + // validate keyless configuration + if config.Keyless != (KeylessConfig{}) { + // validate certificate identity specified + if config.Keyless.CertificateIdentity == "" && config.Keyless.CertificateIdentityRegExp == "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: certificate identity or identity regex pattern is required", config.Name)) + } + // validate certificate OIDC issuer specified + if config.Keyless.CertificateOIDCIssuer == "" && config.Keyless.CertificateOIDCIssuerRegExp == "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: certificate OIDC issuer or issuer regex pattern is required", config.Name)) + } + // validate only expression or value is specified for certificate identity + if config.Keyless.CertificateIdentity != "" && config.Keyless.CertificateIdentityRegExp != "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: only one of certificate identity or identity regex pattern should be specified", config.Name)) + } + // validate only expression or value is specified for certificate OIDC issuer + if config.Keyless.CertificateOIDCIssuer != "" && config.Keyless.CertificateOIDCIssuerRegExp != "" { + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: only one of certificate OIDC issuer or issuer regex pattern should be specified", config.Name)) + } + } + + return nil +} + +// loadKeyFromPath loads a public key from a file path and returns it +// TODO: look into supporting cosign's blob.LoadFileOrURL to support URL + env variables +func loadKeyFromPath(filePath string) (crypto.PublicKey, error) { + contents, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + if len(contents) == 0 { + return nil, fmt.Errorf("key file %s is empty", filePath) + } + + return cryptoutils.UnmarshalPEMToPublicKey(contents) +} diff --git a/pkg/verifier/cosign/trustpolicy_test.go b/pkg/verifier/cosign/trustpolicy_test.go new file mode 100644 index 000000000..65dcb84da --- /dev/null +++ b/pkg/verifier/cosign/trustpolicy_test.go @@ -0,0 +1,537 @@ +/* +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 cosign + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/x509" + "fmt" + "os" + "testing" + + ctxUtils "github.com/ratify-project/ratify/internal/context" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/sigstore/cosign/v2/pkg/cosign" +) + +type mockTrustPolicy struct { + name string + scopes []string + keysMap map[PKKey]keymanagementprovider.PublicKey + shouldErrKeys bool + shouldErrCosignOpts bool +} + +func (m *mockTrustPolicy) GetName() string { + return m.name +} + +func (m *mockTrustPolicy) GetScopes() []string { + return m.scopes +} + +func (m *mockTrustPolicy) GetKeys(_ context.Context, _ string) (map[PKKey]keymanagementprovider.PublicKey, error) { + if m.shouldErrKeys { + return nil, fmt.Errorf("error getting keys") + } + return m.keysMap, nil +} + +func (m *mockTrustPolicy) GetCosignOpts(_ context.Context) (cosign.CheckOpts, error) { + if m.shouldErrCosignOpts { + return cosign.CheckOpts{}, fmt.Errorf("error getting cosign opts") + } + + return cosign.CheckOpts{}, nil +} + +func TestCreateTrustPolicy(t *testing.T) { + tc := []struct { + name string + cfg TrustPolicyConfig + wantErr bool + }{ + { + name: "invalid config", + cfg: TrustPolicyConfig{}, + wantErr: true, + }, + { + name: "invalid local key path", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + File: "invalid", + }, + }, + }, + wantErr: true, + }, + { + name: "valid local key path", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + File: "../../../test/testdata/cosign.pub", + }, + }, + }, + wantErr: false, + }, + { + name: "valid keyless config with rekor specified", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{ + CertificateIdentity: "test-identity", + CertificateOIDCIssuer: "https://test-issuer.com", + }, + }, + wantErr: false, + }, + { + name: "invalid config version", + cfg: TrustPolicyConfig{ + Version: "0.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{ + CertificateIdentity: "test-identity", + CertificateOIDCIssuer: "https://test-issuer.com", + }, + }, + wantErr: true, + }, + } + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + _, err := CreateTrustPolicy(tt.cfg, "test-verifier") + if (err != nil) != tt.wantErr { + t.Fatalf("expected %v, got %v", tt.wantErr, err) + } + }) + } +} + +// TestGetName tests the GetName function for Trust Policy +func TestGetName(t *testing.T) { + trustPolicyConfig := TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + } + trustPolicy, err := CreateTrustPolicy(trustPolicyConfig, "test-verifier") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if trustPolicy.GetName() != trustPolicyConfig.Name { + t.Fatalf("expected %s, got %s", trustPolicyConfig.Name, trustPolicy.GetName()) + } +} + +// TestGetScopes tests the GetScopes function for Trust Policy +func TestGetScopes(t *testing.T) { + trustPolicyConfig := TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + } + trustPolicy, err := CreateTrustPolicy(trustPolicyConfig, "test-verifier") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if len(trustPolicy.GetScopes()) != len(trustPolicyConfig.Scopes) { + t.Fatalf("expected %v, got %v", trustPolicyConfig.Scopes, trustPolicy.GetScopes()) + } + if trustPolicy.GetScopes()[0] != trustPolicyConfig.Scopes[0] { + t.Fatalf("expected %s, got %s", trustPolicyConfig.Scopes[0], trustPolicy.GetScopes()[0]) + } +} + +// TestGetKeys tests the GetKeys function for Trust Policy +func TestGetKeys(t *testing.T) { + inputMap := map[keymanagementprovider.KMPMapKey]crypto.PublicKey{ + {Name: "key1"}: &ecdsa.PublicKey{}, + } + keymanagementprovider.SaveSecrets("ns/kmp", "", inputMap, map[keymanagementprovider.KMPMapKey][]*x509.Certificate{}) + tc := []struct { + name string + cfg TrustPolicyConfig + wantErr bool + }{ + { + name: "only local keys", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + File: "../../../test/testdata/cosign.pub", + }, + }, + }, + wantErr: false, + }, + { + name: "nonexistent KMP", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "nonexistent", + }, + }, + }, + wantErr: true, + }, + { + name: "access nonexistent key from KMP", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "ns/kmp", + Name: "nonexistent", + }, + }, + }, + wantErr: true, + }, + { + name: "valid KMP", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "ns/kmp", + Name: "key1", + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + trustPolicy, err := CreateTrustPolicy(tt.cfg, "test-verifier") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + ctx := ctxUtils.SetContextWithNamespace(context.Background(), "ns") + keys, err := trustPolicy.GetKeys(ctx, "") + if (err != nil) != tt.wantErr { + t.Fatalf("expected %v, got %v", tt.wantErr, err) + } + if err == nil && len(keys) != len(tt.cfg.Keys) { + t.Fatalf("expected %v, got %v", tt.cfg.Keys, keys) + } + }) + } +} + +// TestValidate tests the validate function +func TestValidate(t *testing.T) { + tc := []struct { + name string + policyConfig TrustPolicyConfig + wantErr bool + }{ + { + name: "no version", + policyConfig: TrustPolicyConfig{}, + wantErr: true, + }, + { + name: "no name", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + }, + wantErr: true, + }, + { + name: "no scopes", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + }, + wantErr: true, + }, + { + name: "no keys or keyless defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + }, + wantErr: true, + }, + { + name: "keys and keyless defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "kmp", + }, + }, + Keyless: KeylessConfig{CertificateIdentity: "test-identity", CertificateOIDCIssuer: "https://test-issuer.com"}, + }, + wantErr: true, + }, + { + name: "key provider and key path not defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{{}}, + }, + wantErr: true, + }, + { + name: "key provider and key path both defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "kmp", + File: "path", + }, + }, + }, + wantErr: true, + }, + { + name: "key provider not defined but name defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Name: "key name", + }, + }, + }, + wantErr: true, + }, + { + name: "key provider name not defined but version defined", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "kmp", + Version: "key version", + }, + }, + }, + wantErr: true, + }, + { + name: "valid", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "kmp", + Name: "key name", + Version: "key version", + }, + }, + }, + wantErr: false, + }, + { + name: "keyless but no certificate identity specified", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateOIDCIssuer: "test"}, + }, + wantErr: true, + }, + { + name: "keyless but both certificate identity and expression specified", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test", CertificateIdentityRegExp: "test"}, + }, + wantErr: true, + }, + { + name: "keyless but no certificate oidc issuer specified", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test"}, + }, + wantErr: true, + }, + { + name: "keyless but both certificate oidc issuer and expression specified", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test", CertificateOIDCIssuer: "test", CertificateOIDCIssuerRegExp: "test"}, + }, + wantErr: true, + }, + { + name: "keyless but both certificate identity and expression specified", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateOIDCIssuer: "test", CertificateIdentity: "test", CertificateIdentityRegExp: "test"}, + }, + wantErr: true, + }, + { + name: "valid keyless", + policyConfig: TrustPolicyConfig{ + Version: "1.0.0", + Name: "test", + Scopes: []string{"*"}, + Keyless: KeylessConfig{CertificateIdentity: "test", CertificateOIDCIssuer: "test"}, + }, + wantErr: false, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + actual := validate(tt.policyConfig) + if (actual != nil) != tt.wantErr { + t.Fatalf("expected %v, got %v", tt.wantErr, actual) + } + }) + } +} + +// TestLoadKeyFromPath tests the loadKeyFromPath function +func TestLoadKeyFromPath(t *testing.T) { + cosignValidPath := "../../../test/testdata/cosign.pub" + key, err := loadKeyFromPath(cosignValidPath) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if key == nil { + t.Fatalf("expected key, got nil") + } + switch keyType := key.(type) { + case *ecdsa.PublicKey: + default: + t.Fatalf("expected ecdsa.PublicKey, got %v", keyType) + } +} + +func TestGetCosignOpts(t *testing.T) { + testCases := []struct { + name string + tlogVerify bool + rekorURL string + rekorPubKeyEnv string + isKeyless bool + CTLogVerify bool + CTLogPubKeyEnv string + expectedErr bool + }{ + { + name: "invalid rekor url", + tlogVerify: true, + rekorURL: string([]byte{0x7f}), + expectedErr: true, + }, + { + name: "failed to get rekor public key", + tlogVerify: true, + rekorURL: "https://rekor.sigstore.dev", + rekorPubKeyEnv: "invalid", + expectedErr: true, + }, + { + name: "failed to get CT log public key", + tlogVerify: false, + isKeyless: true, + CTLogVerify: true, + CTLogPubKeyEnv: "invalid", + expectedErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.rekorPubKeyEnv != "" { + val := os.Getenv("SIGSTORE_REKOR_PUBLIC_KEY") + os.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tc.rekorPubKeyEnv) + t.Cleanup(func() { + os.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", val) + }) + } + + if tc.CTLogPubKeyEnv != "" { + val := os.Getenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE") + os.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", tc.CTLogPubKeyEnv) + t.Cleanup(func() { + os.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", val) + }) + } + + tp := trustPolicy{ + config: TrustPolicyConfig{ + TLogVerify: &tc.tlogVerify, + RekorURL: tc.rekorURL, + Keyless: KeylessConfig{ + CTLogVerify: &tc.CTLogVerify, + }, + }, + isKeyless: tc.isKeyless, + } + _, err := tp.GetCosignOpts(context.Background()) + if tc.expectedErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + } + }) + } +} diff --git a/pkg/verifier/factory/factory.go b/pkg/verifier/factory/factory.go index 43e16be3c..fdc6e57a8 100644 --- a/pkg/verifier/factory/factory.go +++ b/pkg/verifier/factory/factory.go @@ -21,13 +21,13 @@ import ( "path" "strings" - re "github.com/deislabs/ratify/errors" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/featureflag" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - "github.com/deislabs/ratify/pkg/verifier/plugin" - "github.com/deislabs/ratify/pkg/verifier/types" + re "github.com/ratify-project/ratify/errors" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/featureflag" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/plugin" + "github.com/ratify-project/ratify/pkg/verifier/types" "github.com/sirupsen/logrus" ) @@ -53,12 +53,12 @@ func Register(name string, factory VerifierFactory) { // namespace is only applicable in K8s environment, namespace is appended to the certstore of the truststore so it is uniquely identifiable in a cluster env // the first element of pluginBinDir will be used as the plugin directory func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersion string, pluginBinDir []string, namespace string) (verifier.ReferenceVerifier, error) { - // in cli mode both `type` and `name`` are read from config, if `type` is not specified, `name` is used as `type` + // in cli mode both `type` and `name` are read from config, if `type` is not specified, `name` is used as `type` var verifierTypeStr string if value, ok := verifierConfig[types.Name]; ok { verifierTypeStr = value.(string) } else { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to find verifier name in the verifier config with key %s", "name")) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("The name field is required in the Verifier configuration: %+v", verifierConfig)) } if value, ok := verifierConfig[types.Type]; ok { @@ -66,7 +66,7 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio } if strings.ContainsRune(verifierTypeStr, os.PathSeparator) { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("invalid plugin name for a verifier: %s", verifierTypeStr)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid name [%s] in the Verifier configuration, [%v] is disallowed", verifierTypeStr, os.PathSeparator)) } // if source is specified, download the plugin @@ -74,13 +74,13 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio if featureflag.DynamicPlugins.Enabled { source, err := pluginCommon.ParsePluginSource(source) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, "", re.EmptyLink, err, "failed to parse plugin source", re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to parse the plugin source").WithError(err) } targetPath := path.Join(pluginBinDir[0], verifierTypeStr) err = pluginCommon.DownloadPlugin(source, targetPath) if err != nil { - return nil, re.ErrorCodeDownloadPluginFailure.NewError(re.Verifier, "", re.EmptyLink, err, "failed to download plugin", re.HideStackTrace) + return nil, re.ErrorCodeDownloadPluginFailure.WithDetail("Failed to download the plugin from the source").WithError(err) } logrus.Infof("downloaded verifier plugin %s from %s to %s", verifierTypeStr, source.Artifact, targetPath) } else { @@ -94,10 +94,14 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio } if _, err := pluginCommon.FindInPaths(verifierTypeStr, pluginBinDir); err != nil { - return nil, re.ErrorCodePluginNotFound.NewError(re.Verifier, "", re.EmptyLink, err, "plugin not found", re.HideStackTrace) + return nil, re.ErrorCodePluginNotFound.WithDetail(fmt.Sprintf("Verifier plugin %s not found", verifierTypeStr)).WithError(err).WithRemediation("Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured.") } - return plugin.NewVerifier(configVersion, verifierConfig, pluginBinDir) + pluginVersion := configVersion + if value, ok := verifierConfig[types.Version]; ok { + pluginVersion = value.(string) + } + return plugin.NewVerifier(pluginVersion, verifierConfig, pluginBinDir) } // TODO pointer to avoid copy @@ -109,11 +113,11 @@ func CreateVerifiersFromConfig(verifiersConfig config.VerifiersConfig, defaultPl err := validateVerifiersConfig(&verifiersConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithError(err) + return nil, re.ErrorCodeConfigInvalid.WithError(err) } if len(verifiersConfig.Verifiers) == 0 { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail("verifiers config should have at least one verifier") + return nil, re.ErrorCodeConfigInvalid.WithDetail("The configuration for verifier.plugins must include at least one plugin") } verifiers := make([]verifier.ReferenceVerifier, 0) @@ -127,7 +131,7 @@ func CreateVerifiersFromConfig(verifiersConfig config.VerifiersConfig, defaultPl for _, verifierConfig := range verifiersConfig.Verifiers { verifier, err := CreateVerifierFromConfig(verifierConfig, verifiersConfig.Version, verifiersConfig.PluginBinDirs, namespace) if err != nil { - return nil, re.ErrorCodePluginInitFailure.WithComponentType(re.Verifier).WithError(err) + return nil, re.ErrorCodePluginInitFailure.WithError(err) } verifiers = append(verifiers, verifier) } diff --git a/pkg/verifier/factory/factory_test.go b/pkg/verifier/factory/factory_test.go index b8ee940b3..7b839e0e5 100644 --- a/pkg/verifier/factory/factory_test.go +++ b/pkg/verifier/factory/factory_test.go @@ -20,15 +20,16 @@ import ( "os" "testing" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - - "github.com/deislabs/ratify/pkg/utils" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - "github.com/deislabs/ratify/pkg/verifier/plugin" + "github.com/ratify-project/ratify/internal/constants" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/featureflag" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + + "github.com/ratify-project/ratify/pkg/utils" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/plugin" ) type TestVerifier struct { @@ -103,6 +104,21 @@ func TestCreateVerifiersFromConfig_BuiltInVerifiers_ReturnsExpected(t *testing.T } } +func TestCreateVerifiersFromConfig_InvalidConfig_ReturnsErr(t *testing.T) { + verifierConfig := map[string]interface{}{ + "name": "test-verifier-0", + } + verifiersConfig := config.VerifiersConfig{ + Verifiers: []config.VerifierConfig{verifierConfig}, + } + + _, err := CreateVerifiersFromConfig(verifiersConfig, "test/dir", constants.EmptyNamespace) + + if err == nil { + t.Fatalf("expected to have an error") + } +} + func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) { dirPath, err := utils.CreatePlugin("sample") if err != nil { @@ -111,8 +127,9 @@ func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) defer os.RemoveAll(dirPath) verifierConfig := map[string]interface{}{ - "name": "plugin-verifier-0", - "type": "sample", + "name": "plugin-verifier-0", + "type": "sample", + "version": "1.0.0", } verifiersConfig := config.VerifiersConfig{ Verifiers: []config.VerifierConfig{verifierConfig}, @@ -136,3 +153,83 @@ func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) t.Fatalf("type assertion failed expected a plugin in verifier") } } + +func TestCreateVerifiersFromConfig_EmptyVerifiers_ReturnsErr(t *testing.T) { + verifiersConfig := config.VerifiersConfig{} + + _, err := CreateVerifiersFromConfig(verifiersConfig, "test/dir", "") + + if err == nil { + t.Fatalf("expected to have an error") + } +} + +func TestCreateVerifierFromConfig(t *testing.T) { + tests := []struct { + name string + verifierConfig config.VerifierConfig + configVersion string + pluginBinDir []string + namespace string + dynamicPluginEnabled bool + expectedErr bool + }{ + { + name: "missing name", + verifierConfig: config.VerifierConfig{}, + expectedErr: true, + }, + { + name: "verifier type contains path separator", + verifierConfig: config.VerifierConfig{ + "name": "test/verifier", + }, + expectedErr: true, + }, + { + name: "external verifier plugin not found", + verifierConfig: config.VerifierConfig{ + "name": "not-found", + }, + pluginBinDir: []string{"test/path"}, + expectedErr: true, + }, + { + name: "parse plugin source failed", + verifierConfig: config.VerifierConfig{ + "name": "test-verifier", + "source": "invalid", + }, + dynamicPluginEnabled: true, + expectedErr: true, + }, + { + name: "download plugin failed", + verifierConfig: config.VerifierConfig{ + "name": "test-verifier", + "source": map[string]interface{}{ + "artifact": "invalid", + }, + }, + pluginBinDir: []string{"test/path"}, + dynamicPluginEnabled: true, + expectedErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.dynamicPluginEnabled { + dynamicVal := featureflag.DynamicPlugins.Enabled + t.Cleanup(func() { featureflag.DynamicPlugins.Enabled = dynamicVal }) + featureflag.DynamicPlugins.Enabled = true + } + + _, err := CreateVerifierFromConfig(tt.verifierConfig, tt.configVersion, tt.pluginBinDir, tt.namespace) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error %v, actual %v", tt.expectedErr, err) + } + }) + } +} diff --git a/pkg/verifier/mocks/types.go b/pkg/verifier/mocks/types.go index c6fb07394..cf311d3d1 100644 --- a/pkg/verifier/mocks/types.go +++ b/pkg/verifier/mocks/types.go @@ -19,9 +19,9 @@ import ( "context" "time" - "github.com/deislabs/ratify/pkg/executor" - "github.com/deislabs/ratify/pkg/executor/types" - "github.com/deislabs/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/executor" + "github.com/ratify-project/ratify/pkg/executor/types" + "github.com/ratify-project/ratify/pkg/verifier" ) type TestExecutor struct { diff --git a/pkg/verifier/notation/certStores.go b/pkg/verifier/notation/certStores.go new file mode 100644 index 000000000..495e26b1c --- /dev/null +++ b/pkg/verifier/notation/certStores.go @@ -0,0 +1,28 @@ +/* +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 notation + +import ( + "context" + + "github.com/notaryproject/notation-go/verifier/truststore" +) + +// certStores is an interface that defines the methods for managing certificate stores. +type certStores interface { + // GetCertGroup returns certain type of cert group from namedStore + GetCertGroup(ctx context.Context, storeType truststore.Type, namedStore string) (certGroup []string) +} diff --git a/pkg/verifier/notation/certstoresbytype.go b/pkg/verifier/notation/certstoresbytype.go new file mode 100644 index 000000000..a5a9228bf --- /dev/null +++ b/pkg/verifier/notation/certstoresbytype.go @@ -0,0 +1,106 @@ +/* +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 notation + +import ( + "context" + "fmt" + + "github.com/notaryproject/notation-go/verifier/truststore" + "github.com/ratify-project/ratify/internal/logger" +) + +type certStoreType string + +const ( + CA certStoreType = "ca" + SigningAuthority certStoreType = "signingAuthority" + TSA certStoreType = "tsa" +) + +func (certstoretype certStoreType) String() string { + return string(certstoretype) +} + +// verificationCertStores describes the configuration of verification certStores +// type verificationCertStores supports new format map[string]map[string][]string +// +// { +// "ca": { +// "certs": {"kv1", "kv2"}, +// }, +// "signingauthority": { +// "certs": {"kv3"} +// }, +// } +// +// type verificationCertStores supports legacy format map[string][]string as well. +// +// { +// "certs": {"kv1", "kv2"}, +// }, +type verificationCertStores map[string]interface{} + +// certStoresByType implements certStores interface and place certs under the trustStoreType +// +// { +// "ca": { +// "certs": {"kv1", "kv2"}, +// }, +// "signingauthority": { +// "certs": {"kv3"} +// }, +// } +type certStoresByType map[certStoreType]map[string][]string + +// newCertStoreByType performs type assertion and converts certificate stores configuration into certStoresByType +func newCertStoreByType(confInNewFormat verificationCertStores) (certStores, error) { + s := make(certStoresByType) + for certstoretype, storeData := range confInNewFormat { + s[certStoreType(certstoretype)] = make(map[string][]string) + parsedStoreData, ok := storeData.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) + } + for storeName, certProviderList := range parsedStoreData { + var certProviderNames []string + parsedCertProviders, ok := certProviderList.([]interface{}) + if !ok { + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) + } + for _, certProvider := range parsedCertProviders { + certProviderName, ok := certProvider.(string) + if !ok { + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) + } + certProviderNames = append(certProviderNames, certProviderName) + } + s[certStoreType(certstoretype)][storeName] = certProviderNames + } + } + return s, nil +} + +// GetCertGroup returns certain type of certs from namedStore +func (s certStoresByType) GetCertGroup(ctx context.Context, storeType truststore.Type, namedStore string) (certGroup []string) { + if certStores, ok := s[certStoreType(storeType)]; ok { + if certGroup, ok = certStores[namedStore]; ok { + return + } + } + logger.GetLogger(ctx, logOpt).Warnf("unable to fetch certGroup from namedStore: %+v in type: %v", namedStore, storeType) + return +} diff --git a/pkg/verifier/notation/certstoresbytype_test.go b/pkg/verifier/notation/certstoresbytype_test.go new file mode 100644 index 000000000..cd4ae9067 --- /dev/null +++ b/pkg/verifier/notation/certstoresbytype_test.go @@ -0,0 +1,61 @@ +/* +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 notation + +import "testing" + +func TestNewCertStoreByTypeInvalidInput(t *testing.T) { + tests := []struct { + name string + conf verificationCertStores + expectErr bool + }{ + { + name: "invalid certStores type", + conf: verificationCertStores{ + trustStoreTypeCA: []string{}, + }, + expectErr: true, + }, + { + name: "invalid certProviderList type", + conf: verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": "akv1", + "certstore2": []interface{}{"akv3", "akv4"}, + }, + }, + expectErr: true, + }, + { + name: "invalid certProvider type", + conf: verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"akv1", []string{}}, + }, + }, + expectErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := newCertStoreByType(tt.conf) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} diff --git a/pkg/verifier/notation/notation.go b/pkg/verifier/notation/notation.go index 7ac4b6f9a..e825db11d 100644 --- a/pkg/verifier/notation/notation.go +++ b/pkg/verifier/notation/notation.go @@ -22,32 +22,35 @@ import ( paths "path/filepath" "strings" - ratifyconfig "github.com/deislabs/ratify/config" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/internal/constants" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/homedir" - - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - "github.com/deislabs/ratify/pkg/verifier/factory" - "github.com/deislabs/ratify/pkg/verifier/types" + ratifyconfig "github.com/ratify-project/ratify/config" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/homedir" + "github.com/notaryproject/notation-go/log" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/factory" + "github.com/ratify-project/ratify/pkg/verifier/types" _ "github.com/notaryproject/notation-core-go/signature/cose" // register COSE signature _ "github.com/notaryproject/notation-core-go/signature/jws" // register JWS signature "github.com/notaryproject/notation-go" notationVerifier "github.com/notaryproject/notation-go/verifier" "github.com/notaryproject/notation-go/verifier/trustpolicy" + "github.com/notaryproject/notation-go/verifier/truststore" oci "github.com/opencontainers/image-spec/specs-go/v1" ) const ( - verifierType = "notation" - defaultCertPath = "ratify-certs/notation/truststore" + verifierType = "notation" + defaultCertPath = "ratify-certs/notation/truststore" + trustStoreTypeCA = string(truststore.TypeCA) + trustStoreTypeSigningAuthority = string(truststore.TypeSigningAuthority) + trustStoreTypeTSA = string(truststore.TypeTSA) ) // NotationPluginVerifierConfig describes the configuration of notation verifier @@ -57,8 +60,21 @@ type NotationPluginVerifierConfig struct { //nolint:revive // ignore linter to h // VerificationCerts is array of directories containing certificates. VerificationCerts []string `json:"verificationCerts"` - // VerificationCerts is map defining which keyvault certificates belong to which trust store - VerificationCertStores map[string][]string `json:"verificationCertStores"` + // VerificationCertStores defines a collection of Notary Project Trust Stores. + // VerificationCertStores accepts new format map[string]map[string][]string + // { + // "ca": { + // "certs": {"kv1", "kv2"}, + // }, + // "signingauthority": { + // "certs": {"kv3"} + // }, + // } + // VerificationCertStores accepts legacy format map[string][]string as well. + // { + // "certs": {"kv1", "kv2"}, + // }, + VerificationCertStores verificationCertStores `json:"verificationCertStores"` // TrustPolicyDoc represents a trustpolicy.json document. Reference: https://pkg.go.dev/github.com/notaryproject/notation-go@v0.12.0-beta.1.0.20221125022016-ab113ebd2a6c/verifier/trustpolicy#Document TrustPolicyDoc trustpolicy.Document `json:"trustPolicyDoc"` } @@ -77,7 +93,7 @@ func init() { } func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.VerifierConfig, pluginDirectory string, namespace string) (verifier.ReferenceVerifier, error) { - logger.GetLogger(context.Background(), logOpt).Debugf("creating notation with config %v, namespace '%v'", verifierConfig, namespace) + logger.GetLogger(context.Background(), logOpt).Debugf("creating Notation verifier with config %v, namespace '%v'", verifierConfig, namespace) verifierName := fmt.Sprintf("%s", verifierConfig[types.Name]) verifierTypeStr := "" if _, ok := verifierConfig[types.Type]; ok { @@ -85,12 +101,12 @@ func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.V } conf, err := parseVerifierConfig(verifierConfig, namespace) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName) + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } verifyService, err := getVerifierService(conf, pluginDirectory) if err != nil { - return nil, re.ErrorCodePluginInitFailure.WithComponentType(re.Verifier).WithPluginName(verifierName).WithError(err) + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } artifactTypes := strings.Split(conf.ArtifactTypes, ",") @@ -127,54 +143,49 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, subjectDesc, err := store.GetSubjectDescriptor(ctx, subjectReference) if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetSubjectDescriptorFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to resolve subject: %+v", subjectReference), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature of the artifact: %+v", subjectReference)).WithError(err) } referenceManifest, err := store.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetReferenceManifestFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to resolve reference manifest: %+v", referenceDescriptor), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature: %+v", referenceDescriptor)).WithError(err) } - if len(referenceManifest.Blobs) == 0 { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeSignatureNotFound.NewError(re.Verifier, v.name, re.EmptyLink, nil, fmt.Sprintf("no signature content found for referrer: %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), re.HideStackTrace) + if len(referenceManifest.Blobs) != 1 { + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Notation signature manifest requires exactly one signature envelope blob, got %d", len(referenceManifest.Blobs))).WithRemediation(fmt.Sprintf("Please inspect the artifact [%s@%s] is correctly signed by Notation signer", subjectReference.Path, referenceDescriptor.Digest.String())) + } + blobDesc := referenceManifest.Blobs[0] + refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest) + if err != nil { + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature of the artifact: %+v", subjectReference)).WithError(err) } - for _, blobDesc := range referenceManifest.Blobs { - refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest) - if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetBlobContentFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to get blob content of digest: %s", blobDesc.Digest), re.HideStackTrace) - } - - // TODO: notation verify API only accepts digested reference now. - // Pass in tagged reference instead once notation-go supports it. - subjectRef := fmt.Sprintf("%s@%s", subjectReference.Path, subjectReference.Digest.String()) - outcome, err := v.verifySignature(ctx, subjectRef, blobDesc.MediaType, subjectDesc.Descriptor, refBlob) - if err != nil { - return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyPluginFailure.NewError(re.Verifier, v.name, re.NotationTsgLink, err, "failed to verify signature of digest", re.HideStackTrace) - } - - // Note: notation verifier already validates certificate chain is not empty. - cert := outcome.EnvelopeContent.SignerInfo.CertificateChain[0] - extensions["Issuer"] = cert.Issuer.String() - extensions["SN"] = cert.Subject.String() + // TODO: notation verify API only accepts digested reference now. + // Pass in tagged reference instead once notation-go supports it. + subjectRef := fmt.Sprintf("%s@%s", subjectReference.Path, subjectReference.Digest.String()) + outcome, err := v.verifySignature(ctx, subjectRef, blobDesc.MediaType, subjectDesc.Descriptor, refBlob) + if err != nil { + return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature: %+v", referenceDescriptor)).WithError(err) } - return verifier.VerifierResult{ - Name: v.name, - Type: v.verifierType, - IsSuccess: true, - Message: "signature verification success", - Extensions: extensions, - }, nil + // Note: notation verifier already validates certificate chain is not empty. + cert := outcome.EnvelopeContent.SignerInfo.CertificateChain[0] + extensions["Issuer"] = cert.Issuer.String() + extensions["SN"] = cert.Subject.String() + + return verifier.NewVerifierResult("", v.name, v.verifierType, "Notation signature verification success", true, nil, extensions), nil } func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory string) (notation.Verifier, error) { - store := &trustStore{ - certPaths: conf.VerificationCerts, - certStores: conf.VerificationCertStores, + store, err := newTrustStore(conf.VerificationCerts, conf.VerificationCertStores) + if err != nil { + return nil, err } - - return notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory)) + verifier, err := notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory)) + if err != nil { + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) + } + return verifier, nil } func (v *notationPluginVerifier) verifySignature(ctx context.Context, subjectRef, mediaType string, subjectDesc oci.Descriptor, refBlob []byte) (*notation.VerificationOutcome, error) { @@ -187,30 +198,25 @@ func (v *notationPluginVerifier) verifySignature(ctx context.Context, subjectRef return (*v.notationVerifier).Verify(ctx, subjectDesc, refBlob, opts) } -func parseVerifierConfig(verifierConfig config.VerifierConfig, namespace string) (*NotationPluginVerifierConfig, error) { - verifierName := verifierConfig[types.Name].(string) +func parseVerifierConfig(verifierConfig config.VerifierConfig, _ string) (*NotationPluginVerifierConfig, error) { conf := &NotationPluginVerifierConfig{} verifierConfigBytes, err := json.Marshal(verifierConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, nil, re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to parse the Notation Verifier configuration: %+v", verifierConfig)).WithError(err) } if err := json.Unmarshal(verifierConfigBytes, &conf); err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, fmt.Sprintf("failed to unmarshal to notationPluginVerifierConfig from: %+v.", verifierConfig), re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to parse the Notation Verifier configuration: %+v", verifierConfig)).WithError(err) } - // append namespace to uniquely identify the certstore + defaultCertsDir := paths.Join(homedir.Get(), ratifyconfig.ConfigFileDir, defaultCertPath) + conf.VerificationCerts = append(conf.VerificationCerts, defaultCertsDir) if len(conf.VerificationCertStores) > 0 { - logger.GetLogger(context.Background(), logOpt).Debugf("VerificationCertStores is not empty, will append namespace %v to certificate store if resource does not already contain a namespace", namespace) - conf.VerificationCertStores, err = prependNamespaceToCertStore(conf.VerificationCertStores, namespace) - if err != nil { + if err := normalizeVerificationCertsStores(conf); err != nil { return nil, err } } - - defaultCertsDir := paths.Join(homedir.Get(), ratifyconfig.ConfigFileDir, defaultCertPath) - conf.VerificationCerts = append(conf.VerificationCerts, defaultCertsDir) return conf, nil } @@ -219,23 +225,45 @@ func (v *notationPluginVerifier) GetNestedReferences() []string { return []string{} } -// append namespace to certStore so they are uniquely identifiable -func prependNamespaceToCertStore(verificationCertStore map[string][]string, namespace string) (map[string][]string, error) { - if namespace == "" { - return nil, re.ErrorCodeEnvNotSet.WithComponentType(re.Verifier).WithDetail("failure to parse VerificationCertStores, namespace for VerificationCertStores must be provided") +// normalizeVerificationCertsStores normalize the structure does not match the latest spec +func normalizeVerificationCertsStores(conf *NotationPluginVerifierConfig) error { + isCertStoresByType, isLegacyCertStore := false, false + for key := range conf.VerificationCertStores { + if key != trustStoreTypeCA && key != trustStoreTypeSigningAuthority && key != trustStoreTypeTSA { + isLegacyCertStore = true + logger.GetLogger(context.Background(), logOpt).Debugf("Get VerificationCertStores in legacy format") + } else { + isCertStoresByType = true + } } - - for _, certStores := range verificationCertStore { - for i, certstore := range certStores { - if !isNamespacedNamed(certstore) { - certStores[i] = namespace + constants.NamespaceSeperator + certstore - } + if isCertStoresByType && isLegacyCertStore { + // showing configuration content in the log with error details for better user experience + err := re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("The verificationCertStores is misconfigured with both legacy and new formats: %+v", conf)).WithRemediation("Please provide only one format for the VerificationCertStores. Refer to the Notation Verifier configuration guide: https://ratify.dev/docs/plugins/verifier/notation#configuration") + logger.GetLogger(context.Background(), logOpt).Error(err) + return err + } else if !isCertStoresByType && isLegacyCertStore { + legacyCertStore, err := normalizeLegacyCertStore(conf) + if err != nil { + return err + } + // support legacy verfier config format for backward compatibility + // normalize : to ca: if no store type is provided + conf.VerificationCertStores = verificationCertStores{ + trustStoreTypeCA: legacyCertStore, } } - return verificationCertStore, nil + return nil } -// return true if string looks like a K8s namespaced resource. e.g. namespace/name -func isNamespacedNamed(name string) bool { - return strings.Contains(name, constants.NamespaceSeperator) +// TODO: remove this function once the refactor is done [refactore tracking issue](https://github.com/ratify-project/ratify/issues/1752) +func normalizeLegacyCertStore(conf *NotationPluginVerifierConfig) (map[string]interface{}, error) { + legacyCertStoreBytes, err := json.Marshal(conf.VerificationCertStores) + if err != nil { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to recognize `verificationCertStores` value of Notation Verifier configuration: %+v", conf.VerificationCertStores)).WithError(err) + } + var legacyCertStore map[string]interface{} + if err := json.Unmarshal(legacyCertStoreBytes, &legacyCertStore); err != nil { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to recognize `verificationCertStores` value of Notation Verifier configuration: %+v", conf.VerificationCertStores)).WithError(err) + } + return legacyCertStore, nil } diff --git a/pkg/verifier/notation/notation_test.go b/pkg/verifier/notation/notation_test.go index e8f155fd2..dc34a1f9d 100644 --- a/pkg/verifier/notation/notation_test.go +++ b/pkg/verifier/notation/notation_test.go @@ -22,17 +22,17 @@ import ( "reflect" "testing" - ratifyconfig "github.com/deislabs/ratify/config" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/homedir" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/verifier" sig "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + ratifyconfig "github.com/ratify-project/ratify/config" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/homedir" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/verifier" ) const ( @@ -71,6 +71,10 @@ var ( } invalidRef = common.Reference{ Original: "invalid", + Tag: "invalid", + } + invalidRef2 = common.Reference{ + Original: "invalid", } testNotationPluginVerifier notation.Verifier = mockNotationPluginVerifier{} validBlobDesc = ocispec.Descriptor{ @@ -136,7 +140,10 @@ func (s mockStore) GetConfig() *config.StoreConfig { return nil } -func (s mockStore) GetSubjectDescriptor(_ context.Context, _ common.Reference) (*ocispecs.SubjectDescriptor, error) { +func (s mockStore) GetSubjectDescriptor(_ context.Context, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) { + if subjectReference.Tag == "invalid" { + return nil, fmt.Errorf("cannot resolve digest for the subject reference") + } return &ocispecs.SubjectDescriptor{ Descriptor: ocispec.Descriptor{}, }, nil @@ -214,7 +221,22 @@ func TestParseVerifierConfig(t *testing.T) { name: "failed unmarshalling to notation config", configMap: map[string]interface{}{ "name": test, - "verificationCerts": test, + "verificationCerts": make(chan int), + }, + expectErr: true, + expect: nil, + }, + { + name: "failed unmarshalling to notation config", + configMap: map[string]interface{}{ + "name": test, + "verificationCertStores": verificationCertStores{ + "certstore1": []interface{}{"akv1", "akv2"}, + "ca": map[string]interface{}{ + "certstore1": []interface{}{"akv1", "akv2"}, + "certstore2": []interface{}{"akv3", "akv4"}, + }, + }, }, expectErr: true, expect: nil, @@ -248,7 +270,7 @@ func TestParseVerifierConfig(t *testing.T) { "name": test, "verificationCerts": []string{testPath}, "verificationCertStores": map[string][]string{ - "certstore1": {"defaultns/akv1", "akv2"}, + "certstore1": {"akv1", "akv2"}, "certstore2": {"akv3", "akv4"}, }, }, @@ -256,9 +278,11 @@ func TestParseVerifierConfig(t *testing.T) { expect: &NotationPluginVerifierConfig{ Name: test, VerificationCerts: []string{testPath, defaultCertDir}, - VerificationCertStores: map[string][]string{ - "certstore1": {"defaultns/akv1", "testns/akv2"}, - "certstore2": {"testns/akv3", "testns/akv4"}, + VerificationCertStores: verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"akv1", "akv2"}, + "certstore2": []interface{}{"akv3", "akv4"}, + }, }, }, }, @@ -300,6 +324,18 @@ func TestCreate(t *testing.T) { expect verifier.ReferenceVerifier expectErr bool }{ + { + name: "failed get verify service", + configMap: map[string]interface{}{ + "name": test, + "verificationCertStores": verificationCertStores{ + trustStoreTypeCA: verificationCertStores{ + "certstore1": []interface{}{"akv1", "akv2", 1}, + }, + }, + }, + expectErr: true, + }, { name: "failed parsing verifier config", configMap: map[string]interface{}{ @@ -348,13 +384,21 @@ func TestVerify(t *testing.T) { expectErr bool }{ { - name: "failed getting manifest", + name: "failed getting subject descriptor", ref: invalidRef, refBlob: []byte(""), manifest: ocispecs.ReferenceManifest{}, expect: failedResult, expectErr: true, }, + { + name: "failed getting manifest", + ref: invalidRef2, + refBlob: []byte(""), + manifest: ocispecs.ReferenceManifest{}, + expect: failedResult, + expectErr: true, + }, { name: "failed verifying signature", ref: validRef2, @@ -365,6 +409,26 @@ func TestVerify(t *testing.T) { expect: failedResult, expectErr: true, }, + { + name: "multiple signature blobs", + ref: validRef2, + refBlob: testRefBlob2, + manifest: ocispecs.ReferenceManifest{ + Blobs: []ocispec.Descriptor{validBlobDesc, validBlobDesc2}, + }, + expect: failedResult, + expectErr: true, + }, + { + name: "get blob content failed", + ref: validRef, + refBlob: nil, + manifest: ocispecs.ReferenceManifest{ + Blobs: []ocispec.Descriptor{validBlobDesc}, + }, + expect: failedResult, + expectErr: true, + }, { name: "verified successfully", ref: validRef, @@ -408,3 +472,89 @@ func TestGetNestedReferences(t *testing.T) { t.Fatalf("notation signature should not have nested references") } } + +func TestNormalizeVerificationCertsStores(t *testing.T) { + tests := []struct { + name string + conf *NotationPluginVerifierConfig + expectErr bool + }{ + { + name: "failed normalizaVerificationCertsStores in marshal function", + conf: &NotationPluginVerifierConfig{ + Name: test, + VerificationCerts: []string{testPath, defaultCertDir}, + VerificationCertStores: verificationCertStores{ + "certstore2": []interface{}{make(chan int)}, + }, + }, + expectErr: true, + }, + { + + name: "failed normalizaVerificationCertsStores with both old VerificationCertStores and new VerificationCertStores are provided", + conf: &NotationPluginVerifierConfig{ + Name: test, + VerificationCerts: []string{testPath, defaultCertDir}, + VerificationCertStores: verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"akv1", "akv2"}, + }, + "certstore2": []interface{}{"akv3", "akv4"}, + }, + }, + expectErr: true, + }, + { + name: "successfully normalizaVerificationCertsStores", + conf: &NotationPluginVerifierConfig{ + Name: test, + VerificationCerts: []string{testPath, defaultCertDir}, + VerificationCertStores: verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"akv1", "akv2"}, + "certstore2": []interface{}{"akv3", "akv4"}, + }, + }, + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := normalizeVerificationCertsStores(tt.conf) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestNormalizeLegacyCertStore(t *testing.T) { + tests := []struct { + name string + conf *NotationPluginVerifierConfig + expectErr bool + }{ + { + name: "successfully normalizaVerificationCertsStores", + conf: &NotationPluginVerifierConfig{ + Name: test, + VerificationCerts: []string{testPath, defaultCertDir}, + VerificationCertStores: verificationCertStores{ + "certstore2": []interface{}{make(chan int)}, + }, + }, + expectErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := normalizeLegacyCertStore(tt.conf) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} diff --git a/pkg/verifier/notation/pluginmanager.go b/pkg/verifier/notation/pluginmanager.go index 20cf097b5..0ab8c4d6f 100644 --- a/pkg/verifier/notation/pluginmanager.go +++ b/pkg/verifier/notation/pluginmanager.go @@ -22,6 +22,7 @@ import ( "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/plugin" + pl "github.com/notaryproject/notation-plugin-framework-go/plugin" ) const ( @@ -39,7 +40,7 @@ func NewRatifyPluginManager(directory string) *RatifyPluginManager { } // Returns a notation Plugin for the given name if present in the target directory -func (m *RatifyPluginManager) Get(ctx context.Context, name string) (plugin.Plugin, error) { +func (m *RatifyPluginManager) Get(ctx context.Context, name string) (pl.Plugin, error) { path, err := m.pluginFS.SysPath(notationPluginPrefix + name) if err != nil { return nil, err @@ -52,7 +53,7 @@ func (m *RatifyPluginManager) Get(ctx context.Context, name string) (plugin.Plug // Lists available notation plugins in the target directory func (m *RatifyPluginManager) List(_ context.Context) ([]string, error) { var plugins []string - err := fs.WalkDir(m.pluginFS, ".", func(dir string, d fs.DirEntry, err error) error { + err := fs.WalkDir(m.pluginFS, ".", func(_ string, d fs.DirEntry, err error) error { if err != nil { return err } diff --git a/pkg/verifier/notation/truststore.go b/pkg/verifier/notation/truststore.go index cf7373cef..e9490cb71 100644 --- a/pkg/verifier/notation/truststore.go +++ b/pkg/verifier/notation/truststore.go @@ -21,11 +21,12 @@ import ( "errors" "fmt" - "github.com/deislabs/ratify/internal/logger" - "github.com/deislabs/ratify/pkg/controllers" - "github.com/deislabs/ratify/pkg/keymanagementprovider" - "github.com/deislabs/ratify/pkg/utils" "github.com/notaryproject/notation-go/verifier/truststore" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/internal/logger" + "github.com/ratify-project/ratify/pkg/controllers" + "github.com/ratify-project/ratify/pkg/keymanagementprovider" + "github.com/ratify-project/ratify/pkg/utils" ) var logOpt = logger.Option{ @@ -34,44 +35,67 @@ var logOpt = logger.Option{ type trustStore struct { certPaths []string - certStores map[string][]string + certStores certStores +} + +func newTrustStore(certPaths []string, verificationCertStores verificationCertStores) (*trustStore, error) { + certStores, err := newCertStoreByType(verificationCertStores) + if err != nil { + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create the trust store from the verificationCertStores parameter in Notation Verifier configuration: %+v", verificationCertStores)).WithError(err).WithRemediation("Please check the value of the verificationCertStores parameter according to the Notation Verifier configuration guide: https://ratify.dev/docs/plugins/verifier/notation#configuration") + } + store := &trustStore{ + certPaths: certPaths, + certStores: certStores, + } + return store, nil } // trustStore implements GetCertificates API of X509TrustStore interface: [https://pkg.go.dev/github.com/notaryproject/notation-go@v1.0.0-rc.3/verifier/truststore#X509TrustStore] // Note: this api gets invoked when Ratify calls verify API, so the certificates // will be loaded for each signature verification. // And this API must follow the Notation Trust Store spec: https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#trust-store -func (s trustStore) GetCertificates(ctx context.Context, _ truststore.Type, namedStore string) ([]*x509.Certificate, error) { - certs, err := s.getCertificatesInternal(ctx, namedStore, controllers.GetCertificatesMap()) +func (s *trustStore) GetCertificates(ctx context.Context, trustStoreType truststore.Type, namedStore string) ([]*x509.Certificate, error) { + certs, err := s.getCertificatesInternal(ctx, trustStoreType, namedStore) if err != nil { return nil, err } return s.filterValidCerts(certs) } -func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore string, certificatesMap map[string][]*x509.Certificate) ([]*x509.Certificate, error) { +func (s *trustStore) getCertificatesInternal(ctx context.Context, storeType truststore.Type, namedStore string) ([]*x509.Certificate, error) { certs := make([]*x509.Certificate, 0) + certGroup := s.certStores.GetCertGroup(ctx, storeType, namedStore) // certs configured for this namedStore overrides cert path - if certGroup := s.certStores[namedStore]; len(certGroup) > 0 { + if len(certGroup) > 0 { for _, certStore := range certGroup { logger.GetLogger(ctx, logOpt).Debugf("truststore getting certStore %v", certStore) - result := keymanagementprovider.FlattenKMPMap(keymanagementprovider.GetCertificatesFromMap(certStore)) + certMap, kmpErr := keymanagementprovider.GetCertificatesFromMap(ctx, certStore) + if kmpErr != nil { + logger.GetLogger(ctx, logOpt).Warnf("unable to fetch certificates for Key Management Provider %+v: %v", certStore, kmpErr) + } + result := keymanagementprovider.FlattenKMPMap(certMap) + var certStoreErr error // notation verifier does not consider specific named/versioned certificates within a key management provider resource if len(result) == 0 { logger.GetLogger(ctx, logOpt).Warnf("no certificate fetched for Key Management Provider %+v", certStore) // check certificate store if key management provider does not have certificates. // NOTE: certificate store and key management provider should not be configured together. // User will be warned by the controller/CLI - result = certificatesMap[certStore] + if result, certStoreErr = controllers.NamespacedCertStores.GetCertsFromStore(ctx, certStore); certStoreErr != nil { + logger.GetLogger(ctx, logOpt).Warnf("unable to fetch certificates for Certificate Store %+v: %v", certStore, certStoreErr) + } if len(result) == 0 { logger.GetLogger(ctx, logOpt).Warnf("no certificate fetched for Certificate Store %+v", certStore) } } + if err := parseErrFromKmpAndCertStore(kmpErr, certStoreErr); err != nil { + return []*x509.Certificate{}, re.ErrorCodeCertInvalid.WithError(err).WithDetail(fmt.Sprintf("unable to fetch certificates from Key Management Provider and Certificate Store: %s", certStore)) + } certs = append(certs, result...) } if len(certs) == 0 { - return certs, fmt.Errorf("unable to fetch certificates for namedStore: %+v", namedStore) + return certs, fmt.Errorf("no certificates fetched in namedStore: %+v", namedStore) } } else { for _, path := range s.certPaths { @@ -87,7 +111,7 @@ func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore stri } // filterValidCerts keeps CA certificates and self-signed certs. -func (s trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certificate, error) { +func (s *trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certificate, error) { filteredCerts := make([]*x509.Certificate, 0) for _, cert := range certs { if !cert.IsCA { @@ -103,3 +127,33 @@ func (s trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certifi } return filteredCerts, nil } + +// parseErrFromKmpAndCertStore prioritizes and returns the appropriate error from either KMP or CertStore. +// If the certStoreErr is a reconcile error while kmpErr is not, it returns certStoreErr, +// otherwise it returns kmpErr. +// A reconcile error occurs during CR reconciliation, indicating that the resource +// was applied by users. +// Since key management provider and certificate store are mutually exclusive, +// a reconcile error will only originate from one of them. +// Consequently, the reconcile error from one resource takes precedence over +// errors from the other. +// Note: this method should be deleted once certificate store is completely removed. +func parseErrFromKmpAndCertStore(kmpErr, certStoreErr error) error { + if kmpErr == nil || certStoreErr == nil { + return nil + } + if isReconcileError(certStoreErr) && !isReconcileError(kmpErr) { + return certStoreErr + } + return kmpErr +} + +// isReconcileError checks if the error is a reconcile error. +// If the error is not NotFound or Forbidden, it is considered as an error from KMP/CertStore reconciliation. +func isReconcileError(err error) bool { + ratifyErr := &re.Error{} + if errors.As(err, ratifyErr) { + return ratifyErr.ErrorCode() != re.ErrorCodeNotFound && ratifyErr.ErrorCode() != re.ErrorCodeForbidden + } + return true +} diff --git a/pkg/verifier/notation/truststore_test.go b/pkg/verifier/notation/truststore_test.go index e2a6ce882..eb64c042d 100644 --- a/pkg/verifier/notation/truststore_test.go +++ b/pkg/verifier/notation/truststore_test.go @@ -17,9 +17,14 @@ import ( "context" "crypto/x509" "encoding/pem" + "errors" "os" "reflect" "testing" + + "github.com/notaryproject/notation-go/verifier/truststore" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/controllers" ) const ( @@ -29,27 +34,129 @@ const ( leafCertStr = "-----BEGIN CERTIFICATE-----\nMIIC7jCCAdagAwIBAgIURNiOON+GKbFS8yFxG6aMRoMg29cwDQYJKoZIhvcNAQEL\nBQAwKjEPMA0GA1UECgwGUmF0aWZ5MRcwFQYDVQQDDA5SYXRpZnkgUm9vdCBDQTAe\nFw0yMzAzMTAwMTEwMjlaFw0yNDAzMDkwMTEwMjlaMBkxFzAVBgNVBAMMDnJhdGlm\neS5kZWZhdWx0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUwGuWJ5\nDwspcL+7K+0XlkQ0g+sbyvfY0j0NdUmzsTPQNxsdUsbgYeidLnp0ruHKuHLq6Y9t\nEHUPF+A4S6lIi5OPhEkVxd/A5kzSX23WocJGmlew+Z/usjQdtiQ4ylYyHoHfPNrf\nrocbY21XQ3x2IM3yIo1QqSHNdCsE0UxsFI3j9XC+saIqrkr+k1SsI2AhhGRjXTke\nPNpOaJ+CRwsGz7PbnsACLbiAdOUJUGRkOlIl/p7hU2IcZUYTTGcKOFXP8DtbUJ+K\nQcBQOsfZyg36jvkpzmw/yAK00Uuc0X+5CaKfDKDw4MXvJFpRvG+Vc0mb5RB1E8py\neA6eXtUrZ5J4hQIDAQABox0wGzAZBgNVHREEEjAQgg5yYXRpZnkuZGVmYXVsdDAN\nBgkqhkiG9w0BAQsFAAOCAQEAHbiuodTJCDpCUu8tNjbww5ebTRznKZGnFmKQs5zU\no8KyCfLhR9/9zetDADwtWCQUvykFuHjx8tj41hALXXXafzkYPeTsfDmEoVWIJMQ1\nHqjbzc6bbxQAY7cC5HqM67fXYjPs1v3Uv3GZhF2EjBMqymKC+lZ/RSfktzN0iADn\nlwG9DrDibD739jBF09b3LHtdV55blN2wyB54DwMl5x0a4+bFYVj7fZzjctG4pH7T\njnBS69oxetPaqcRY7SQljJKaesiqx3CtiwVUpGTBexDtw6OIj9cWiCFT0lS3TfCh\nunfSQvVgezqE7txrFbXDQCgbl1jGagfia2ol7+IbLUR6TQ==\n-----END CERTIFICATE-----\n" ) -func TestGetCertificates_EmptyCertMap(t *testing.T) { - certStore := map[string][]string{} - certStore["store1"] = []string{"kv1"} - certStore["store2"] = []string{"kv2"} - store := &trustStore{ - certStores: certStore, +type mockCertStores struct { + certMap map[string][]*x509.Certificate + reconcileErr error +} + +func (m *mockCertStores) GetCertsFromStore(_ context.Context, storeName string) ([]*x509.Certificate, error) { + if m.reconcileErr != nil { + return nil, m.reconcileErr } + if m.certMap == nil { + return nil, nil + } + return m.certMap[storeName], nil +} - certificatesMap := map[string][]*x509.Certificate{} - if _, err := store.getCertificatesInternal(context.Background(), "store1", certificatesMap); err == nil { +func (m *mockCertStores) AddStore(_ string, _ []*x509.Certificate) {} + +func (m *mockCertStores) DeleteStore(_ string) {} + +func (m *mockCertStores) AddStoreError(_ string, _ error) {} + +func TestIsReconcileError(t *testing.T) { + err := errors.New("reconcile error") + if !isReconcileError(err) { + t.Fatalf("expected to be a reconcile error") + } + + err = re.ErrorCodeNotFound.WithDetail("not found") + if isReconcileError(err) { + t.Fatalf("expected not to be a reconcile error") + } +} + +func TestParseErrFromKmpAndCertStore(t *testing.T) { + reconcileErr := errors.New("reconcile error") + notFoundErr := re.ErrorCodeNotFound.WithDetail("not found") + tests := []struct { + name string + kmpErr error + certStoreErr error + expectedErr error + }{ + { + name: "nil errors", + kmpErr: nil, + certStoreErr: nil, + expectedErr: nil, + }, + { + name: "kmpErr should be returned", + kmpErr: reconcileErr, + certStoreErr: notFoundErr, + expectedErr: reconcileErr, + }, + { + name: "certStoreErr should be returned", + kmpErr: notFoundErr, + certStoreErr: reconcileErr, + expectedErr: reconcileErr, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseErrFromKmpAndCertStore(tt.kmpErr, tt.certStoreErr) + if !errors.Is(result, tt.expectedErr) { + t.Fatalf("expected %+v, got %+v", tt.expectedErr, result) + } + }) + } +} + +func TestGetCertificates_EmptyCertMap(t *testing.T) { + resetCertStore() + certStore := verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"akv1", "akv2"}, + "certstore2": []interface{}{"akv3", "akv4"}, + }, + } + store, err := newTrustStore([]string{}, certStore) + if err != nil { + panic("failed to parse verificationCertStores: " + err.Error()) + } + if _, err := store.getCertificatesInternal(context.Background(), truststore.TypeCA, "certstore1"); err == nil { t.Fatalf("error expected if cert map is empty") } } -func TestGetCertificates_NamedStore(t *testing.T) { - certStore := map[string][]string{} - certStore["store1"] = []string{"default/kv1"} - certStore["store2"] = []string{"projecta/kv2"} +func TestGetCertificates_ErrorFromKMPReconcile(t *testing.T) { + resetCertStore() + certStore := verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"default/kv1"}, + "certstore2": []interface{}{"projecta/kv2"}, + }, + } + store, err := newTrustStore(nil, certStore) + if err != nil { + t.Fatalf("failed to parse verificationCertStores: " + err.Error()) + } - store := &trustStore{ - certStores: certStore, + controllers.NamespacedCertStores = &mockCertStores{ + reconcileErr: errors.New("reconcile error"), + } + + // only the certificate in the specified namedStore should be returned + if _, err := store.getCertificatesInternal(context.Background(), truststore.TypeCA, "certstore1"); err == nil { + t.Fatalf("error expected if error from KMP reconcile") + } +} + +func TestGetCertificates_NamedStore(t *testing.T) { + resetCertStore() + certStore := verificationCertStores{ + trustStoreTypeCA: map[string]interface{}{ + "certstore1": []interface{}{"default/kv1"}, + "certstore2": []interface{}{"projecta/kv2"}, + }, + } + store, err := newTrustStore(nil, certStore) + if err != nil { + panic("failed to parse verificationCertStores: " + err.Error()) } kv1Cert := getCert(certStr) @@ -58,9 +165,12 @@ func TestGetCertificates_NamedStore(t *testing.T) { certificatesMap := map[string][]*x509.Certificate{} certificatesMap["default/kv1"] = []*x509.Certificate{kv1Cert} certificatesMap["projecta/kv2"] = []*x509.Certificate{kv2Cert} + controllers.NamespacedCertStores = &mockCertStores{ + certMap: certificatesMap, + } // only the certificate in the specified namedStore should be returned - result, _ := store.getCertificatesInternal(context.Background(), "store1", certificatesMap) + result, _ := store.getCertificatesInternal(context.Background(), truststore.TypeCA, "certstore1") expectedLen := 1 if len(result) != expectedLen { @@ -73,6 +183,7 @@ func TestGetCertificates_NamedStore(t *testing.T) { } func TestGetCertificates_certPath(t *testing.T) { + resetCertStore() // create a temporary certificate file tmpFile, err := os.CreateTemp("", "*.pem") if err != nil { @@ -83,9 +194,10 @@ func TestGetCertificates_certPath(t *testing.T) { } trustStore := &trustStore{ - certPaths: []string{tmpFile.Name()}, + certPaths: []string{tmpFile.Name()}, + certStores: certStoresByType{}, } - certs, err := trustStore.getCertificatesInternal(context.Background(), "", nil) + certs, err := trustStore.getCertificatesInternal(context.Background(), truststore.TypeCA, "") if err != nil { t.Fatalf("failed to get certs: %v", err) } @@ -155,3 +267,7 @@ func getCert(certString string) *x509.Certificate { return test } + +func resetCertStore() { + controllers.NamespacedCertStores = &mockCertStores{} +} diff --git a/pkg/verifier/plugin/args.go b/pkg/verifier/plugin/args.go index 893743295..b4f627369 100644 --- a/pkg/verifier/plugin/args.go +++ b/pkg/verifier/plugin/args.go @@ -19,7 +19,7 @@ import ( "fmt" "os" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" ) // VerifierPluginArgs describes arguments that are passed to the verifier plugin diff --git a/pkg/verifier/plugin/plugin.go b/pkg/verifier/plugin/plugin.go index 2d51ebe50..9357b46bd 100644 --- a/pkg/verifier/plugin/plugin.go +++ b/pkg/verifier/plugin/plugin.go @@ -22,15 +22,15 @@ import ( "os" "strings" - re "github.com/deislabs/ratify/errors" - "github.com/deislabs/ratify/pkg/common" - pluginCommon "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - rc "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - "github.com/deislabs/ratify/pkg/verifier/types" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + pluginCommon "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + rc "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + "github.com/ratify-project/ratify/pkg/verifier/types" ) // VerifierPlugin describes a verifier that is implemented by invoking the plugins diff --git a/pkg/verifier/plugin/plugin_test.go b/pkg/verifier/plugin/plugin_test.go index dd6c2faf6..50da60188 100644 --- a/pkg/verifier/plugin/plugin_test.go +++ b/pkg/verifier/plugin/plugin_test.go @@ -20,9 +20,9 @@ import ( "strings" "testing" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - sm "github.com/deislabs/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + sm "github.com/ratify-project/ratify/pkg/referrerstore/mocks" ) const ( @@ -68,10 +68,10 @@ func TestNewVerifier_Expected(t *testing.T) { func TestVerify_IsSuccessTrue_Expected(t *testing.T) { testPlugin := "test-plugin" testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } @@ -145,10 +145,10 @@ func TestVerify_IsSuccessTrue_Expected(t *testing.T) { func TestVerify_IsSuccessFalse_Expected(t *testing.T) { testPlugin := "test-plugin" testExecutor := &TestExecutor{ - find: func(plugin string, paths []string) (string, error) { + find: func(_ string, _ []string) (string, error) { return testPath, nil }, - execute: func(ctx context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { + execute: func(_ context.Context, pluginPath string, cmdArgs []string, stdinData []byte, environ []string) ([]byte, error) { if pluginPath != testPath { t.Fatalf("mismatch in plugin path expected %s actual %s", testPath, pluginPath) } diff --git a/pkg/verifier/plugin/skel/skel.go b/pkg/verifier/plugin/skel/skel.go index 834c69166..05b0ebd08 100644 --- a/pkg/verifier/plugin/skel/skel.go +++ b/pkg/verifier/plugin/skel/skel.go @@ -23,17 +23,17 @@ import ( "os" "strings" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/common/plugin" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - storeConfig "github.com/deislabs/ratify/pkg/referrerstore/config" - "github.com/deislabs/ratify/pkg/referrerstore/factory" - "github.com/deislabs/ratify/pkg/utils" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/config" - vp "github.com/deislabs/ratify/pkg/verifier/plugin" - "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/common/plugin" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + storeConfig "github.com/ratify-project/ratify/pkg/referrerstore/config" + "github.com/ratify-project/ratify/pkg/referrerstore/factory" + "github.com/ratify-project/ratify/pkg/utils" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/config" + vp "github.com/ratify-project/ratify/pkg/verifier/plugin" + "github.com/ratify-project/ratify/pkg/verifier/types" ) type pcontext struct { diff --git a/pkg/verifier/plugin/skel/skel_test.go b/pkg/verifier/plugin/skel/skel_test.go index 97a78b411..0807b7311 100644 --- a/pkg/verifier/plugin/skel/skel_test.go +++ b/pkg/verifier/plugin/skel/skel_test.go @@ -23,18 +23,18 @@ import ( "strings" "testing" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - sp "github.com/deislabs/ratify/pkg/referrerstore/plugin" - "github.com/deislabs/ratify/pkg/verifier/plugin" - "github.com/deislabs/ratify/pkg/verifier/types" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + sp "github.com/ratify-project/ratify/pkg/referrerstore/plugin" + "github.com/ratify-project/ratify/pkg/verifier/plugin" + "github.com/ratify-project/ratify/pkg/verifier/types" - "github.com/deislabs/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier" // This import is required to utilize the oras built-in referrer store - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" - "github.com/deislabs/ratify/pkg/utils" + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" + "github.com/ratify-project/ratify/pkg/utils" ) const ( @@ -60,7 +60,7 @@ func teardown() { } func TestPluginMain_VerifyReference_ReturnsExpected(t *testing.T) { - verifyReference := func(args *CmdArgs, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { + verifyReference := func(_ *CmdArgs, _ common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { if referenceDescriptor.ArtifactType != "test-type" { t.Fatalf("expected artifact type %s actual %s", "test-type", referenceDescriptor.ArtifactType) } @@ -107,7 +107,7 @@ func TestPluginMain_VerifyReference_ReturnsExpected(t *testing.T) { } func TestPluginMain_VerifyReference_CanUseBuiltinStores(t *testing.T) { - verifyReference := func(args *CmdArgs, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { + verifyReference := func(_ *CmdArgs, _ common.Reference, _ ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { // expect to find a builtin store and fail if it was configured as a plugin if _, ok := referrerStore.(*sp.StorePlugin); ok { t.Fatalf("expected store to be builtin") @@ -144,7 +144,7 @@ func TestPluginMain_VerifyReference_CanUseBuiltinStores(t *testing.T) { } func TestPluginMain_ErrorCases(t *testing.T) { - verifyReference := func(args *CmdArgs, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { + verifyReference := func(_ *CmdArgs, _ common.Reference, _ ocispecs.ReferenceDescriptor, _ referrerstore.ReferrerStore) (*verifier.VerifierResult, error) { return nil, fmt.Errorf("simulated error") } environment := map[string]string{ diff --git a/pkg/verifier/result.go b/pkg/verifier/result.go new file mode 100644 index 000000000..dff28e9e8 --- /dev/null +++ b/pkg/verifier/result.go @@ -0,0 +1,62 @@ +/* +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 verifier + +import "github.com/ratify-project/ratify/errors" + +// VerifierResult describes the result of verifying a reference manifest for a subject. +// Note: This struct is used to represent the result of verification in v0. +type VerifierResult struct { //nolint:revive // ignore linter to have unique type name + Subject string `json:"subject,omitempty"` + IsSuccess bool `json:"isSuccess"` + // Name will be deprecated in v2, tracking issue: https://github.com/ratify-project/ratify/issues/1707 + Name string `json:"name,omitempty"` + VerifierName string `json:"verifierName,omitempty"` + // Type will be deprecated in v2, tracking issue: https://github.com/ratify-project/ratify/issues/1707 + Type string `json:"type,omitempty"` + VerifierType string `json:"verifierType,omitempty"` + ReferenceDigest string `json:"referenceDigest,omitempty"` + ArtifactType string `json:"artifactType,omitempty"` + Message string `json:"message,omitempty"` + ErrorReason string `json:"errorReason,omitempty"` + Remediation string `json:"remediation,omitempty"` + Extensions interface{} `json:"extensions,omitempty"` + NestedResults []VerifierResult `json:"nestedResults,omitempty"` +} + +// NewVerifierResult creates a new VerifierResult object with the given parameters. +func NewVerifierResult(subject, verifierName, verifierType, message string, isSuccess bool, err *errors.Error, extensions interface{}) VerifierResult { + var errorReason, remediation string + if err != nil { + if err.GetDetail() != "" { + message = err.GetDetail() + } + errorReason = err.GetErrorReason() + remediation = err.GetRemediation() + } + return VerifierResult{ + Subject: subject, + IsSuccess: isSuccess, + Name: verifierName, + Type: verifierType, + VerifierName: verifierName, + VerifierType: verifierType, + Message: message, + ErrorReason: errorReason, + Remediation: remediation, + Extensions: extensions, + } +} diff --git a/pkg/verifier/result_test.go b/pkg/verifier/result_test.go new file mode 100644 index 000000000..64efd2c52 --- /dev/null +++ b/pkg/verifier/result_test.go @@ -0,0 +1,83 @@ +/* +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 verifier + +import ( + "fmt" + "testing" + + "github.com/ratify-project/ratify/errors" +) + +const ( + testMsg1 = "test message 1" + testMsg2 = "test message 2" + testErrReason = "test error reason" + testRemediation = "test remediation" +) + +func TestNewVerifierResult(t *testing.T) { + tests := []struct { + name string + message string + err errors.Error + expectedMsg string + expectedErrReason string + expectedRemediation string + }{ + { + name: "nil error", + message: testMsg1, + err: errors.Error{}, + expectedMsg: testMsg1, + }, + { + name: "error without detail", + message: testMsg1, + err: errors.ErrorCodeUnknown.WithError(fmt.Errorf(testErrReason)).WithRemediation(testRemediation), + expectedMsg: testMsg1, + expectedErrReason: testErrReason, + expectedRemediation: testRemediation, + }, + { + name: "error with detail", + message: testMsg1, + err: errors.ErrorCodeUnknown.WithError(fmt.Errorf(testErrReason)).WithRemediation(testRemediation).WithDetail(testMsg2), + expectedMsg: testMsg2, + expectedErrReason: testErrReason, + expectedRemediation: testRemediation, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := &tt.err + if tt.err == (errors.Error{}) { + err = nil + } + + result := NewVerifierResult("", "", "", tt.message, false, err, nil) + if result.Message != tt.expectedMsg { + t.Errorf("expected message %s, got %s", tt.expectedMsg, result.Message) + } + if result.ErrorReason != tt.expectedErrReason { + t.Errorf("expected error reason %s, got %s", tt.expectedErrReason, result.ErrorReason) + } + if result.Remediation != tt.expectedRemediation { + t.Errorf("expected remediation %s, got %s", tt.expectedRemediation, result.Remediation) + } + }) + } +} diff --git a/pkg/verifier/types/types.go b/pkg/verifier/types/types.go index df9890efb..d21e9b098 100644 --- a/pkg/verifier/types/types.go +++ b/pkg/verifier/types/types.go @@ -19,7 +19,8 @@ import ( "encoding/json" "io" - "github.com/deislabs/ratify/pkg/verifier" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/verifier" ) const ( @@ -47,11 +48,17 @@ const ( // VerifierResult describes the verification result returned from the verifier plugin type VerifierResult struct { - IsSuccess bool `json:"isSuccess"` - Message string `json:"message"` - Name string `json:"name"` - Type string `json:"type,omitempty"` - Extensions interface{} `json:"extensions"` + IsSuccess bool `json:"isSuccess"` + Message string `json:"message"` + ErrorReason string `json:"errorReason,omitempty"` + Remediation string `json:"remediation,omitempty"` + // Name will be deprecated in v2, tracking issue: https://github.com/ratify-project/ratify/issues/1707 + Name string `json:"name"` + VerifierName string `json:"verifierName,omitempty"` + // Type will be deprecated in v2, tracking issue: https://github.com/ratify-project/ratify/issues/1707 + Type string `json:"type,omitempty"` + VerifierType string `json:"verifierType,omitempty"` + Extensions interface{} `json:"extensions"` } // GetVerifierResult encodes the given JSON data into verify result object @@ -61,11 +68,13 @@ func GetVerifierResult(result []byte) (*verifier.VerifierResult, error) { return nil, err } return &verifier.VerifierResult{ - IsSuccess: vResult.IsSuccess, - Message: vResult.Message, - Name: vResult.Name, - Type: vResult.Type, - Extensions: vResult.Extensions, + IsSuccess: vResult.IsSuccess, + Message: vResult.Message, + Name: vResult.Name, + Type: vResult.Type, + VerifierName: vResult.Name, + VerifierType: vResult.Type, + Extensions: vResult.Extensions, }, nil } @@ -74,13 +83,42 @@ func WriteVerifyResultResult(result *verifier.VerifierResult, w io.Writer) error return json.NewEncoder(w).Encode(result) } +// CreateVerifierResult creates a new verifier result object from given input. +func CreateVerifierResult(verifierName, verifierType, message string, isSuccess bool, err *errors.Error) VerifierResult { + var errorReason string + var remediation string + if err != nil { + if err.GetDetail() != "" { + message = err.GetDetail() + } + errorReason = err.GetErrorReason() + remediation = err.GetRemediation() + } + + return VerifierResult{ + IsSuccess: isSuccess, + Name: verifierName, + Type: verifierType, + VerifierName: verifierName, + VerifierType: verifierType, + Message: message, + ErrorReason: errorReason, + Remediation: remediation, + } +} + // NewVerifierResult creates a new verifier result object from the given // verifier.VerifierResult. func NewVerifierResult(result verifier.VerifierResult) VerifierResult { return VerifierResult{ - IsSuccess: result.IsSuccess, - Message: result.Message, - Name: result.Name, - Extensions: result.Extensions, + IsSuccess: result.IsSuccess, + Message: result.Message, + Name: result.Name, + Type: result.Type, + VerifierName: result.VerifierName, + VerifierType: result.VerifierType, + Extensions: result.Extensions, + ErrorReason: result.ErrorReason, + Remediation: result.Remediation, } } diff --git a/pkg/verifier/types/types_test.go b/pkg/verifier/types/types_test.go new file mode 100644 index 000000000..ce1cd39f6 --- /dev/null +++ b/pkg/verifier/types/types_test.go @@ -0,0 +1,83 @@ +/* +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 types + +import ( + "fmt" + "testing" + + "github.com/ratify-project/ratify/errors" +) + +const ( + testMsg1 = "test message 1" + testMsg2 = "test message 2" + testErrReason = "test error reason" + testRemediation = "test remediation" +) + +func TestCreateVerifierResult(t *testing.T) { + tests := []struct { + name string + message string + err errors.Error + expectedMsg string + expectedErrReason string + expectedRemediation string + }{ + { + name: "nil error", + message: testMsg1, + err: errors.Error{}, + expectedMsg: testMsg1, + }, + { + name: "error without detail", + message: testMsg1, + err: errors.ErrorCodeUnknown.WithError(fmt.Errorf(testErrReason)).WithRemediation(testRemediation), + expectedMsg: testMsg1, + expectedErrReason: testErrReason, + expectedRemediation: testRemediation, + }, + { + name: "error with detail", + message: testMsg1, + err: errors.ErrorCodeUnknown.WithError(fmt.Errorf(testErrReason)).WithRemediation(testRemediation).WithDetail(testMsg2), + expectedMsg: testMsg2, + expectedErrReason: testErrReason, + expectedRemediation: testRemediation, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := &tt.err + if tt.err == (errors.Error{}) { + err = nil + } + + result := CreateVerifierResult("", "", tt.message, false, err) + if result.Message != tt.expectedMsg { + t.Errorf("expected message %s, got %s", tt.expectedMsg, result.Message) + } + if result.ErrorReason != tt.expectedErrReason { + t.Errorf("expected error reason %s, got %s", tt.expectedErrReason, result.ErrorReason) + } + if result.Remediation != tt.expectedRemediation { + t.Errorf("expected remediation %s, got %s", tt.expectedRemediation, result.Remediation) + } + }) + } +} diff --git a/pkg/verifier/utils/utils.go b/pkg/verifier/utils/utils.go new file mode 100644 index 000000000..7e128e6c7 --- /dev/null +++ b/pkg/verifier/utils/utils.go @@ -0,0 +1,27 @@ +/* +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 utils + +import ( + "strings" + + "github.com/ratify-project/ratify/internal/constants" +) + +// return true if string looks like a K8s namespaced resource. e.g. namespace/name +func IsNamespacedNamed(name string) bool { + return strings.Contains(name, constants.NamespaceSeperator) +} diff --git a/pkg/verifiercache/api.go b/pkg/verifiercache/api.go index a15ba76ee..03e14a4ce 100644 --- a/pkg/verifiercache/api.go +++ b/pkg/verifiercache/api.go @@ -19,7 +19,7 @@ import ( "context" "time" - et "github.com/deislabs/ratify/pkg/executor/types" + et "github.com/ratify-project/ratify/pkg/executor/types" ) // VerifierCache is an interface that defines methods to set/get results from a cache diff --git a/pkg/verifiercache/memory/memorycache.go b/pkg/verifiercache/memory/memorycache.go index 5ece3b0ea..a75126023 100644 --- a/pkg/verifiercache/memory/memorycache.go +++ b/pkg/verifiercache/memory/memorycache.go @@ -19,7 +19,7 @@ import ( "context" "time" - et "github.com/deislabs/ratify/pkg/executor/types" + et "github.com/ratify-project/ratify/pkg/executor/types" ) // Cache describes an in-memory cache with automatic expiration diff --git a/plugins/referrerstore/sample/sample.go b/plugins/referrerstore/sample/sample.go index 0eeef28f6..0a26d1fb3 100644 --- a/plugins/referrerstore/sample/sample.go +++ b/plugins/referrerstore/sample/sample.go @@ -16,12 +16,12 @@ limitations under the License. package main import ( - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/referrerstore/plugin/skel" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/referrerstore/plugin/skel" ) func main() { diff --git a/plugins/verifier/licensechecker/licensechecker.go b/plugins/verifier/licensechecker/licensechecker.go index f38ca9037..0e252f2a4 100644 --- a/plugins/verifier/licensechecker/licensechecker.go +++ b/plugins/verifier/licensechecker/licensechecker.go @@ -20,14 +20,14 @@ import ( "encoding/json" "fmt" - "github.com/deislabs/ratify/plugins/verifier/licensechecker/utils" - - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" + "github.com/ratify-project/ratify/plugins/verifier/licensechecker/utils" + + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" ) type PluginConfig struct { diff --git a/plugins/verifier/sample/sample.go b/plugins/verifier/sample/sample.go index 8fc48554a..1fb80f9b8 100644 --- a/plugins/verifier/sample/sample.go +++ b/plugins/verifier/sample/sample.go @@ -20,12 +20,12 @@ import ( "fmt" "os" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/common/plugin/logger" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/common/plugin/logger" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" ) type PluginConfig struct { diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index df93746b7..70a18ad2a 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -22,15 +22,17 @@ import ( "fmt" "strings" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - "github.com/deislabs/ratify/plugins/verifier/sbom/utils" + "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + "github.com/ratify-project/ratify/plugins/verifier/sbom/utils" // This import is required to utilize the oras built-in referrer store - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" + re "github.com/ratify-project/ratify/errors" + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" jsonLoader "github.com/spdx/tools-golang/json" "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/spdx/v2/v2_3" @@ -82,20 +84,15 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe ctx := context.Background() referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Error fetching reference manifest for subject: %s reference descriptor: %v, err: %v", subjectReference, referenceDescriptor.Descriptor, err), - }, nil + storeErr := re.ErrorCodeGetReferenceManifestFailure.WithDetail(fmt.Sprintf("Failed to fetch reference manifest for subject: %s reference descriptor: %v", subjectReference, referenceDescriptor.Descriptor)).WithError(err) + result := verifier.NewVerifierResult("", input.Name, verifierType, "", false, &storeErr, nil) + return &result, nil } if len(referenceManifest.Blobs) == 0 { - return &verifier.VerifierResult{ - Name: input.Name, - IsSuccess: false, - Message: fmt.Sprintf("SBOM validation failed: no layers found in manifest for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), - }, nil + noBlobErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("No layers found in manifest for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String())) + result := verifier.NewVerifierResult("", input.Name, verifierType, "SBOM validation failed", false, &noBlobErr, nil) + return &result, nil } artifactType := referenceDescriptor.ArtifactType @@ -103,33 +100,22 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe refBlob, err := referrerStore.GetBlobContent(ctx, subjectReference, blobDesc.Digest) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Error fetching blob for subject: %s digest: %s, err: %v", subjectReference, blobDesc.Digest, err), - }, nil + storeErr := re.ErrorCodeGetBlobContentFailure.WithDetail(fmt.Sprintf("Failed to fetch blob for subject: %s digest: %s", subjectReference, blobDesc.Digest)).WithError(err) + result := verifier.NewVerifierResult("", input.Name, verifierType, "", false, &storeErr, nil) + return &result, nil } switch artifactType { case SpdxJSONMediaType: return processSpdxJSONMediaType(input.Name, verifierType, refBlob, input.DisallowedLicenses, input.DisallowedPackages), nil default: - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Unsupported artifactType: %s", artifactType), - }, nil + storeErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Unsupported artifactType: %s", artifactType)) + result := verifier.NewVerifierResult("", input.Name, verifierType, "Failed to process SBOM blobs.", false, &storeErr, nil) + return &result, nil } } - - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: true, - Message: "SBOM verification success. No license or package violation found.", - }, nil + result := verifier.NewVerifierResult("", input.Name, verifierType, "SBOM verification success. No license or package violation found.", true, nil, nil) + return &result, nil } // getViolations returns the package and license violations based on the deny list @@ -177,31 +163,26 @@ func processSpdxJSONMediaType(name string, verifierType string, refBlob []byte, } if len(licenseViolation) != 0 || len(packageViolation) != 0 { - return &verifier.VerifierResult{ - Name: name, - IsSuccess: false, - Extensions: extensionData, - Message: "SBOM validation failed. Please review extensions data for license and package violation found.", - } + sbomErr := errors.ErrorCodeVerifyPluginFailure.WithDetail("License or package violation found.").WithRemediation("Please review extensions data for license and package violation found.") + result := verifier.NewVerifierResult("", name, verifierType, "SBOM validation failed", false, &sbomErr, extensionData) + return &result } } - return &verifier.VerifierResult{ - Name: name, - Type: verifierType, - IsSuccess: true, - Extensions: map[string]interface{}{ - CreationInfo: spdxDoc.CreationInfo, - }, - Message: "SBOM verification success. No license or package violation found.", - } - } - return &verifier.VerifierResult{ - Name: name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("SBOM failed to parse: %v", err), + result := verifier.NewVerifierResult( + "", + name, + verifierType, + "SBOM verification success. No license or package violation found.", + true, + nil, + map[string]interface{}{CreationInfo: spdxDoc.CreationInfo}, + ) + return &result } + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("failed to verify artifact: %s", name)).WithError(err) + result := verifier.NewVerifierResult("", name, verifierType, "", false, &verifierErr, nil) + return &result } // iterate through all package info and check against the deny list diff --git a/plugins/verifier/sbom/sbom_test.go b/plugins/verifier/sbom/sbom_test.go index 042c2c378..8609fc500 100644 --- a/plugins/verifier/sbom/sbom_test.go +++ b/plugins/verifier/sbom/sbom_test.go @@ -15,14 +15,24 @@ limitations under the License. package main import ( + "errors" + "fmt" "os" "path/filepath" "strings" "testing" - "github.com/deislabs/ratify/plugins/verifier/sbom/utils" + "github.com/opencontainers/go-digest" + oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" + "github.com/ratify-project/ratify/plugins/verifier/sbom/utils" ) +const mediaType string = "application/vnd.syft+json" + func TestProcessSPDXJsonMediaType(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", "bom.json")) if err != nil { @@ -41,8 +51,178 @@ func TestProcessInvalidSPDXJsonMediaType(t *testing.T) { } report := processSpdxJSONMediaType("test", "", b, nil, nil) - if !strings.Contains(report.Message, "SBOM failed to parse") { - t.Fatalf("expected to have an error processing spdx json file: %s", filepath.Join("testdata", "bom.json")) + if !strings.Contains(report.Message, "failed to verify artifact") { + t.Fatalf("report message: %s does not contain expected error message", report.Message) + } + if report.ErrorReason != "JSON document does not contain spdxVersion field" { + t.Fatalf("expected error reason: %s, got: %s", "JSON document does not contain spdxVersion field", report.ErrorReason) + } +} + +func TestVerifyReference(t *testing.T) { + manifestDigest := digest.FromString("test_manifest_digest") + manifestDigest2 := digest.FromString("test_manifest_digest_2") + blobDigest := digest.FromString("test_blob_digest") + blobDigest2 := digest.FromString("test_blob_digest_2") + type args struct { + stdinData string + referenceManifest ocispecs.ReferenceManifest + blobContent string + refDesc ocispecs.ReferenceDescriptor + } + type want struct { + message string + errorReason string + err error + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "invalid stdin data", + args: args{}, + want: want{ + err: errors.New("failed to parse stdin for the input: unexpected end of JSON input"), + }, + }, + { + name: "failed to get reference manifest", + args: args{ + stdinData: `{"config":{"name":"sbom","type":"sbom"}}`, + referenceManifest: ocispecs.ReferenceManifest{}, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest2, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + message: "Failed to fetch reference manifest for subject: test_subject reference descriptor: { sha256:b55e209647d87fcd95a94c59ff4d342e42bf10f02a7c10b5192131f8d959ff5a 0 [] map[] [] }", + errorReason: "manifest not found", + }, + }, + { + name: "empty blobs", + args: args{ + stdinData: `{"config":{"name":"sbom","type":"sbom"}}`, + referenceManifest: ocispecs.ReferenceManifest{}, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + message: "SBOM validation failed", + errorReason: fmt.Sprintf("No layers found in manifest for referrer %s@%s", "test_subject_path", manifestDigest.String()), + }, + }, + { + name: "get blob content error", + args: args{ + stdinData: `{"config":{"name":"sbom","type":"sbom"}}`, + referenceManifest: ocispecs.ReferenceManifest{ + Blobs: []oci.Descriptor{ + { + MediaType: mediaType, + Digest: blobDigest2, + }, + }, + }, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + message: fmt.Sprintf("Failed to fetch blob for subject: test_subject digest: %s", blobDigest2.String()), + errorReason: "blob not found", + }, + }, + { + name: "unsupported artifactType", + args: args{ + stdinData: `{"config":{"name":"sbom","type":"sbom"}}`, + referenceManifest: ocispecs.ReferenceManifest{ + Blobs: []oci.Descriptor{ + { + MediaType: mediaType, + Digest: blobDigest, + }, + }, + }, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + message: "Failed to process SBOM blobs.", + errorReason: "Unsupported artifactType: application/vnd.syft+json", + }, + }, + { + name: "process spdx json mediaType error", + args: args{ + stdinData: `{"config":{"name":"sbom","type":"sbom"}}`, + referenceManifest: ocispecs.ReferenceManifest{ + Blobs: []oci.Descriptor{ + { + MediaType: mediaType, + Digest: blobDigest, + }, + }, + }, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: SpdxJSONMediaType, + }, + }, + want: want{ + message: "failed to verify artifact: sbom", + errorReason: "unexpected end of JSON input", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmdArgs := &skel.CmdArgs{ + Version: "1.0.0", + Subject: "test_subject", + StdinData: []byte(tt.args.stdinData), + } + testStore := &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{manifestDigest: tt.args.referenceManifest}, + Blobs: map[digest.Digest][]byte{blobDigest: []byte(tt.args.blobContent)}, + } + subjectRef := common.Reference{ + Path: "test_subject_path", + Original: "test_subject", + } + verifierResult, err := VerifyReference(cmdArgs, subjectRef, tt.args.refDesc, testStore) + if err != nil && err.Error() != tt.want.err.Error() { + t.Fatalf("verifyReference() error = %v, wantErr %v", err, tt.want.err) + } + if verifierResult != nil { + if verifierResult.Message != tt.want.message { + t.Fatalf("verifyReference() verifier report message = %s, want = %s", verifierResult.Message, tt.want.message) + } + if verifierResult.ErrorReason != tt.want.errorReason { + t.Fatalf("verifyReference() verifier report error reason = %s, want = %s", verifierResult.ErrorReason, tt.want.errorReason) + } + } + }) } } diff --git a/plugins/verifier/sbom/testdata/osv-scanner.toml b/plugins/verifier/sbom/testdata/osv-scanner.toml new file mode 100644 index 000000000..837d88470 --- /dev/null +++ b/plugins/verifier/sbom/testdata/osv-scanner.toml @@ -0,0 +1,19 @@ +[[IgnoredVulns]] +id = "CVE-2022-48174" +reason = "Test manifest file(syftbom.spdx.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42366" +reason = "Test manifest file(syftbom.spdx.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42363" +reason = "Test manifest file(syftbom.spdx.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42364" +reason = "Test manifest file(syftbom.spdx.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42365" +reason = "Test manifest file(syftbom.spdx.json)" \ No newline at end of file diff --git a/plugins/verifier/schemavalidator/schema_validator.go b/plugins/verifier/schemavalidator/schema_validator.go index a5ab0edd4..d31886222 100644 --- a/plugins/verifier/schemavalidator/schema_validator.go +++ b/plugins/verifier/schemavalidator/schema_validator.go @@ -20,13 +20,15 @@ import ( "encoding/json" "fmt" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" - "github.com/deislabs/ratify/plugins/verifier/schemavalidator/schemavalidation" + "github.com/ratify-project/ratify/errors" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" + "github.com/ratify-project/ratify/plugins/verifier/schemavalidator/schemavalidation" ) type PluginConfig struct { @@ -71,12 +73,9 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe } if len(referenceManifest.Blobs) == 0 { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("schema validation failed: no blobs found for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), - }, nil + noBlobErr := errors.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("No blobs found for referrer %s@%s.", subjectReference.Path, referenceDescriptor.Digest.String())) + result := verifier.NewVerifierResult("", input.Name, verifierType, "", false, &noBlobErr, nil) + return &result, nil } for _, blobDesc := range referenceManifest.Blobs { @@ -87,21 +86,14 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe err = processMediaType(schemaMap, blobDesc.MediaType, refBlob) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("schema validation failed for digest:[%s],media type:[%s],parse errors:[%v]", blobDesc.Digest, blobDesc.MediaType, err.Error()), - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("schema validation failed for digest:[%s], media type:[%s].", blobDesc.Digest, blobDesc.MediaType)).WithError(err) + result := verifier.NewVerifierResult("", input.Name, verifierType, "", false, &verifierErr, nil) + return &result, nil } } - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: true, - Message: "schema validation passed for configured media types", - }, nil + result := verifier.NewVerifierResult("", input.Name, verifierType, "schema validation passed for configured media types", true, nil, nil) + return &result, nil } func processMediaType(schemaMap map[string]string, mediaType string, refBlob []byte) error { diff --git a/plugins/verifier/schemavalidator/schema_validator_test.go b/plugins/verifier/schemavalidator/schema_validator_test.go new file mode 100644 index 000000000..3448ec897 --- /dev/null +++ b/plugins/verifier/schemavalidator/schema_validator_test.go @@ -0,0 +1,170 @@ +/* +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 main + +import ( + "errors" + "fmt" + "testing" + + "github.com/opencontainers/go-digest" + oci "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" +) + +const mediaType string = "application/schema+json" + +func TestVerifyReference(t *testing.T) { + manifestDigest := digest.FromString("test_manifest_digest") + manifestDigest2 := digest.FromString("test_manifest_digest_2") + blobDigest := digest.FromString("test_blob_digest") + blobDigest2 := digest.FromString("test_blob_digest_2") + type args struct { + stdinData string + referenceManifest ocispecs.ReferenceManifest + blobContent string + refDesc ocispecs.ReferenceDescriptor + } + type want struct { + message string + errorReason string + err error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "invalid stdin data", + args: args{}, + want: want{ + err: errors.New("failed to parse stdin for the input: unexpected end of JSON input"), + }, + }, + { + name: "failed to get reference manifest", + args: args{ + stdinData: `{"config":{"name":"schemavalidator","type":"schemavalidator"}}`, + referenceManifest: ocispecs.ReferenceManifest{}, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest2, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + err: errors.New("error fetching reference manifest for subject: test_subject reference descriptor: { sha256:b55e209647d87fcd95a94c59ff4d342e42bf10f02a7c10b5192131f8d959ff5a 0 [] map[] [] }"), + }, + }, + { + name: "empty blobs", + args: args{ + stdinData: `{"config":{"name":"schemavalidator","type":"schemavalidator"}}`, + referenceManifest: ocispecs.ReferenceManifest{}, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + errorReason: fmt.Sprintf("No blobs found for referrer %s@%s.", "test_subject_path", manifestDigest.String()), + }, + }, + { + name: "get blob content error", + args: args{ + stdinData: `{"config":{"name":"schemavalidator","type":"schemavalidator"}}`, + referenceManifest: ocispecs.ReferenceManifest{ + Blobs: []oci.Descriptor{ + { + MediaType: mediaType, + Digest: blobDigest2, + }, + }, + }, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + err: fmt.Errorf("error fetching blob for subject:[%s] digest:[%s]", "test_subject", blobDigest2.String()), + }, + }, + { + name: "process mediaType error", + args: args{ + stdinData: `{"config":{"name":"schemavalidator","type":"schemavalidator"}}`, + referenceManifest: ocispecs.ReferenceManifest{ + Blobs: []oci.Descriptor{ + { + MediaType: mediaType, + Digest: blobDigest, + }, + }, + }, + refDesc: ocispecs.ReferenceDescriptor{ + Descriptor: oci.Descriptor{ + Digest: manifestDigest, + }, + ArtifactType: mediaType, + }, + }, + want: want{ + message: fmt.Sprintf("schema validation failed for digest:[%s], media type:[%s].", blobDigest.String(), mediaType), + errorReason: "media type not configured for plugin:[application/schema+json]", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmdArgs := &skel.CmdArgs{ + Version: "1.0.0", + Subject: "test_subject", + StdinData: []byte(tt.args.stdinData), + } + testStore := &mocks.MemoryTestStore{ + Manifests: map[digest.Digest]ocispecs.ReferenceManifest{manifestDigest: tt.args.referenceManifest}, + Blobs: map[digest.Digest][]byte{blobDigest: []byte(tt.args.blobContent)}, + } + subjectRef := common.Reference{ + Path: "test_subject_path", + Original: "test_subject", + } + verifierResult, err := VerifyReference(cmdArgs, subjectRef, tt.args.refDesc, testStore) + if err != nil && err.Error() != tt.want.err.Error() { + t.Fatalf("verifyReference() error = %v, wantErr %v", err, tt.want.err) + } + if verifierResult != nil { + if verifierResult.Message != tt.want.message { + t.Fatalf("verifyReference() verifier report message = %s, want = %s", verifierResult.Message, tt.want.message) + } + if verifierResult.ErrorReason != tt.want.errorReason { + t.Fatalf("verifyReference() verifier report error reason = %s, want = %s", verifierResult.ErrorReason, tt.want.errorReason) + } + } + }) + } +} diff --git a/plugins/verifier/schemavalidator/schemavalidation/testdata/osv-scanner.toml b/plugins/verifier/schemavalidator/schemavalidation/testdata/osv-scanner.toml new file mode 100644 index 000000000..d825c979d --- /dev/null +++ b/plugins/verifier/schemavalidator/schemavalidation/testdata/osv-scanner.toml @@ -0,0 +1,19 @@ +[[IgnoredVulns]] +id = "CVE-2022-48174" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42366" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42363" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42364" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42365" +reason = "Test manifest file(trivy_scan_report.json)" \ No newline at end of file diff --git a/plugins/verifier/vulnerabilityreport/schemavalidation/testdata/osv-scanner.toml b/plugins/verifier/vulnerabilityreport/schemavalidation/testdata/osv-scanner.toml new file mode 100644 index 000000000..d825c979d --- /dev/null +++ b/plugins/verifier/vulnerabilityreport/schemavalidation/testdata/osv-scanner.toml @@ -0,0 +1,19 @@ +[[IgnoredVulns]] +id = "CVE-2022-48174" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42366" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42363" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42364" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42365" +reason = "Test manifest file(trivy_scan_report.json)" \ No newline at end of file diff --git a/plugins/verifier/vulnerabilityreport/vulnerability_report.go b/plugins/verifier/vulnerabilityreport/vulnerability_report.go index c28f0b62c..ec4004f8f 100644 --- a/plugins/verifier/vulnerabilityreport/vulnerability_report.go +++ b/plugins/verifier/vulnerabilityreport/vulnerability_report.go @@ -24,15 +24,17 @@ import ( "strings" "time" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore" - _ "github.com/deislabs/ratify/pkg/referrerstore/oras" - "github.com/deislabs/ratify/pkg/verifier" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" - "github.com/deislabs/ratify/plugins/verifier/vulnerabilityreport/schemavalidation" imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/ratify-project/ratify/errors" + re "github.com/ratify-project/ratify/errors" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore" + _ "github.com/ratify-project/ratify/pkg/referrerstore/oras" + "github.com/ratify-project/ratify/pkg/verifier" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" + "github.com/ratify-project/ratify/plugins/verifier/vulnerabilityreport/schemavalidation" ) //go:embed schemavalidation/schemas @@ -93,38 +95,39 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe } createdTime, err := extractCreationTimestamp(input.CreatedAnnotationName, referenceDescriptor) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error extracting create timestamp annotation:[%v]", err.Error()), - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to create timestamp annotation.").WithError(err) + result := verifier.NewVerifierResult("", input.Name, verifierType, "", false, &verifierErr, nil) + return &result, nil } // check report is newer than allowed maximum age if input.MaximumAge != "" { ok, err := validateMaximumAge(input.MaximumAge, createdTime) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error validating maximum age:[%v]", err.Error()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate maximum age.").WithError(err) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } if !ok { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: report is older than maximum age:[%s]", input.MaximumAge), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Report is older than maximum age:[%s].", input.MaximumAge)) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } } @@ -132,84 +135,96 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error fetching reference manifest for subject: %s reference descriptor: %v: [%v]", subjectReference, referenceDescriptor.Descriptor, err.Error()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to fetch reference manifest for subject: %s, reference descriptor: %v.", subjectReference, referenceDescriptor)).WithError(err) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } if len(referenceManifest.Blobs) == 0 { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: no layers found in manifest for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + noBlobErr := errors.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("No layers found in manifest for referrer %s@%s.", subjectReference.Path, referenceDescriptor.Digest.String())) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &noBlobErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } blobDesc := referenceManifest.Blobs[0] refBlob, err := referrerStore.GetBlobContent(ctx, subjectReference, blobDesc.Digest) if err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error fetching blob for subject:[%s] digest:[%s]: [%v]", subjectReference, blobDesc.Digest, err.Error()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to fetch blob for subject:[%s] digest:[%s].", subjectReference, blobDesc.Digest)).WithError(err) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } // skip all validation if passthrough is enabled if input.Passthrough { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: true, - Message: "Validation skipped. passthrough enabled", - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "Validation skipped. passthrough enabled", + true, + nil, + map[string]interface{}{ CreatedAnnotation: createdTime, "passthrough": true, "report": string(refBlob), }, - }, nil + ) + return &result, nil } // validate json schema if err := verifyJSONSchema(referenceDescriptor.ArtifactType, refBlob, input.SchemaURL); err != nil { - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: schema validation failed for digest:[%s],artifact type:[%s],parse errors:[%v]", blobDesc.Digest, referenceDescriptor.ArtifactType, err.Error()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Schema validation failed for digest:[%s],artifact type:[%s].", blobDesc.Digest, referenceDescriptor.ArtifactType)).WithError(err) + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } if referenceDescriptor.ArtifactType == SarifArtifactType { return processSarifReport(input, input.Name, verifierType, refBlob, createdTime) } - return &verifier.VerifierResult{ - Name: input.Name, - Type: verifierType, - IsSuccess: true, - Message: "Validation succeeded", - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + result := verifier.NewVerifierResult( + "", + input.Name, + verifierType, + "Validation succeeded", + true, + nil, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } // verifyJSONSchema validates the json schema of the report @@ -234,27 +249,30 @@ func verifyJSONSchema(artifactType string, refBlob []byte, schemaURL string) err func processSarifReport(input *PluginConfig, verifierName string, verifierType string, blob []byte, createdTime time.Time) (*verifier.VerifierResult, error) { sarifReport, err := sarif.FromBytes(blob) if err != nil { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error parsing sarif report:[%v]", err.Error()), - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to parse sarif report.").WithError(err) + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } // verify that there is at least one run in the report if len(sarifReport.Runs) < 1 { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: "Validation failed: no runs found in sarif report", - Extensions: map[string]interface{}{ - CreatedAnnotation: createdTime, - }, - }, nil + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "No runs found in sarif report.", + false, + nil, + map[string]interface{}{CreatedAnnotation: createdTime}, + ) + return &result, nil } scannerName := strings.ToLower(sarifReport.Runs[0].Tool.Driver.Name) if len(input.DenylistCVEs) > 0 { @@ -276,16 +294,19 @@ func processSarifReport(input *PluginConfig, verifierName string, verifierType s } } - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: true, - Message: "Validation succeeded", - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "Validation succeeded", + true, + nil, + map[string]interface{}{ CreatedAnnotation: createdTime, "scanner": scannerName, }, - }, nil + ) + return &result, nil } // verifyDenyListCVEs verifies that the report does not contain any deny-listed CVEs @@ -301,16 +322,19 @@ func verifyDenyListCVEs(verifierName string, verifierType string, scannerName st // iterate over the results and check which cves are deny-listed for _, result := range sarifReport.Runs[0].Results { if result.RuleID == nil || *result.RuleID == "" { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: rule id not found for result:[%v]", result), - Extensions: map[string]interface{}{ + verifierResult := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + fmt.Sprintf("Rule id not found for result:[%v].", result), + false, + nil, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &verifierResult, nil } ruleIDLower := strings.ToLower(*result.RuleID) if _, ok := denylistCVESet[ruleIDLower]; ok { @@ -326,30 +350,36 @@ func verifyDenyListCVEs(verifierName string, verifierType string, scannerName st } if len(denylistViolations) > 0 { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "Found denied CVEs. See extensions field for details.", + false, + nil, + map[string]interface{}{ "scanner": scannerName, "denylistCVEs": denylistCVEs, "cveViolations": denylistViolations, CreatedAnnotation: createdTime, }, - Message: "Validation failed: found denied CVEs. See extensions field for details.", - }, nil + ) + return &result, nil } - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: true, - Message: "Validation succeeded", - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "Validation succeeded", + true, + nil, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &result, nil } // verifyDisallowedSeverities verifies that the report does not contain any disallowed severity levels @@ -363,42 +393,52 @@ func verifyDisallowedSeverities(verifierName string, verifierType string, scanne // iterate over the results and check if the severity is disallowed for _, result := range sarifReport.Runs[0].Results { if result.RuleID == nil || *result.RuleID == "" { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: rule id not found for result:[%v]", result), - Extensions: map[string]interface{}{ + verifierResult := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + fmt.Sprintf("Rule id not found for result:[%v].", result), + false, + nil, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &verifierResult, nil } rule, ok := ruleMap[*result.RuleID] if !ok { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: rule not found for result:[%v]", result), - Extensions: map[string]interface{}{ + verifierResult := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + fmt.Sprintf("Rule not found for result:[%v].", result), + false, + nil, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &verifierResult, nil } severity, err := extractSeverity(scannerName, *rule) if err != nil { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Message: fmt.Sprintf("Validation failed: error extracting severity:[%v]", err.Error()), - Extensions: map[string]interface{}{ + verifierErr := re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to extract severity.").WithError(err) + verifierResult := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "", + false, + &verifierErr, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &verifierResult, nil } // check if the severity is disallowed and add it to the map of violating CVE IDs for _, disallowed := range disallowedSeverities { @@ -409,29 +449,35 @@ func verifyDisallowedSeverities(verifierName string, verifierType string, scanne } // if there are violating rules, return them as custom extension field if len(violatingRules) > 0 { - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: false, - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "Found disallowed severities. See extensions field for details.", + false, + nil, + map[string]interface{}{ "scanner": scannerName, "disallowedSeverities": disallowedSeverities, "severityViolations": violatingRules, CreatedAnnotation: createdTime, }, - Message: "Validation failed: found disallowed severities. See extensions field for details.", - }, nil + ) + return &result, nil } - return &verifier.VerifierResult{ - Name: verifierName, - Type: verifierType, - IsSuccess: true, - Message: "Validation succeeded", - Extensions: map[string]interface{}{ + result := verifier.NewVerifierResult( + "", + verifierName, + verifierType, + "Validation succeeded", + true, + nil, + map[string]interface{}{ "scanner": scannerName, CreatedAnnotation: createdTime, }, - }, nil + ) + return &result, nil } // extractSeverity extracts the severity from the rule help text using regex diff --git a/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go b/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go index eeea7f765..0823d2ab9 100644 --- a/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go +++ b/plugins/verifier/vulnerabilityreport/vulnerability_report_test.go @@ -20,13 +20,13 @@ import ( "testing" "time" - "github.com/deislabs/ratify/pkg/common" - "github.com/deislabs/ratify/pkg/ocispecs" - "github.com/deislabs/ratify/pkg/referrerstore/mocks" - "github.com/deislabs/ratify/pkg/verifier/plugin/skel" "github.com/opencontainers/go-digest" oci "github.com/opencontainers/image-spec/specs-go/v1" "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/ocispecs" + "github.com/ratify-project/ratify/pkg/referrerstore/mocks" + "github.com/ratify-project/ratify/pkg/verifier/plugin/skel" ) const sampleSarifReport string = `{ @@ -76,8 +76,9 @@ func TestVerifyReference(t *testing.T) { blobContent string } type want struct { - message string - err error + message string + errorReason string + err error } tests := []struct { name string @@ -103,7 +104,8 @@ func TestVerifyReference(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: fmt.Sprintf("Validation failed: error extracting create timestamp annotation:[%s]", "no annotations found for descriptor:[{{ sha256:b2f67b016d3c646f025099b363b4f83a56a44d067a846be74e8866342c56f216 0 [] map[] [] } application/sarif+json}]"), + message: "Failed to create timestamp annotation.", + errorReason: "no annotations found for descriptor:[{{ sha256:b2f67b016d3c646f025099b363b4f83a56a44d067a846be74e8866342c56f216 0 [] map[] [] } application/sarif+json}]", }, }, { @@ -118,7 +120,8 @@ func TestVerifyReference(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: fmt.Sprintf("Validation failed: error validating maximum age:[%s]", "error parsing maximum age:[1d]"), + message: "Failed to validate maximum age.", + errorReason: "error parsing maximum age:[1d]", }, }, { @@ -133,7 +136,7 @@ func TestVerifyReference(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: fmt.Sprintf("Validation failed: report is older than maximum age:[%s]", "24h"), + errorReason: "Report is older than maximum age:[24h].", }, }, { @@ -148,7 +151,7 @@ func TestVerifyReference(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: fmt.Sprintf("Validation failed: no layers found in manifest for referrer %s@%s", "test_subject_path", manifestDigest.String()), + errorReason: fmt.Sprintf("No layers found in manifest for referrer %s@%s.", "test_subject_path", manifestDigest.String()), }, }, { @@ -189,7 +192,8 @@ func TestVerifyReference(t *testing.T) { blobContent: "{}", }, want: want{ - message: fmt.Sprintf("Validation failed: schema validation failed for digest:[%s],artifact type:[%s],parse errors:[%v]", blobDigest, SarifArtifactType, "version is required: runs is required: "), + message: fmt.Sprintf("Schema validation failed for digest:[%s],artifact type:[%s].", blobDigest, SarifArtifactType), + errorReason: "version is required: runs is required: ", }, }, { @@ -238,12 +242,15 @@ func TestVerifyReference(t *testing.T) { } verifierResult, err := VerifyReference(&cmdArgs, subjectRef, refDesc, testStore) if err != nil && err.Error() != tt.want.err.Error() { - t.Errorf("verifyReference() error = %v, wantErr %v", err, tt.want.err) - return + t.Fatalf("verifyReference() error = %v, wantErr %v", err, tt.want.err) } - if verifierResult != nil && verifierResult.Message != tt.want.message { - t.Errorf("verifyReference() verifier report message = %s, want %s", verifierResult.Message, tt.want.message) - return + if verifierResult != nil { + if verifierResult.Message != tt.want.message { + t.Fatalf("verifyReference() verifier report message = %s, want = %s", verifierResult.Message, tt.want.message) + } + if verifierResult.ErrorReason != tt.want.errorReason { + t.Fatalf("verifyReference() verifier report error reason = %s, want = %s", verifierResult.ErrorReason, tt.want.errorReason) + } } }) } @@ -313,8 +320,9 @@ func TestProcessSarifReport(t *testing.T) { blobContent string } type want struct { - message string - err error + message string + errorReason string + err error } tests := []struct { name string @@ -328,8 +336,9 @@ func TestProcessSarifReport(t *testing.T) { blobContent: "invalid", }, want: want{ - message: fmt.Sprintf("Validation failed: error parsing sarif report:[%s]", "invalid character 'i' looking for beginning of value"), - err: nil, + message: "Failed to parse sarif report.", + errorReason: "invalid character 'i' looking for beginning of value", + err: nil, }, }, { @@ -343,7 +352,7 @@ func TestProcessSarifReport(t *testing.T) { }`, }, want: want{ - message: "Validation failed: no runs found in sarif report", + message: "No runs found in sarif report.", err: nil, }, }, @@ -357,7 +366,7 @@ func TestProcessSarifReport(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: "Validation failed: found denied CVEs. See extensions field for details.", + message: "Found denied CVEs. See extensions field for details.", err: nil, }, }, @@ -374,7 +383,7 @@ func TestProcessSarifReport(t *testing.T) { blobContent: sampleSarifReport, }, want: want{ - message: "Validation failed: found disallowed severities. See extensions field for details.", + message: "Found disallowed severities. See extensions field for details.", err: nil, }, }, @@ -407,6 +416,9 @@ func TestProcessSarifReport(t *testing.T) { t.Errorf("processSarifReport() verifier report message = %s, want %s", verifierReport.Message, tt.want.message) return } + if verifierReport.ErrorReason != tt.want.errorReason { + t.Errorf("processSarifReport() verifier report error reason = %s, want %s", verifierReport.ErrorReason, tt.want.errorReason) + } }) } } @@ -419,8 +431,9 @@ func TestVerifyDenyListCVEs(t *testing.T) { sarifReport sarif.Report } type want struct { - message string - err error + message string + errorReason string + err error } tests := []struct { name string @@ -452,7 +465,7 @@ func TestVerifyDenyListCVEs(t *testing.T) { }, }, want: want{ - message: fmt.Sprintf("Validation failed: rule id not found for result:[%v]", &sarif.Result{}), + message: fmt.Sprintf("Rule id not found for result:[%v].", &sarif.Result{}), err: nil, }, }, @@ -483,7 +496,7 @@ func TestVerifyDenyListCVEs(t *testing.T) { }, }, want: want{ - message: "Validation failed: found denied CVEs. See extensions field for details.", + message: "Found denied CVEs. See extensions field for details.", err: nil, }, }, @@ -523,12 +536,13 @@ func TestVerifyDenyListCVEs(t *testing.T) { t.Run(tt.name, func(t *testing.T) { verifierReport, err := verifyDenyListCVEs("test_verifier", "", TrivyScannerName, &tests[i].args.sarifReport, tt.args.denyListCVEs, time.Now()) if err != nil && err.Error() != tt.want.err.Error() { - t.Errorf("verifyDenyListCVEs() error = %v, wantErr %v", err, tt.want.err) - return + t.Fatalf("verifyDenyListCVEs() error = %v, wantErr %v", err, tt.want.err) } if verifierReport.Message != tt.want.message { - t.Errorf("verifyDenyListCVEs() verifier report message = %s, want %s", verifierReport.Message, tt.want.message) - return + t.Fatalf("verifyDenyListCVEs() verifier report message = %s, want %s", verifierReport.Message, tt.want.message) + } + if verifierReport.ErrorReason != tt.want.errorReason { + t.Fatalf("verifyDenyListCVEs() verifier report error reaon = %s, want = %s", verifierReport.ErrorReason, tt.want.errorReason) } }) } @@ -545,8 +559,9 @@ func TestVerifyDisallowedSeverities(t *testing.T) { sarifReport sarif.Report } type want struct { - message string - err error + message string + errorReason string + err error } tests := []struct { name string @@ -582,7 +597,7 @@ func TestVerifyDisallowedSeverities(t *testing.T) { }, }, want: want{ - message: fmt.Sprintf("Validation failed: rule id not found for result:[%v]", &sarif.Result{}), + message: fmt.Sprintf("Rule id not found for result:[%v].", &sarif.Result{}), err: nil, }, }, @@ -617,7 +632,7 @@ func TestVerifyDisallowedSeverities(t *testing.T) { }, }, want: want{ - message: fmt.Sprintf("Validation failed: rule not found for result:[%v]", &sarif.Result{RuleID: &invalidRuleID}), + message: fmt.Sprintf("Rule not found for result:[%v].", &sarif.Result{RuleID: &invalidRuleID}), err: nil, }, }, @@ -652,8 +667,9 @@ func TestVerifyDisallowedSeverities(t *testing.T) { }, }, want: want{ - message: fmt.Sprintf("Validation failed: error extracting severity:[severity not found in help text:[%s]]", invalidSeverityText), - err: nil, + message: "Failed to extract severity.", + errorReason: fmt.Sprintf("severity not found in help text:[%s]", invalidSeverityText), + err: nil, }, }, { @@ -687,7 +703,7 @@ func TestVerifyDisallowedSeverities(t *testing.T) { }, }, want: want{ - message: "Validation failed: found disallowed severities. See extensions field for details.", + message: "Found disallowed severities. See extensions field for details.", err: nil, }, }, @@ -731,13 +747,16 @@ func TestVerifyDisallowedSeverities(t *testing.T) { t.Run(tt.name, func(t *testing.T) { verifierReport, err := verifyDisallowedSeverities("test_verifier", "", TrivyScannerName, &tests[i].args.sarifReport, tt.args.disallowedSeverities, time.Now()) if err != nil && err.Error() != tt.want.err.Error() { - t.Errorf("verifyDisallowedSeverities() error = %v, wantErr %v", err, tt.want.err) + t.Fatalf("verifyDisallowedSeverities() error = %v, wantErr %v", err, tt.want.err) return } if verifierReport.Message != tt.want.message { - t.Errorf("verifyDisallowedSeverities() verifier report message = %s, want %s", verifierReport.Message, tt.want.message) + t.Fatalf("verifyDisallowedSeverities() verifier report message = %s, want %s", verifierReport.Message, tt.want.message) return } + if verifierReport.ErrorReason != tt.want.errorReason { + t.Fatalf("verifyDisalowedServerities() verifier report error reason = %s, want = %s", verifierReport.ErrorReason, tt.want.errorReason) + } }) } } diff --git a/scripts/azure-ci-test.sh b/scripts/azure-ci-test.sh index 15d4513b7..b5ddce9ce 100755 --- a/scripts/azure-ci-test.sh +++ b/scripts/azure-ci-test.sh @@ -26,14 +26,16 @@ export ACR_NAME="${ACR_NAME:-ratifyacr${SUFFIX}}" export AKS_NAME="${AKS_NAME:-ratify-aks-${SUFFIX}}" export KEYVAULT_NAME="${KEYVAULT_NAME:-ratify-akv-${SUFFIX}}" export USER_ASSIGNED_IDENTITY_NAME="${USER_ASSIGNED_IDENTITY_NAME:-ratify-e2e-identity-${SUFFIX}}" -export LOCATION="eastus" -export KUBERNETES_VERSION=${1:-1.27.7} -GATEKEEPER_VERSION=${2:-3.15.0} +export LOCATION="westus2" +export KUBERNETES_VERSION=${1:-1.29.2} +GATEKEEPER_VERSION=${2:-3.17.0} TENANT_ID=$3 export RATIFY_NAMESPACE=${4:-gatekeeper-system} CERT_DIR=${5:-"~/ratify/certs"} +export AZURE_SP_OBJECT_ID=$6 export NOTATION_PEM_NAME="notation" export NOTATION_CHAIN_PEM_NAME="notationchain" +export KEYVAULT_KEY_NAME="test-key" TAG="test${SUFFIX}" REGISTRY="${ACR_NAME}.azurecr.io" @@ -64,21 +66,21 @@ deploy_ratify() { --set image.crdRepository=${REGISTRY}/test/localbuildcrd \ --set image.tag=${TAG} \ --set gatekeeper.version=${GATEKEEPER_VERSION} \ - --set akvCertConfig.enabled=true \ - --set akvCertConfig.vaultURI=${VAULT_URI} \ - --set akvCertConfig.certificates[0].name=${NOTATION_PEM_NAME} \ - --set akvCertConfig.certificates[1].name=${NOTATION_CHAIN_PEM_NAME} \ - --set akvCertConfig.tenantId=${TENANT_ID} \ + --set azurekeyvault.enabled=true \ + --set azurekeyvault.vaultURI=${VAULT_URI} \ + --set azurekeyvault.certificates[0].name=${NOTATION_PEM_NAME} \ + --set azurekeyvault.certificates[1].name=${NOTATION_CHAIN_PEM_NAME} \ + --set azurekeyvault.tenantId=${TENANT_ID} \ --set oras.authProviders.azureWorkloadIdentityEnabled=true \ --set azureWorkloadIdentity.clientId=${IDENTITY_CLIENT_ID} \ - --set-file cosign.key=".staging/cosign/cosign.pub" \ + --set azurekeyvault.keys[0].name=${KEYVAULT_KEY_NAME} \ --set featureFlags.RATIFY_CERT_ROTATION=true \ --set logger.level=debug kubectl delete verifiers.config.ratify.deislabs.io/verifier-cosign - kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml - kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml + kubectl apply -f https://ratify-project.github.io/ratify/library/default/template.yaml + kubectl apply -f https://ratify-project.github.io/ratify/library/default/samples/constraint.yaml } upload_cert_to_akv() { @@ -105,6 +107,14 @@ upload_cert_to_akv() { -p @./test/bats/tests/config/akvpolicy.json } +create_key_akv() { + az keyvault key create \ + --vault-name ${KEYVAULT_NAME} \ + -n ${KEYVAULT_KEY_NAME} \ + --kty RSA \ + --size 2048 +} + save_logs() { echo "Saving logs" local LOG_SUFFIX="${KUBERNETES_VERSION}-${GATEKEEPER_VERSION}" @@ -117,6 +127,12 @@ save_logs() { cleanup() { save_logs || true + echo "Delete key vault" + az keyvault delete --name "${KEYVAULT_NAME}" --resource-group "${GROUP_NAME}" || true + + echo "Purge key vault" + az keyvault purge --name "${KEYVAULT_NAME}" --no-wait || true + echo "Deleting group" az group delete --name "${GROUP_NAME}" --yes --no-wait || true } @@ -125,17 +141,20 @@ trap cleanup EXIT main() { ./scripts/create-azure-resources.sh + create_key_akv local ACR_USER_NAME="00000000-0000-0000-0000-000000000000" local ACR_PASSWORD=$(az acr login --name ${ACR_NAME} --expose-token --output tsv --query accessToken) - make e2e-azure-setup TEST_REGISTRY=$REGISTRY TEST_REGISTRY_USERNAME=${ACR_USER_NAME} TEST_REGISTRY_PASSWORD=${ACR_PASSWORD} + make e2e-azure-setup TEST_REGISTRY=$REGISTRY TEST_REGISTRY_USERNAME=${ACR_USER_NAME} TEST_REGISTRY_PASSWORD=${ACR_PASSWORD} KEYVAULT_KEY_NAME=${KEYVAULT_KEY_NAME} KEYVAULT_NAME=${KEYVAULT_NAME} build_push_to_acr upload_cert_to_akv deploy_gatekeeper deploy_ratify - TEST_REGISTRY=$REGISTRY bats -t ./test/bats/azure-test.bats + local IDENTITY_CLIENT_ID=$(az identity show --name ${USER_ASSIGNED_IDENTITY_NAME} --resource-group ${GROUP_NAME} --query 'clientId' -o tsv) + local VAULT_URI=$(az keyvault show --name ${KEYVAULT_NAME} --resource-group ${GROUP_NAME} --query "properties.vaultUri" -otsv) + TEST_REGISTRY=$REGISTRY IDENTITY_CLIENT_ID=$IDENTITY_CLIENT_ID VAULT_URI=$VAULT_URI bats -t ./test/bats/azure-test.bats } main diff --git a/scripts/create-azure-resources.sh b/scripts/create-azure-resources.sh index b72f044ae..bf86fe568 100755 --- a/scripts/create-azure-resources.sh +++ b/scripts/create-azure-resources.sh @@ -23,12 +23,6 @@ set -o pipefail : "${AKS_NAME:?AKS_NAME environment variable empty or not defined.}" : "${ACR_NAME:?ACR_NAME environment variable empty or not defined.}" -register_feature() { - az extension add --name aks-preview - az feature register --namespace "Microsoft.ContainerService" --name "EnableWorkloadIdentityPreview" - az provider register --namespace Microsoft.ContainerService -} - create_user_managed_identity() { SUBSCRIPTION_ID="$(az account show --query id --output tsv)" @@ -66,6 +60,7 @@ create_aks() { --generate-ssh-keys \ --enable-workload-identity \ --attach-acr ${ACR_NAME} \ + --enable-addons "" \ --enable-oidc-issuer >/dev/null echo "AKS '${AKS_NAME}' is created" @@ -95,15 +90,29 @@ create_akv() { echo "AKV '${KEYVAULT_NAME}' is created" - # Grant permissions to access the certificate. - az keyvault set-policy --name ${KEYVAULT_NAME} --secret-permissions get --object-id ${USER_ASSIGNED_IDENTITY_OBJECT_ID} + # Grant ratify identity permissions to access the secret + az role assignment create \ + --assignee-object-id ${USER_ASSIGNED_IDENTITY_OBJECT_ID} \ + --assignee-principal-type "ServicePrincipal" \ + --role "Key Vault Secrets User" \ + --scope subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${GROUP_NAME}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME} + + # Grant ratify identity permissions to access keys + az role assignment create \ + --assignee-object-id ${USER_ASSIGNED_IDENTITY_OBJECT_ID} \ + --assignee-principal-type "ServicePrincipal" \ + --role "Key Vault Crypto User" \ + --scope subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${GROUP_NAME}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME} + + # Grant runner SP permissions to create keys and import certificates + az role assignment create \ + --assignee-object-id ${AZURE_SP_OBJECT_ID} \ + --assignee-principal-type "ServicePrincipal" \ + --role "Key Vault Administrator" \ + --scope subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${GROUP_NAME}/providers/Microsoft.KeyVault/vaults/${KEYVAULT_NAME} } main() { - export -f register_feature - # might take around 20 minutes to register - timeout --foreground 1200 bash -c register_feature - az group create --name "${GROUP_NAME}" --tags "ratifye2e" --location "${LOCATION}" >/dev/null create_user_managed_identity diff --git a/terraform/azure/main.tf b/terraform/azure/main.tf index 46f9edee3..512aedf37 100644 --- a/terraform/azure/main.tf +++ b/terraform/azure/main.tf @@ -107,7 +107,7 @@ resource "azurerm_kubernetes_cluster" "aks" { location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name dns_prefix = "${var.cluster_name}-dns" - kubernetes_version = "1.26.3" + kubernetes_version = "1.29.2" workload_identity_enabled = true oidc_issuer_enabled = true diff --git a/test/bats/azure-test.bats b/test/bats/azure-test.bats index b638b3716..d37a8f5c7 100644 --- a/test/bats/azure-test.bats +++ b/test/bats/azure-test.bats @@ -24,7 +24,7 @@ SLEEP_TIME=1 # ensure that the chart deployment is reset to a clean state for other tests teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-dynamic --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-dynamic --ignore-not-found=true' pod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) helm upgrade --atomic --namespace gatekeeper-system --reuse-values --set featureFlags.RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS=false ratify ./charts/ratify wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl -n gatekeeper-system delete $pod --force --grace-period=0' @@ -32,10 +32,10 @@ SLEEP_TIME=1 # enable dynamic plugins helm upgrade --atomic --namespace gatekeeper-system --reuse-values --set featureFlags.RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS=true ratify ./charts/ratify - sleep 5 + sleep 30 latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 # parse the logs for the newly created ratify pod @@ -45,7 +45,7 @@ SLEEP_TIME=1 @test "validate image signed by leaf cert" { teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf2 --namespace default --force --ignore-not-found=true' @@ -54,13 +54,13 @@ SLEEP_TIME=1 } # configure the default template/constraint - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success - + # verify that the image can be run with a root cert, root verification cert should have been configured on deployment - run kubectl run demo-leaf --namespace default --image=${TEST_REGISTRY}/notation:leafSigned + wait_for_process 20 10 'kubectl run demo-leaf --namespace default --image=${TEST_REGISTRY}/notation:leafSigned' assert_success # add the leaf certificate as an inline certificate store @@ -70,7 +70,7 @@ SLEEP_TIME=1 sed -i '10,$d' ./test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml # configure the notation verifier to use the inline key management provider - run kubectl apply -f ./test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml + run kubectl replace -f ./test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml assert_success # wait for the httpserver cache to be invalidated @@ -87,13 +87,13 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl run demo --namespace default --image=${TEST_REGISTRY}/notation:signed + wait_for_process 20 10 'kubectl run demo --namespace default --image=${TEST_REGISTRY}/notation:signed' assert_success run kubectl run demo1 --namespace default --image=${TEST_REGISTRY}/notation:unsigned assert_failure @@ -106,14 +106,17 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml + assert_success + sleep 5 + run kubectl apply -f ./test/bats/tests/config/config_v1beta1_verifier_cosign_akv.yaml assert_success sleep 5 - run kubectl run cosign-demo --namespace default --image=${TEST_REGISTRY}/cosign:signed-key + wait_for_process 20 10 'kubectl run cosign-demo --namespace default --image=${TEST_REGISTRY}/cosign:signed-key' assert_success run kubectl run cosign-demo2 --namespace default --image=${TEST_REGISTRY}/cosign:unsigned assert_failure @@ -124,25 +127,25 @@ SLEEP_TIME=1 echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod license-checker --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod license-checker2 --namespace default --force --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_partial_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml sleep 5 run kubectl run license-checker --namespace default --image=${TEST_REGISTRY}/licensechecker:v0 assert_failure - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml # wait for the httpserver cache to be invalidated sleep 15 - run kubectl run license-checker2 --namespace default --image=${TEST_REGISTRY}/licensechecker:v0 + wait_for_process 20 10 'kubectl run license-checker2 --namespace default --image=${TEST_REGISTRY}/licensechecker:v0' assert_success } @@ -153,16 +156,16 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod sbom2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 - run kubectl run sbom --namespace default --image=${TEST_REGISTRY}/sbom:v0 + wait_for_process 20 10 'kubectl run sbom --namespace default --image=${TEST_REGISTRY}/sbom:v0' assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom @@ -176,27 +179,27 @@ SLEEP_TIME=1 @test "schemavalidator verifier test" { teardown() { echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-schemavalidator --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-schemavalidator --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 - run kubectl run schemavalidator --namespace default --image=${TEST_REGISTRY}/schemavalidator:v0 + wait_for_process 20 10 'kubectl run schemavalidator --namespace default --image=${TEST_REGISTRY}/schemavalidator:v0' assert_success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run schemavalidator2 --namespace default --image=${TEST_REGISTRY}/schemavalidator:v0 @@ -206,30 +209,28 @@ SLEEP_TIME=1 @test "sbom/notary/cosign/licensechecker/schemavalidator verifiers test" { teardown() { echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-schemavalidator --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-cosign --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-schemavalidator --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-cosign --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod all-in-one --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_cosign.yaml - sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 - run kubectl run all-in-one --namespace default --image=${TEST_REGISTRY}/all:v0 + wait_for_process 20 10 'kubectl run all-in-one --namespace default --image=${TEST_REGISTRY}/all:v0' assert_success } @@ -240,7 +241,7 @@ SLEEP_TIME=1 } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -250,34 +251,34 @@ SLEEP_TIME=1 assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation_kmprovider.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation_kmprovider.yaml assert_success # wait for the httpserver cache to be invalidated sleep 15 - run kubectl run crdtest --namespace default --image=${TEST_REGISTRY}/notation:signed + wait_for_process 20 10 'kubectl run crdtest --namespace default --image=${TEST_REGISTRY}/notation:signed' assert_success } @test "configmap update test" { skip "Skipping test for now as we are no longer watching for configfile update in a K8s environment.This test ensures we are watching config file updates in a non-kub scenario" - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl run demo2 --image=${TEST_REGISTRY}/notation:signed + wait_for_process 20 10 'kubectl run demo2 --image=${TEST_REGISTRY}/notation:signed' assert_success run kubectl get configmaps ratify-configuration --namespace=gatekeeper-system -o yaml >currentConfig.yaml - run kubectl delete -f ./library/default/samples/constraint.yaml + run kubectl delete -f ./library/multi-tenancy-validation/samples/constraint.yaml wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "kubectl replace --namespace=gatekeeper-system -f ${BATS_TESTS_DIR}/configmap/invalidconfigmap.yaml" echo "Waiting for 150 second for configuration update" sleep 150 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success run kubectl run demo3 --image=${TEST_REGISTRY}/notation:signed echo "Current time after validate : $(date +"%T")" @@ -294,7 +295,7 @@ SLEEP_TIME=1 start=$(date --iso-8601=seconds) latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 run bash -c "kubectl -n gatekeeper-system logs $latestpod --since-time=$start | grep 'dynamic plugins are currently disabled'" @@ -306,14 +307,91 @@ SLEEP_TIME=1 echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod mutate-demo --namespace default --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl run mutate-demo --namespace default --image=${TEST_REGISTRY}/notation:signed + wait_for_process 20 10 'kubectl run mutate-demo --namespace default --image=${TEST_REGISTRY}/notation:signed' assert_success result=$(kubectl get pod mutate-demo --namespace default -o json | jq -r ".spec.containers[0].image" | grep @sha) assert_mutate_success } + +@test "validate refresher reconcile count" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementprovider kmp-akv-refresh --ignore-not-found=true' + rm test.yaml + } + sed -e "s/keymanagementprovider-akv/kmp-akv-refresh/" \ + -e "s/1m/1s/" \ + -e "s/yourCertName/${NOTATION_PEM_NAME}/" \ + -e '/version: yourCertVersion/d' \ + -e "s|https://yourkeyvault.vault.azure.net/|${VAULT_URI}|" \ + -e "s/tenantID:/tenantID: ${TENANT_ID}/" \ + -e "s/clientID:/clientID: ${IDENTITY_CLIENT_ID}/" \ + ./config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml >test.yaml + run kubectl apply -f test.yaml + assert_success + sleep 10 + count=$(kubectl logs deployment/ratify -n gatekeeper-system | grep "Reconciled KeyManagementProvider" | wc -l) + [ $count -ge 4 ] +} + +@test "validate refresher updates kmp with latest certificate version" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementprovider kmp-akv-refresh --ignore-not-found=true' + rm test.yaml + rm policy.json + } + sed -e "s/keymanagementprovider-akv/kmp-akv-refresh/" \ + -e "s/1m/5s/" \ + -e "s/yourCertName/${NOTATION_PEM_NAME}/" \ + -e '/version: yourCertVersion/d' \ + -e "s|https://yourkeyvault.vault.azure.net/|${VAULT_URI}|" \ + -e "s/tenantID:/tenantID: ${TENANT_ID}/" \ + -e "s/clientID:/clientID: ${IDENTITY_CLIENT_ID}/" \ + ./config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml >test.yaml + run kubectl apply -f test.yaml + assert_success + sleep 5 + result=$(kubectl get keymanagementprovider kmp-akv-refresh -o jsonpath='{.status.properties.Certificates[0].Version}') + az keyvault certificate get-default-policy -o json >>policy.json + wait_for_process 20 10 "az keyvault certificate create --vault-name $KEYVAULT_NAME --name $NOTATION_PEM_NAME --policy @policy.json" + sleep 30 + refreshResult=$(kubectl get keymanagementprovider kmp-akv-refresh -o jsonpath='{.status.properties.Certificates[0].Version}') + [ "$result" != "$refreshResult" ] +} + +@test "validate certificate specified version" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementprovider kmp-akv-refresh --ignore-not-found=true' + rm policy.json + rm test.yaml + } + sed -e "s/keymanagementprovider-akv/kmp-akv-refresh/" \ + -e "s/1m/1s/" \ + -e "s/yourCertName/${NOTATION_PEM_NAME}/" \ + -e '/version: yourCertVersion/d' \ + -e "s|https://yourkeyvault.vault.azure.net/|${VAULT_URI}|" \ + -e "s/tenantID:/tenantID: ${TENANT_ID}/" \ + -e "s/clientID:/clientID: ${IDENTITY_CLIENT_ID}/" \ + ./config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml >test.yaml + version=$(az keyvault certificate show --vault-name $KEYVAULT_NAME --name $NOTATION_PEM_NAME --query 'sid' -o tsv | rev | cut -d'/' -f1 | rev) + sed -i \ + -e "/name: ${NOTATION_PEM_NAME}/a \ \ \ \ \ \ \ \ version: ${version}" \ + test.yaml + run kubectl apply -f test.yaml + assert_success + sleep 10 + result=$(kubectl get keymanagementprovider kmp-akv-refresh -o jsonpath='{.status.properties.Certificates[0].Version}') + az keyvault certificate get-default-policy -o json >>policy.json + wait_for_process 20 10 "az keyvault certificate create --vault-name $KEYVAULT_NAME --name $NOTATION_PEM_NAME --policy @policy.json" + sleep 30 + refreshResult=$(kubectl get keymanagementprovider kmp-akv-refresh -o jsonpath='{.status.properties.Certificates[0].Version}') + [ "$result" = "$refreshResult" ] +} diff --git a/test/bats/base-test.bats b/test/bats/base-test.bats index 53c79f5dd..bc30766df 100644 --- a/test/bats/base-test.bats +++ b/test/bats/base-test.bats @@ -28,14 +28,14 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod initcontainer-pod --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod initcontainer-pod1 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -45,7 +45,7 @@ RATIFY_NAMESPACE=gatekeeper-system # validate initContainers image run kubectl apply -f ./test/testdata/pod_initContainers_signed.yaml --namespace default assert_success - + run kubectl apply -f ./test/testdata/pod_initContainers_unsigned.yaml --namespace default assert_failure @@ -60,14 +60,14 @@ RATIFY_NAMESPACE=gatekeeper-system @test "crd version test" { run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success - run kubectl apply -f ./config/samples/config_v1alpha1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1alpha1_verifier_notation.yaml assert_success run bash -c "kubectl get verifiers.config.ratify.deislabs.io/verifier-notation -o yaml | grep 'apiVersion: config.ratify.deislabs.io/v1beta1'" assert_success run kubectl delete stores.config.ratify.deislabs.io/store-oras assert_success - run kubectl apply -f ./config/samples/config_v1alpha1_store_oras_http.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1alpha1_store_oras_http.yaml assert_success run bash -c "kubectl get stores.config.ratify.deislabs.io/store-oras -o yaml | grep 'apiVersion: config.ratify.deislabs.io/v1beta1'" assert_success @@ -79,15 +79,15 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -96,50 +96,168 @@ RATIFY_NAMESPACE=gatekeeper-system assert_failure } +@test "notation test timestamping" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-tsa --namespace default --force --ignore-not-found=true' + + # restore the original notation verifier for other tests + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' + } + + # validate key management provider status property shows success + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml | grep 'issuccess: true'" + assert_success + + # add the tsaroot certificate as an inline key management provider + cat ./test/bats/tests/config/config_v1beta1_keymanagementprovider_inline.yaml >> tsakmprovider.yaml + cat ./test/bats/tests/certificates/tsarootca.cer | sed 's/^/ /g' >> tsakmprovider.yaml + run kubectl apply -f tsakmprovider.yaml --namespace ${RATIFY_NAMESPACE} + assert_success + + # configure the notation verifier to use the inline key management provider + run kubectl replace -f ./test/bats/tests/config/config_v1beta1_verifier_notation_tsa.yaml + assert_success + sleep 10 + + # verify that the image can now be run + run kubectl run demo-tsa --namespace default --image=registry:5000/notation:tsa + assert_success +} + @test "notation test with certs across namespace" { teardown() { echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true' - + # restore cert store in ratify namespace - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n default > kmprovider.yaml" - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n default - sed 's/default/gatekeeper-system/' kmprovider.yaml > kmproviderNewNS.yaml - run kubectl apply -f kmproviderNewNS.yaml + run kubectl apply -f clusterkmprovider.yaml assert_success # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' + + # delete new namespace + run kubectl delete namespace new-namespace + assert_success } - run kubectl apply -f ./library/default/template.yaml + + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml + assert_success + sleep 5 + + # create a new namespace. + run kubectl create namespace new-namespace + assert_success + sleep 3 + + # apply the key management provider to new namespace + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml > clusterkmprovider.yaml" + assert_success + sed 's/KeyManagementProvider/NamespacedKeyManagementProvider/' clusterkmprovider.yaml >namespacedkmprovider.yaml + run kubectl apply -f namespacedkmprovider.yaml -n new-namespace + assert_success + + # delete the cluster-wide key management provider + run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 + assert_success + + # configure the notation verifier to use inline certificate store in new namespace. + sed 's/default\//new-namespace\//' ./config/samples/clustered/verifier/config_v1beta1_verifier_notation_specificnskmprovider.yaml >verifier-new-namespace.yaml + run kubectl apply -f verifier-new-namespace.yaml + assert_success + sleep 3 + + run kubectl run demo --namespace new-namespace --image=registry:5000/notation:signed + assert_success + + run kubectl run demo1 --namespace new-namespace --image=registry:5000/notation:unsigned + assert_failure +} +@test "cosign test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' + } + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - - # apply the key management provider to default namespace - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml -n ${RATIFY_NAMESPACE} > kmprovider.yaml" + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success - sed 's/gatekeeper-system/default/' kmprovider.yaml > kmproviderNewNS.yaml - run kubectl apply -f kmproviderNewNS.yaml + sleep 5 + + run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key assert_success - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} + + run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned + assert_failure +} + +@test "cosign legacy keyed test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' + } + + # use imperative command to guarantee verifier config is updated + run kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign_legacy.yaml + sleep 5 + + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success - - # configure the notation verifier to use inline certificate store with specific namespace - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation_specificnskmprovider.yaml + sleep 5 + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success + sleep 5 - run kubectl run demo --namespace default --image=registry:5000/notation:signed + run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key assert_success - run kubectl run demo1 --namespace default --image=registry:5000/notation:unsigned + run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned assert_failure } +@test "cosign keyless test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-keyless --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras_http.yaml' + } + + run kubectl replace -f ./test/bats/tests/config/config_v1beta1_verifier_cosign_keyless.yaml + sleep 5 + + run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml + sleep 5 + + wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' +} + +@test "cosign legacy keyless test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-keyless --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras_http.yaml' + } + + # use imperative command to guarantee useHttp is updated + run kubectl replace -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign_keyless_legacy.yaml + sleep 5 + + run kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras.yaml + sleep 5 + + wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' +} @test "validate crd add, replace and delete" { teardown() { echo "cleaning up" @@ -147,7 +265,7 @@ RATIFY_NAMESPACE=gatekeeper-system } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -157,7 +275,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml assert_success # wait for the httpserver cache to be invalidated @@ -166,34 +284,6 @@ RATIFY_NAMESPACE=gatekeeper-system assert_success } -@test "verifier crd status check" { - teardown() { - echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker' - } - - # apply a valid verifier, validate status property shows success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml - assert_success - run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" - assert_success - - # apply a invalid verifier CR, validate status with error - sed 's/licensechecker/invalidlicensechecker/' ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml > invalidVerifier.yaml - run kubectl apply -f invalidVerifier.yaml - assert_success - run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror: Original Error:'" - assert_success - - # apply a valid verifier, validate status property shows success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml - assert_success - run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" - assert_success - run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror: Original Error:'" - assert_failure -} - @test "store crd status check" { teardown() { echo "cleaning up" @@ -201,7 +291,7 @@ RATIFY_NAMESPACE=gatekeeper-system } # apply a invalid verifier CR, validate status with error - sed 's/:v1/:invalid/' ./config/samples/config_v1beta1_store_dynamic.yaml > invalidstore.yaml + sed 's/:v1/:invalid/' ./config/samples/clustered/store/config_v1beta1_store_dynamic.yaml >invalidstore.yaml run kubectl apply -f invalidstore.yaml assert_success # wait for download of image @@ -212,23 +302,23 @@ RATIFY_NAMESPACE=gatekeeper-system @test "configmap update test" { skip "Skipping test for now as we are no longer watching for configfile update in a K8s environment. This test ensures we are watching config file updates in a non-kub scenario" - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 run kubectl run demo2 --image=registry:5000/notation:signed assert_success run kubectl get configmaps ratify-configuration --namespace=${RATIFY_NAMESPACE} -o yaml >currentConfig.yaml - run kubectl delete -f ./library/default/samples/constraint.yaml + run kubectl delete -f ./library/multi-tenancy-validation/samples/constraint.yaml wait_for_process ${WAIT_TIME} ${SLEEP_TIME} "kubectl replace --namespace=${RATIFY_NAMESPACE} -f ${BATS_TESTS_DIR}/configmap/invalidconfigmap.yaml" echo "Waiting for 150 second for configuration update" sleep 150 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success run kubectl run demo3 --image=registry:5000/notation:signed echo "Current time after validate : $(date +"%T")" @@ -243,10 +333,10 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod mutate-demo --namespace default --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 run kubectl run mutate-demo --namespace default --image=registry:5000/notation:signed @@ -263,11 +353,11 @@ RATIFY_NAMESPACE=gatekeeper-system # restore the original key management provider wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f kmprovider_staging.yaml' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # save the existing key management provider inline resource to restore later - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml > kmprovider_staging.yaml" + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml > kmprovider_staging.yaml" assert_success # configure the default template/constraint run kubectl apply -f ./library/default/template.yaml @@ -280,7 +370,7 @@ RATIFY_NAMESPACE=gatekeeper-system assert_failure # delete the existing key management provider inline resource since certificate store and key management provider cannot be used together - run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} + run kubectl delete keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 assert_success # add the alternate certificate as an inline certificate store cat ~/.config/notation/truststore/x509/ca/alternate-cert/alternate-cert.crt | sed 's/^/ /g' >>./test/bats/tests/config/config_v1beta1_certstore_inline.yaml @@ -300,17 +390,17 @@ RATIFY_NAMESPACE=gatekeeper-system @test "validate inline key management provider" { teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-alternate --namespace default --force --ignore-not-found=true' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # configure the default template/constraint - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success # verify that the image cannot be run due to an invalid cert @@ -344,21 +434,21 @@ RATIFY_NAMESPACE=gatekeeper-system wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete certificatestores.config.ratify.deislabs.io/ratify-notation-inline-cert-0 --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' } - # configure the default template/constraint - run kubectl apply -f ./library/default/template.yaml + # configure the default template/constraint + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n ${RATIFY_NAMESPACE} -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success sleep 10 - # apply an invalid cert in an inline certificate store + # apply an invalid cert in an inline certificate store run kubectl apply -f ./test/bats/tests/config/config_v1beta1_certstore_inline_invalid.yaml -n ${RATIFY_NAMESPACE} assert_success # validate key management provider status property shows success @@ -375,17 +465,17 @@ RATIFY_NAMESPACE=gatekeeper-system echo "cleaning up" wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/config_v1beta1_store_oras_http.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/clustered/store/config_v1beta1_store_oras_http.yaml' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 # apply store CRD with K8s secret auth provier enabled - run kubectl apply -f ./config/samples/config_v1beta1_store_oras_k8secretAuth.yaml + run kubectl apply -f ./config/samples/clustered/store/config_v1beta1_store_oras_k8secretAuth.yaml assert_success sleep 5 run kubectl run demo --namespace default --image=registry:5000/notation:signed @@ -396,18 +486,18 @@ RATIFY_NAMESPACE=gatekeeper-system @test "validate image signed by leaf cert" { teardown() { - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --namespace ${RATIFY_NAMESPACE} --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete keymanagementproviders.config.ratify.deislabs.io/keymanagementprovider-inline --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-leaf2 --namespace default --force --ignore-not-found=true' # restore the original notation verifier for other tests - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' } # configure the default template/constraint - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success # add the root certificate as an inline key management provider @@ -456,12 +546,81 @@ RATIFY_NAMESPACE=gatekeeper-system sleep 100 # verify that the verification succeeds - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success } + +@test "namespaced notation/cosign verifiers test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-cosign --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-notation --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clusternotationkmprovider.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -n default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clustercosignkmprovider.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedpolicies.config.ratify.deislabs.io/ratify-policy --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clusterpolicy.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod notation-demo --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod notation-demo1 --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' + } + + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml + sleep 3 + + # apply namespaced policy and delete cluster-wide policy. + run bash -c "kubectl get policies.config.ratify.deislabs.io/ratify-policy -o yaml > clusterpolicy.yaml" + assert_success + sed 's/kind: Policy/kind: NamespacedPolicy/;/^\s*resourceVersion:/d' clusterpolicy.yaml >namespacedpolicy.yaml + run kubectl apply -f namespacedpolicy.yaml + assert_success + + # apply namespaced kmp and delete cluster-wide kmp. + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml > clusternotationkmprovider.yaml" + assert_success + sed 's/KeyManagementProvider/NamespacedKeyManagementProvider/' clusternotationkmprovider.yaml >namespacednotationkmprovider.yaml + run kubectl apply -f namespacednotationkmprovider.yaml + assert_success + + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -o yaml > clustercosignkmprovider.yaml" + assert_success + sed 's/KeyManagementProvider/NamespacedKeyManagementProvider/;/^\s*resourceVersion:/d' clustercosignkmprovider.yaml >namespacedcosignkmprovider.yaml + run kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -n default --ignore-not-found=true + sleep 5 + run kubectl apply -f namespacedcosignkmprovider.yaml + assert_success + sleep 5 + + # apply namespaced notation verifiers and delete cluster-wide notation verifiers. + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml + run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation --ignore-not-found=true + + # validate notation images. + run kubectl run notation-demo --namespace default --image=registry:5000/notation:signed + assert_success + + run kubectl run notation-demo1 --namespace default --image=registry:5000/notation:unsigned + assert_failure + + # apply namespaced cosign verifiers and delete cluster-wide cosign verifiers. + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml + run kubectl delete verifiers.config.ratify.deislabs.io/verifier-cosign --ignore-not-found=true + + # validate cosign images. + run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key + assert_success + + run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned + assert_failure +} diff --git a/test/bats/cli-test.bats b/test/bats/cli-test.bats index 1960eac56..abbe8e24a 100644 --- a/test/bats/cli-test.bats +++ b/test/bats/cli-test.bats @@ -21,6 +21,9 @@ load helpers run bin/ratify verify -c $RATIFY_DIR/config.json -s $TEST_REGISTRY/notation:unsigned assert_cmd_verify_failure + + run bin/ratify verify -c $RATIFY_DIR/config_tsa.json -s $TEST_REGISTRY/notation:tsa + assert_cmd_verify_success } @test "notation verifier leaf cert test" { @@ -75,6 +78,10 @@ load helpers } @test "sbom verifier test" { + # run with mismatch plugin version config should fail + run bin/ratify verify -c $RATIFY_DIR/sbom_version_mismatch.json -s $TEST_REGISTRY/sbom:v0 + assert_cmd_verify_failure + # run with deny license config should fail run bin/ratify verify -c $RATIFY_DIR/sbom_denylist_config_licensematch.json -s $TEST_REGISTRY/sbom:v0 assert_cmd_verify_failure @@ -138,3 +145,19 @@ load helpers test -x $RATIFY_DIR/plugins/dynamicstore assert_success } + +@test "notation verifier tsa test" { + teardown() { + # reset current_time + run sudo date -s "-2 days" + } + + # update system date to expire the cert and trigger timestamp verification + run sudo date -s "2 days" + + run bin/ratify verify -c $RATIFY_DIR/config.json -s $TEST_REGISTRY/notation:tsa + assert_cmd_verify_failure + + run bin/ratify verify -c $RATIFY_DIR/config_tsa.json -s $TEST_REGISTRY/notation:tsa + assert_cmd_verify_success +} \ No newline at end of file diff --git a/test/bats/high-availability.bats b/test/bats/high-availability.bats index e0a11bd72..a175019aa 100644 --- a/test/bats/high-availability.bats +++ b/test/bats/high-availability.bats @@ -25,14 +25,14 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --force --ignore-not-found=true' wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 # validate key management provider status property shows success - run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n gatekeeper-system -o yaml | grep 'issuccess: true'" + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml | grep 'issuccess: true'" assert_success run kubectl run demo --namespace default --image=registry:5000/notation:signed assert_success @@ -47,4 +47,4 @@ SLEEP_TIME=1 assert_success run bash -c "kubectl logs -l app.kubernetes.io/name=ratify -c ratify --tail=-1 -n gatekeeper-system | grep 'cache hit for subject registry:5000/notation'" assert_success -} \ No newline at end of file +} diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats index c2c744939..0cc9dea3a 100644 --- a/test/bats/plugin-test.bats +++ b/test/bats/plugin-test.bats @@ -18,6 +18,7 @@ load helpers BATS_TESTS_DIR=${BATS_TESTS_DIR:-test/bats/tests} WAIT_TIME=60 SLEEP_TIME=1 +RATIFY_NAMESPACE=gatekeeper-system @test "helm genCert test" { # tls cert provided @@ -59,44 +60,6 @@ SLEEP_TIME=1 assert_success } -@test "cosign test" { - teardown() { - echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-key --namespace default --force --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-unsigned --namespace default --force --ignore-not-found=true' - } - run kubectl apply -f ./library/default/template.yaml - assert_success - sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml - assert_success - sleep 5 - - run kubectl run cosign-demo-key --namespace default --image=registry:5000/cosign:signed-key - assert_success - - run kubectl run cosign-demo-unsigned --namespace default --image=registry:5000/cosign:unsigned - assert_failure -} - -@test "cosign keyless test" { - teardown() { - echo "cleaning up" - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod cosign-demo-keyless --namespace default --force --ignore-not-found=true' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/config_v1beta1_verifier_cosign.yaml' - wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl replace -f ./config/samples/config_v1beta1_store_oras_http.yaml' - } - - # use imperative command to guarantee useHttp is updated - run kubectl replace -f ./config/samples/config_v1beta1_verifier_cosign_keyless.yaml - sleep 5 - - run kubectl replace -f ./config/samples/config_v1beta1_store_oras.yaml - sleep 5 - - wait_for_process 20 10 'kubectl run cosign-demo-keyless --namespace default --image=wabbitnetworks.azurecr.io/test/cosign-image:signed-keyless' -} - @test "licensechecker test" { teardown() { echo "cleaning up" @@ -105,18 +68,18 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_partial_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_partial_licensechecker.yaml sleep 5 run kubectl run license-checker --namespace default --image=registry:5000/licensechecker:v0 assert_failure - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run license-checker2 --namespace default --image=registry:5000/licensechecker:v0 @@ -130,19 +93,19 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod sbom2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom_deny.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom_deny.yaml sleep 5 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 assert_failure - - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml # wait for the httpserver cache to be invalidated sleep 15 run kubectl run sbom --namespace default --image=registry:5000/sbom:v0 @@ -167,19 +130,19 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml sleep 5 run kubectl run schemavalidator --namespace default --image=registry:5000/schemavalidator:v0 assert_success - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator_bad.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator_bad.yaml assert_success # wait for the httpserver cache to be invalidated sleep 15 @@ -195,19 +158,19 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod vulnerabilityreport2 --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_vulnerabilityreport2.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport2.yaml sleep 5 run kubectl run vulnerabilityreport --namespace default --image=registry:5000/vulnerabilityreport:v0 assert_success sleep 15 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_vulnerabilityreport.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_vulnerabilityreport.yaml sleep 5 run kubectl run vulnerabilityreport2 --namespace default --image=registry:5000/vulnerabilityreport:v0 assert_failure @@ -223,19 +186,82 @@ SLEEP_TIME=1 wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod all-in-one --namespace default --force --ignore-not-found=true' } - run kubectl apply -f ./library/default/template.yaml + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml assert_success sleep 5 - run kubectl apply -f ./library/default/samples/constraint.yaml + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml assert_success sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_cosign.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_sbom.yaml sleep 5 - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml - run kubectl apply -f ./config/samples/config_v1beta1_verifier_schemavalidator.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_schemavalidator.yaml + sleep 5 + + # wait for the httpserver cache to be invalidated + sleep 15 + run kubectl run all-in-one --namespace default --image=registry:5000/all:v0 + assert_success +} + +@test "namespaced sbom/notary/cosign/licensechecker/schemavalidator verifiers test" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-sbom --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-schemavalidator --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-cosign --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedverifiers.config.ratify.deislabs.io/verifier-notation --namespace default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod all-in-one --namespace default --force --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_cosign.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -n default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clusternotationkmprovider.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -n default --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clustercosignkmprovider.yaml' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete namespacedpolicies.config.ratify.deislabs.io/ratify-policy --ignore-not-found=true' + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl apply -f clusterpolicy.yaml' + } + + run kubectl apply -f ./library/multi-tenancy-validation/template.yaml + assert_success + sleep 5 + run kubectl apply -f ./library/multi-tenancy-validation/samples/constraint.yaml + assert_success + sleep 5 + + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_notation.yaml + run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation --ignore-not-found=true + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_cosign.yaml + run kubectl delete verifiers.config.ratify.deislabs.io/verifier-cosign --ignore-not-found=true + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_sbom.yaml + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/namespaced/verifier/config_v1beta1_verifier_schemavalidator.yaml + + # apply namespaced policy and delete clustered policy. + run bash -c "kubectl get policies.config.ratify.deislabs.io/ratify-policy -o yaml > clusterpolicy.yaml" + assert_success + sed 's/kind: Policy/kind: NamespacedPolicy/;/^\s*resourceVersion:/d' clusterpolicy.yaml >namespacedpolicy.yaml + run kubectl apply -f namespacedpolicy.yaml + assert_success + + # apply namespaced kmp and delete clustered kmp. + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-notation-inline-cert-0 -o yaml > clusternotationkmprovider.yaml" + assert_success + sed 's/KeyManagementProvider/NamespacedKeyManagementProvider/' clusternotationkmprovider.yaml >namespacednotationkmprovider.yaml + run kubectl apply -f namespacednotationkmprovider.yaml + assert_success + + run bash -c "kubectl get keymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -o yaml > clustercosignkmprovider.yaml" + assert_success + sed 's/KeyManagementProvider/NamespacedKeyManagementProvider/;/^\s*resourceVersion:/d' clustercosignkmprovider.yaml >namespacedcosignkmprovider.yaml + run kubectl delete namespacedkeymanagementproviders.config.ratify.deislabs.io/ratify-cosign-inline-key-0 -n default --ignore-not-found=true + sleep 5 + run kubectl apply -f namespacedcosignkmprovider.yaml + assert_success sleep 5 # wait for the httpserver cache to be invalidated @@ -251,7 +277,7 @@ SLEEP_TIME=1 } echo "adding license checker, delete notation verifier and validate deployment fails due to missing notation verifier" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_complete_licensechecker.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml assert_success run kubectl delete verifiers.config.ratify.deislabs.io/verifier-notation assert_success @@ -261,7 +287,7 @@ SLEEP_TIME=1 assert_failure echo "Add notation verifier and validate deployment succeeds" - run kubectl apply -f ./config/samples/config_v1beta1_verifier_notation.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_notation.yaml assert_success # wait for the httpserver cache to be invalidated @@ -270,6 +296,34 @@ SLEEP_TIME=1 assert_success } +@test "verifier crd status check" { + teardown() { + echo "cleaning up" + wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker' + } + + # apply a valid verifier, validate status property shows success + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml + assert_success + run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" + assert_success + + # apply a invalid verifier CR, validate status with error + sed 's/licensechecker/invalidlicensechecker/' ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml >invalidVerifier.yaml + run kubectl apply -f invalidVerifier.yaml + assert_success + run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror: PLUGIN_NOT_FOUND:'" + assert_success + + # apply a valid verifier, validate status property shows success + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml + assert_success + run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Issuccess: true'" + assert_success + run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror: Original Error:'" + assert_failure +} + @test "dynamic plugins disabled test" { teardown() { echo "cleaning up" @@ -279,7 +333,7 @@ SLEEP_TIME=1 start=$(date --iso-8601=seconds) latestpod=$(kubectl -n gatekeeper-system get pod -l=app.kubernetes.io/name=ratify --sort-by=.metadata.creationTimestamp -o=name | tail -n 1) - run kubectl apply -f ./config/samples/config_v1beta1_verifier_dynamic.yaml + run kubectl apply -f ./config/samples/clustered/verifier/config_v1beta1_verifier_dynamic.yaml sleep 5 run bash -c "kubectl -n gatekeeper-system logs $latestpod --since-time=$start | grep 'dynamic plugins are currently disabled'" diff --git a/test/bats/quickstart-test.bats b/test/bats/quickstart-test.bats index e6c25cc14..1682b90f9 100644 --- a/test/bats/quickstart-test.bats +++ b/test/bats/quickstart-test.bats @@ -16,10 +16,10 @@ load helpers @test "validate quick start steps" { - run kubectl run demo --image=ghcr.io/deislabs/ratify/notary-image:signed + run kubectl run demo --image=ghcr.io/ratify-project/ratify/notary-image:signed assert_success # validate unsigned fails - run kubectl run demo1 --image=ghcr.io/deislabs/ratify/notary-image:unsigned + run kubectl run demo1 --image=ghcr.io/ratify-project/ratify/notary-image:unsigned assert_failure } diff --git a/test/bats/tests/certificates/tsarootca.cer b/test/bats/tests/certificates/tsarootca.cer new file mode 100644 index 000000000..1a1e4fbc8 --- /dev/null +++ b/test/bats/tests/certificates/tsarootca.cer @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- diff --git a/test/bats/tests/config/config_tsa.json b/test/bats/tests/config/config_tsa.json new file mode 100644 index 000000000..e3791df09 --- /dev/null +++ b/test/bats/tests/config/config_tsa.json @@ -0,0 +1,101 @@ +{ + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "cosignEnabled": true, + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "configPolicy" + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "name": "schemavalidator", + "artifactTypes": "application/vnd.aquasecurity.trivy.report.sarif.v1", + "schemas": { + "application/sarif+json": "file:///home/runner/.ratify/schemas/sarif-2.1.0-rtm.5.json" + } + }, + { + "name": "sbom", + "artifactTypes": "application/spdx+json", + "nestedReferences": "application/vnd.cncf.notary.signature", + "disallowedLicenses":["AGPL"], + "disallowedPackages":[{"name":"log4j-core","versionInfo":"2.13.0"}] + + }, + { + "name": "cosign", + "artifactTypes": "application/vnd.dev.cosign.artifact.sig.v1+json", + "key": ".staging/cosign/cosign.pub" + }, + { + "name": "notation", + "artifactTypes": "application/vnd.cncf.notary.signature", + "verificationCerts": [ + "~/.config/notation/localkeys/ratify-bats-test.crt", + "~/.ratify/ratify-certs/notation/tsarootca.cer" + ], + "trustPolicyDoc": { + "version": "1.0", + "trustPolicies": [ + { + "name": "default", + "registryScopes": [ + "*" + ], + "signatureVerification": { + "level": "strict", + "verifyTimestamp": "afterCertExpiry" + }, + "trustStores": [ + "ca:ca-certs", + "tsa:tsa-certs" + ], + "trustedIdentities": [ + "*" + ] + } + ] + } + }, + { + "name": "schemavalidator", + "artifactTypes": "application/vnd.aquasecurity.trivy.report.sarif.v1", + "schemas": { + "application/sarif+json": "https://json.schemastore.org/sarif-2.1.0-rtm.5.json" + } + }, + { + "name": "licensechecker", + "artifactTypes": "application/vnd.ratify.spdx.v0", + "allowedLicenses": [ + "GPL-2.0-only", + "MIT", + "OpenSSL", + "BSD-2-Clause AND BSD-3-Clause", + "Zlib", + "MPL-2.0 AND MIT", + "ISC", + "Apache-2.0", + "MIT AND BSD-2-Clause AND GPL-2.0-or-later", + "MIT AND LicenseRef-AND AND BSD-2-Clause AND LicenseRef-AND AND GPL-2.0-or-later", + "MPL-2.0 AND LicenseRef-AND AND MIT", + "BSD-2-Clause AND LicenseRef-AND AND BSD-3-Clause", + "NONE", + "NOASSERTION", + "" + ] + } + ] + } +} \ No newline at end of file diff --git a/test/bats/tests/config/config_v1beta1_verifier_cosign_akv.yaml b/test/bats/tests/config/config_v1beta1_verifier_cosign_akv.yaml new file mode 100644 index 000000000..69fb99605 --- /dev/null +++ b/test/bats/tests/config/config_v1beta1_verifier_cosign_akv.yaml @@ -0,0 +1,19 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: default + version: 1.0.0 + scopes: + - "*" + keys: + - provider: kmprovider-akv + tLogVerify: false \ No newline at end of file diff --git a/test/bats/tests/config/config_v1beta1_verifier_cosign_keyless.yaml b/test/bats/tests/config/config_v1beta1_verifier_cosign_keyless.yaml new file mode 100644 index 000000000..6e5d134c0 --- /dev/null +++ b/test/bats/tests/config/config_v1beta1_verifier_cosign_keyless.yaml @@ -0,0 +1,15 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-cosign +spec: + name: cosign + artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json + parameters: + trustPolicies: + - name: default + scopes: + - '*' + keyless: + certificateIdentity: sozercan@gmail.com + certificateOIDCIssuer: https://github.com/login/oauth \ No newline at end of file diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml index fb6595d85..2fdfc94dd 100644 --- a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml +++ b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml @@ -7,8 +7,9 @@ spec: artifactTypes: application/vnd.cncf.notary.signature parameters: verificationCertStores: - certs: - - certstore-inline + ca: + ca-certs: + - certstore-inline trustPolicyDoc: version: "1.0" trustPolicies: @@ -18,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation_akv.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation_akv.yaml index 031180229..ea2d24c8e 100644 --- a/test/bats/tests/config/config_v1beta1_verifier_notation_akv.yaml +++ b/test/bats/tests/config/config_v1beta1_verifier_notation_akv.yaml @@ -7,8 +7,9 @@ spec: artifactTypes: application/vnd.cncf.notary.signature parameters: verificationCertStores: - certs: - - kmprovider-akv + ca: + ca-certs: + - kmprovider-akv trustPolicyDoc: version: "1.0" trustPolicies: @@ -18,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml index c90ccc5fd..04304c3f1 100644 --- a/test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml +++ b/test/bats/tests/config/config_v1beta1_verifier_notation_kmprovider.yaml @@ -7,8 +7,9 @@ spec: artifactTypes: application/vnd.cncf.notary.signature parameters: verificationCertStores: - certs: - - keymanagementprovider-inline + ca: + ca-certs: + - keymanagementprovider-inline trustPolicyDoc: version: "1.0" trustPolicies: @@ -18,6 +19,6 @@ spec: signatureVerification: level: strict trustStores: - - ca:certs + - ca:ca-certs trustedIdentities: - "*" diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation_tsa.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation_tsa.yaml new file mode 100644 index 000000000..46d8048b5 --- /dev/null +++ b/test/bats/tests/config/config_v1beta1_verifier_notation_tsa.yaml @@ -0,0 +1,28 @@ +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: Verifier +metadata: + name: verifier-notation +spec: + name: notation + artifactTypes: application/vnd.cncf.notary.signature + parameters: + verificationCertStores: + ca: + ca-certs: + - ratify-notation-inline-cert-0 + tsa: + tsa-certs: + - keymanagementprovider-inline + trustPolicyDoc: + version: "1.0" + trustPolicies: + - name: default + registryScopes: + - "*" + signatureVerification: + level: strict + trustStores: + - ca:ca-certs + - tsa:tsa-certs + trustedIdentities: + - "*" diff --git a/test/bats/tests/config/sbom_version_mismatch.json b/test/bats/tests/config/sbom_version_mismatch.json new file mode 100644 index 000000000..7713dd756 --- /dev/null +++ b/test/bats/tests/config/sbom_version_mismatch.json @@ -0,0 +1,31 @@ +{ + "store": { + "version": "1.0.0", + "plugins": [ + { + "name": "oras", + "useHttp": true + } + ] + }, + "policy": { + "version": "1.0.0", + "plugin": { + "name": "configPolicy", + "artifactVerificationPolicies": { + "application/spdx+json": "all" + } + } + }, + "verifier": { + "version": "1.0.0", + "plugins": [ + { + "version": "3.0.0", + "name": "sbom", + "artifactTypes": "application/spdx+json", + "disallowedLicenses": ["NOASSERTION"] + } + ] + } +} \ No newline at end of file diff --git a/test/testdata/cosign.pub b/test/testdata/cosign.pub index 349220e9c..4b483f700 100644 --- a/test/testdata/cosign.pub +++ b/test/testdata/cosign.pub @@ -1,4 +1,4 @@ -----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvjrMZFyaBDsvg5e0C8JaHqw8ULuc -n947ODVAMvfdqtjqK2eW77OGrsFLdkbG3BET9U4Dj37odn4kI5lC4Lj9Eg== +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBiEwSL0YJy4hyyEB5S7K7rERVqt1 +K2RMHUvjNzk5/bWzWNqI4GspPDaVtIHSm3DhCqIC/Ip25hTRovMeho847Q== -----END PUBLIC KEY----- diff --git a/test/testdata/osv-scanner.toml b/test/testdata/osv-scanner.toml new file mode 100644 index 000000000..d825c979d --- /dev/null +++ b/test/testdata/osv-scanner.toml @@ -0,0 +1,19 @@ +[[IgnoredVulns]] +id = "CVE-2022-48174" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42366" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42363" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42364" +reason = "Test manifest file(trivy_scan_report.json)" + +[[IgnoredVulns]] +id = "CVE-2023-42365" +reason = "Test manifest file(trivy_scan_report.json)" \ No newline at end of file diff --git a/test/validation.md b/test/validation.md index 7e17a0c95..e659ae9a3 100644 --- a/test/validation.md +++ b/test/validation.md @@ -12,6 +12,7 @@ While we are working on improving our coverage, here is the list of scenarios th ### CLI - Verifier Scenarios - Notation + - TSA - Cosign - Keyed - Keyless @@ -25,6 +26,7 @@ While we are working on improving our coverage, here is the list of scenarios th ### Kubernetes - Verifier Scenarios - Notation + - TSA - Cosign - SBOM - License Checker diff --git a/utils/utils.go b/utils/utils.go index df05b618a..be9b8c78c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -29,3 +29,8 @@ func SanitizeString(input string) string { func SanitizeURL(input url.URL) string { return SanitizeString(input.String()) } + +func MakePtr[T any](value T) *T { + b := value + return &b +}