diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 0af3612f8..804ea6a88 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -15,7 +15,7 @@ jobs: 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') }} @@ -25,8 +25,8 @@ jobs: fail-fast: false matrix: KUBERNETES_VERSION: ["1.29.2"] - GATEKEEPER_VERSION: ["3.16.0"] - uses: ./.github/workflows/e2e-k8s.yml + GATEKEEPER_VERSION: ["3.17.0"] + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} @@ -38,11 +38,11 @@ jobs: fail-fast: false matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] - GATEKEEPER_VERSION: ["3.14.0", "3.15.0", "3.16.0"] - uses: ./.github/workflows/e2e-k8s.yml + 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" @@ -54,15 +54,15 @@ jobs: fail-fast: false matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] - GATEKEEPER_VERSION: ["3.14.0", "3.15.0", "3.16.0"] + 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: - needs: ['build_test_aks_e2e_conditional'] + needs: ["build_test_aks_e2e_conditional"] runs-on: ubuntu-latest permissions: id-token: write @@ -70,7 +70,7 @@ jobs: environment: azure-test steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -79,7 +79,7 @@ jobs: - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.22' + go-version: "1.22" - name: Az CLI login uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 @@ -90,4 +90,4 @@ jobs: - name: clean up run: | - make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ secrets.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 5fbb3d0c8..46042f7f1 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -10,16 +10,16 @@ permissions: jobs: cleanup: runs-on: ubuntu-latest - steps: + steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + 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 ) @@ -34,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 index 98cc5cfff..0a53bd8d0 100644 --- a/.github/workflows/clean-dev-package.yml +++ b/.github/workflows/clean-dev-package.yml @@ -10,24 +10,24 @@ jobs: cleanup-packages: runs-on: ubuntu-latest permissions: - packages: write + packages: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + 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' + 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' + with: + package-name: "ratify-dev" + package-type: "container" min-versions-to-keep: 7 - delete-only-pre-release-versions: "true" \ No newline at end of file + delete-only-pre-release-versions: "true" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 82ddd78ea..fe0ac8345 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,19 +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 @@ -27,7 +26,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index a202673ea..f6a2c1f9d 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -7,14 +7,14 @@ on: workflow_call: inputs: k8s_version: - description: 'Kubernetes version' + description: "Kubernetes version" required: true - default: '1.29.2' + default: "1.29.2" type: string gatekeeper_version: - description: 'Gatekeeper version' + description: "Gatekeeper version" required: true - default: '3.16.0' + default: "3.17.0" type: string jobs: @@ -28,7 +28,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -37,7 +37,7 @@ jobs: - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.22' + go-version: "1.22" - name: Az CLI login uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 with: @@ -69,4 +69,4 @@ jobs: 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 324de264f..67038445c 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -29,13 +29,12 @@ 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@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -64,7 +63,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -89,10 +88,10 @@ jobs: 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@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -103,7 +102,7 @@ jobs: - 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 57a04b69a..717ff937c 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -7,14 +7,14 @@ on: workflow_call: inputs: k8s_version: - description: 'Kubernetes version' + description: "Kubernetes version" required: true - default: '1.29.2' + default: "1.29.2" type: string gatekeeper_version: - description: 'Gatekeeper version' + description: "Gatekeeper version" required: true - default: '3.16.0' + default: "3.17.0" type: string jobs: @@ -26,7 +26,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -35,7 +35,7 @@ jobs: - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.22' + go-version: "1.22" - name: Bootstrap e2e run: | @@ -70,4 +70,4 @@ jobs: 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 dd5bec53a..cb827af40 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,13 +15,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + 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' + go-version: "1.22" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: golangci-lint uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index e711f94f8..631f9bb6f 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -30,7 +30,7 @@ jobs: DAPR_VERSION: ["1.13.2"] steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -39,7 +39,7 @@ jobs: - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.22' + go-version: "1.22" - name: Bootstrap e2e run: | diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml index 3aecd8b89..5fcefe211 100644 --- a/.github/workflows/pr-to-main.yml +++ b/.github/workflows/pr-to-main.yml @@ -2,7 +2,7 @@ name: pr_to_main 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: @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index cfb578923..850838750 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -21,4 +21,4 @@ jobs: - 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 index 925284a8c..566c27885 100644 --- a/.github/workflows/publish-cosign-sample.yml +++ b/.github/workflows/publish-cosign-sample.yml @@ -1,11 +1,11 @@ name: publish-cosign-sample -on: +on: workflow_dispatch: - + env: REGISTRY: ghcr.io - + permissions: contents: read @@ -18,9 +18,9 @@ jobs: contents: write packages: write id-token: write - steps: + steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -28,7 +28,7 @@ jobs: uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0 - name: Get repo - run: | + run: | echo "REPOSITORY=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_ENV - name: Write signing key to disk diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index c5dc1ef6a..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 @@ -17,7 +17,7 @@ jobs: environment: azure-publish steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout @@ -108,11 +108,11 @@ jobs: run: | 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 + 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 }} diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 32a893d36..e4d81f984 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -16,7 +16,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - name: Checkout diff --git a/.github/workflows/publish-sample.yml b/.github/workflows/publish-sample.yml index b21e8840e..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,14 +17,14 @@ jobs: permissions: contents: write packages: write - steps: + steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + 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 @@ -35,9 +35,7 @@ jobs: 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 6e26af5cb..f6739f243 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -30,7 +30,7 @@ jobs: KUBERNETES_VERSION: ["1.29.2"] steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13f91853e..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,41 +15,41 @@ jobs: permissions: contents: write steps: - - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 - with: - egress-policy: audit - - - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 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: 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: + + - 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@50769540e7f4bd5e21e526ee35c689e35e0d6874 # tag=v4.4.0 - 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 71db17f86..3f2464cbc 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -27,8 +27,8 @@ jobs: fail-fast: false matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] - GATEKEEPER_VERSION: ["3.14.0", "3.15.0", "3.16.0"] - uses: ./.github/workflows/e2e-k8s.yml + 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 }} @@ -42,7 +42,7 @@ jobs: fail-fast: false matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] - GATEKEEPER_VERSION: ["3.14.0", "3.15.0", "3.16.0"] + GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] uses: ./.github/workflows/e2e-aks.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} @@ -50,7 +50,7 @@ jobs: secrets: inherit aks-test-cleanup: - needs: ['build_test_aks_e2e'] + needs: ["build_test_aks_e2e"] runs-on: ubuntu-latest permissions: id-token: write @@ -58,7 +58,7 @@ jobs: environment: azure-test steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -67,7 +67,7 @@ jobs: - name: Set up Go 1.22 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version: '1.22' + go-version: "1.22" - name: Az CLI login uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1 @@ -78,4 +78,4 @@ jobs: - name: clean up run: | - make e2e-cleanup AZURE_SUBSCRIPTION_ID=${{ secrets.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 index 7c0ff23c4..aed9bba20 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -11,7 +11,7 @@ on: - "library/**" - "**.md" 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 @@ -23,7 +23,7 @@ jobs: timeout-minutes: 15 steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -39,7 +39,7 @@ jobs: timeout-minutes: 15 steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 970cd1182..96efefe42 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -3,7 +3,7 @@ on: branch_protection_rule: schedule: # Weekly on Saturdays. - - cron: '30 1 * * 6' + - cron: "30 1 * * 6" push: branches: - main @@ -27,10 +27,10 @@ jobs: id-token: write actions: read contents: read - + steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -53,7 +53,7 @@ jobs: name: SARIF file path: results.sarif retention-days: 5 - + - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # tag=v3.26.6 with: diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml index fd44a65d1..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 @@ -17,7 +17,7 @@ jobs: repository-projects: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit @@ -26,5 +26,5 @@ jobs: 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/Makefile b/Makefile index f6895c911..376a2d170 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ LDFLAGS += -X $(GO_PKG)/internal/version.GitTag=$(GIT_TAG) KIND_VERSION ?= 0.22.0 KUBERNETES_VERSION ?= 1.29.2 KIND_KUBERNETES_VERSION ?= 1.29.2 -GATEKEEPER_VERSION ?= 3.16.0 +GATEKEEPER_VERSION ?= 3.17.0 DAPR_VERSION ?= 1.12.5 COSIGN_VERSION ?= 2.2.3 NOTATION_VERSION ?= 1.2.0 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 index 70dcf557c..cdccfe7f1 100644 --- a/api/unversioned/namespacedkeymanagementprovider_types.go +++ b/api/unversioned/namespacedkeymanagementprovider_types.go @@ -33,6 +33,10 @@ type NamespacedKeyManagementProviderSpec 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"` diff --git a/api/unversioned/namespacedverifier_types.go b/api/unversioned/namespacedverifier_types.go index 7e196a233..994e9d1f8 100644 --- a/api/unversioned/namespacedverifier_types.go +++ b/api/unversioned/namespacedverifier_types.go @@ -27,19 +27,22 @@ 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 + // 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"` // Parameters for this verifier 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/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index 467a815ca..9be9f8190 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -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/v1beta1/namespacedverifier_types.go b/api/v1beta1/namespacedverifier_types.go index 31e1c8e9f..17809a456 100644 --- a/api/v1beta1/namespacedverifier_types.go +++ b/api/v1beta1/namespacedverifier_types.go @@ -29,19 +29,22 @@ 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 + // 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/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 fafb65bab..0b69afaf9 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -569,6 +569,7 @@ func Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProvi 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 } @@ -580,6 +581,7 @@ func Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProvi 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 } @@ -673,6 +675,7 @@ func Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_Namespac 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 } @@ -684,6 +687,7 @@ func Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_Namespac 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 } @@ -983,6 +987,7 @@ func Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierLis 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 @@ -998,6 +1003,7 @@ func Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpe 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 @@ -1319,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 @@ -1334,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/charts/ratify/README.md b/charts/ratify/README.md index 10ec62582..af3600209 100644 --- a/charts/ratify/README.md +++ b/charts/ratify/README.md @@ -77,7 +77,7 @@ Values marked `# DEPRECATED` in the `values.yaml` as well as **DEPRECATED** in t | 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.16.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` | diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml index 2bc52a02d..aa08d898e 100644 --- a/charts/ratify/values.yaml +++ b/charts/ratify/values.yaml @@ -53,7 +53,7 @@ serviceAccount: create: true name: ratify-admin gatekeeper: - version: "3.16.0" + version: "3.17.0" namespace: # default is gatekeeper-system instrumentation: metricsEnabled: true diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml index 784bcb5f5..b61b07734 100644 --- a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml @@ -48,20 +48,20 @@ spec: description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier 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 @@ -72,6 +72,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/bases/config.ratify.deislabs.io_verifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml index a23d9819f..8d08f76e5 100644 --- a/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml @@ -107,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 @@ -131,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/dev.helmfile.yaml b/dev.helmfile.yaml index 0a3f4060a..bec894af9 100644 --- a/dev.helmfile.yaml +++ b/dev.helmfile.yaml @@ -10,7 +10,7 @@ releases: namespace: gatekeeper-system createNamespace: true chart: gatekeeper/gatekeeper - version: 3.16.0 + version: 3.17.0 wait: true set: - name: enableExternalData diff --git a/dev.high-availability.helmfile.yaml b/dev.high-availability.helmfile.yaml index 3436a1c55..29c40fe8a 100644 --- a/dev.high-availability.helmfile.yaml +++ b/dev.high-availability.helmfile.yaml @@ -20,7 +20,7 @@ releases: namespace: gatekeeper-system createNamespace: true chart: gatekeeper/gatekeeper - version: 3.16.0 + version: 3.17.0 wait: true set: - name: enableExternalData diff --git a/pkg/controllers/clusterresource/keymanagementprovider_controller.go b/pkg/controllers/clusterresource/keymanagementprovider_controller.go index 93dda9806..afb53a904 100644 --- a/pkg/controllers/clusterresource/keymanagementprovider_controller.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller.go @@ -18,11 +18,19 @@ package clusterresource import ( "context" + "encoding/json" "fmt" + 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" @@ -37,20 +45,91 @@ type KeyManagementProviderReconciler struct { Scheme *runtime.Scheme } -func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) { - refresher, err := refresh.CreateRefresherFromConfig(config) - if err != nil { +func (r *KeyManagementProviderReconciler) ReconcileWithType(ctx context.Context, req ctrl.Request, refresherType string) (ctrl.Result, error) { + logger := logrus.WithContext(ctx) + + var keyManagementProvider configv1beta1.KeyManagementProvider + + 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) + kmp.DeleteResourceFromMap(resource) + } else { + logger.Error(err, "unable to fetch key management provider") + } + + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + lastFetchedTime := metav1.Now() + + // 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, kmpErr) + kmp.SetKeyError(resource, kmpErr) + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, false, &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") + 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 { - return ctrl.Result{}, err + 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()) + } + + writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, true, nil, lastFetchedTime, status) + return result, nil } @@ -58,12 +137,7 @@ func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Contex // +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) { - refresherConfig := map[string]interface{}{ - "type": refresh.KubeRefresherType, - "client": r.Client, - "request": req, - } - return r.ReconcileWithConfig(ctx, refresherConfig) + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) } // SetupWithManager sets up the controller with the Manager. @@ -77,3 +151,40 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err For(&configv1beta1.KeyManagementProvider{}).WithEventFilter(pred). Complete(r) } + +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, err, &operationTime) + } + if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { + logger.Error(statusErr, ",unable to update key management provider error status") + } +} + +// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time +func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, 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 +} + +// 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 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/clusterresource/keymanagementprovider_controller_test.go b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go index d4129e2c6..60254eeef 100644 --- a/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go +++ b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go @@ -18,82 +18,357 @@ package clusterresource import ( "context" "errors" + "fmt" "reflect" "testing" - "github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh" + 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 TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) { +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 - refresherType string - createConfigError bool - refreshError bool - expectedError bool + 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 }{ { - name: "Successful Reconcile", - refresherType: "mockRefresher", - createConfigError: false, - refreshError: false, - expectedError: false, + // 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 Error", - refresherType: "mockRefresher", - createConfigError: false, - refreshError: true, - 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: "Invalid Refresher Type", - refresherType: "invalidRefresher", - createConfigError: true, - refreshError: false, - 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 { - t.Run(tt.name, func(t *testing.T) { - req := ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: "fake-name", - Namespace: "fake-namespace", - }, - } - scheme, _ := test.CreateScheme() - client := fake.NewClientBuilder().WithScheme(scheme).Build() + 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: client, - Scheme: runtime.NewScheme(), - } + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } - refresherConfig := map[string]interface{}{ - "type": tt.refresherType, - "client": client, - "request": req, - "createConfigError": tt.createConfigError, - "refreshError": tt.refreshError, - "shouldError": tt.expectedError, - } + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) - _, err := r.ReconcileWithConfig(context.TODO(), 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) - } - }) + 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{ @@ -124,36 +399,150 @@ func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { } } -type MockRefresher struct { - Results ctrl.Result - CreateConfigError bool - RefreshError bool - ShouldError bool -} +// TestUpdateErrorStatus tests the updateErrorStatus method +func TestKMProviderUpdateErrorStatus(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) -func (mr *MockRefresher) Refresh(_ context.Context) error { - if mr.RefreshError { - return errors.New("refresh error") + 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)) } - return nil } -func (mr *MockRefresher) GetResult() interface{} { - return ctrl.Result{} +// 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") + } } -func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { - createConfigError := config["createConfigError"].(bool) - refreshError := config["refreshError"].(bool) - if createConfigError { - return nil, errors.New("create error") +// 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") } - return &MockRefresher{ - CreateConfigError: createConfigError, - RefreshError: refreshError, - }, 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 + 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, + }, + }, + } -func init() { - refresh.Register("mockRefresher", &MockRefresher{}) + 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/clusterresource/verifier_controller.go b/pkg/controllers/clusterresource/verifier_controller.go index c8c8cd838..f752fa154 100644 --- a/pkg/controllers/clusterresource/verifier_controller.go +++ b/pkg/controllers/clusterresource/verifier_controller.go @@ -85,7 +85,7 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // 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, spec.Name, spec.ArtifactTypes, spec.Source) + 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) diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go index ba439a254..2bbdd0b29 100644 --- a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go @@ -18,11 +18,18 @@ package namespaceresource import ( "context" - "fmt" + "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" @@ -37,20 +44,89 @@ type KeyManagementProviderReconciler struct { Scheme *runtime.Scheme } -func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) { - refresher, err := refresh.CreateRefresherFromConfig(config) - if err != nil { +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 { - return ctrl.Result{}, err + 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 { - return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult()) + 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 } @@ -58,12 +134,7 @@ func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Contex // +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) { - refresherConfig := map[string]interface{}{ - "type": refresh.KubeRefresherNamespacedType, - "client": r.Client, - "request": req, - } - return r.ReconcileWithConfig(ctx, refresherConfig) + return r.ReconcileWithType(ctx, req, refresh.KubeRefresherType) } // SetupWithManager sets up the controller with the Manager. @@ -77,3 +148,40 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err 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 index 7e4717a8e..d934754b8 100644 --- a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go +++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go @@ -18,80 +18,358 @@ 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 TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) { +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 - createConfigError bool - refreshError bool + expectedResult ctrl.Result expectedError bool }{ { - name: "Successful Reconcile", + // 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", - createConfigError: false, - refreshError: false, + expectedResult: ctrl.Result{}, expectedError: false, }, { - name: "Refresher Error", + 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", - createConfigError: false, - refreshError: true, + expectedResult: ctrl.Result{}, expectedError: true, }, { - name: "Invalid Refresher Type", + 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", - createConfigError: true, - refreshError: false, + 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 { - t.Run(tt.name, func(t *testing.T) { - req := ctrl.Request{ - NamespacedName: client.ObjectKey{ - Name: "fake-name", - Namespace: "fake-namespace", - }, - } - scheme, _ := test.CreateScheme() - client := fake.NewClientBuilder().WithScheme(scheme).Build() + 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: client, - Scheme: runtime.NewScheme(), - } + r := &KeyManagementProviderReconciler{ + Client: &mockClient, + Scheme: scheme, + } - refresherConfig := map[string]interface{}{ - "type": tt.refresherType, - "client": client, - "request": req, - "createConfigError": tt.createConfigError, - "refreshError": tt.refreshError, - "shouldError": tt.expectedError, - } + result, err := r.ReconcileWithType(context.Background(), req, tt.refresherType) - _, err := r.ReconcileWithConfig(context.TODO(), 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) - } - }) + 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") + } } } @@ -124,37 +402,148 @@ func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) { t.Errorf("Expected result %v, got %v", expectedResult, result) } } +func TestKMProviderUpdateErrorStatusNamespaced(t *testing.T) { + var parametersString = "{\"certs\":{\"name\":\"certName\"}}" + var kmProviderStatus = []byte(parametersString) -type MockRefresher struct { - Results ctrl.Result - CreateConfigError bool - RefreshError bool - ShouldError bool -} + 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) -func (mr *MockRefresher) Refresh(_ context.Context) error { - if mr.RefreshError { - return errors.New("refresh error") + 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)) } - return nil } -func (mr *MockRefresher) GetResult() interface{} { - return ctrl.Result{} +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 (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) { - createConfigError := config["shouldError"].(bool) - refreshError := config["refreshError"].(bool) - if createConfigError { - return nil, errors.New("create error") +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") } - return &MockRefresher{ - CreateConfigError: createConfigError, - RefreshError: refreshError, - }, nil } -func init() { - refresh.Register("mockRefresher", &MockRefresher{}) +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/verifier_controller.go b/pkg/controllers/namespaceresource/verifier_controller.go index 4b19b6178..a2926efe9 100644 --- a/pkg/controllers/namespaceresource/verifier_controller.go +++ b/pkg/controllers/namespaceresource/verifier_controller.go @@ -84,7 +84,7 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // 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, spec.Name, spec.ArtifactTypes, spec.Source) + 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) diff --git a/pkg/controllers/utils/verifier.go b/pkg/controllers/utils/verifier.go index e65a05973..43a9e858d 100644 --- a/pkg/controllers/utils/verifier.go +++ b/pkg/controllers/utils/verifier.go @@ -72,3 +72,22 @@ func SpecToVerifierConfig(raw []byte, verifierName, verifierType, artifactTypes 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 index 8ccf9a6d7..349653a07 100644 --- a/pkg/controllers/utils/verifier_test.go +++ b/pkg/controllers/utils/verifier_test.go @@ -119,3 +119,46 @@ func TestSpecToVerifierConfig(t *testing.T) { 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/keymanagementprovider/mocks/client.go b/pkg/keymanagementprovider/mocks/client.go index f24c19029..4d651ce92 100644 --- a/pkg/keymanagementprovider/mocks/client.go +++ b/pkg/keymanagementprovider/mocks/client.go @@ -17,7 +17,7 @@ package mocks import ( "context" - "fmt" + "errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -25,8 +25,43 @@ import ( 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, _ types.NamespacedName, _ client.Object, _ ...client.GetOption) error { - return fmt.Errorf("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 index 0230582e2..15a0e0d7d 100644 --- a/pkg/keymanagementprovider/mocks/factory.go +++ b/pkg/keymanagementprovider/mocks/factory.go @@ -16,6 +16,7 @@ limitations under the License. package mocks import ( + "context" "crypto" "crypto/x509" @@ -24,10 +25,19 @@ import ( ) 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}, nil + 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 246de21ba..5cfa21645 100644 --- a/pkg/keymanagementprovider/mocks/types.go +++ b/pkg/keymanagementprovider/mocks/types.go @@ -24,20 +24,32 @@ import ( ) type TestKeyManagementProvider struct { - certificates map[keymanagementprovider.KMPMapKey][]*x509.Certificate - keys map[keymanagementprovider.KMPMapKey]crypto.PublicKey - 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 { - return true + if c.IsRefreshableFunc != nil { + return c.IsRefreshableFunc() + } + return false } diff --git a/pkg/keymanagementprovider/refresh/factory.go b/pkg/keymanagementprovider/refresh/factory.go index 186951c16..eed41e119 100644 --- a/pkg/keymanagementprovider/refresh/factory.go +++ b/pkg/keymanagementprovider/refresh/factory.go @@ -21,7 +21,8 @@ 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 map[string]interface{}) (Refresher, error) + Create(config RefresherConfig) (Refresher, error) } // Refresher is an interface that defines methods to be implemented by a each refresher @@ -37,17 +38,10 @@ func Register(name string, factory RefresherFactory) { } // CreateRefresherFromConfig creates a new instance of the refresher using the provided configuration -func CreateRefresherFromConfig(refresherConfig map[string]interface{}) (Refresher, error) { - refresherType, ok := refresherConfig["type"].(string) +func CreateRefresherFromConfig(refresherConfig RefresherConfig) (Refresher, error) { + factory, ok := refresherFactories[refresherConfig.RefresherType] if !ok { - return nil, fmt.Errorf("refresher type is not a string") - } - if !ok || refresherType == "" { - return nil, fmt.Errorf("refresher type cannot be empty") - } - factory, ok := refresherFactories[refresherType] - if !ok { - return nil, fmt.Errorf("refresher factory with name %s not found", refresherType) + 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 index a4a267cc2..e8762a444 100644 --- a/pkg/keymanagementprovider/refresh/factory_test.go +++ b/pkg/keymanagementprovider/refresh/factory_test.go @@ -22,7 +22,7 @@ import ( type MockRefresher struct{} -func (f *MockRefresher) Create(_ map[string]interface{}) (Refresher, error) { +func (f *MockRefresher) Create(_ RefresherConfig) (Refresher, error) { return &MockRefresher{}, nil } @@ -34,10 +34,14 @@ func (f *MockRefresher) GetResult() interface{} { return nil } +func (f *MockRefresher) GetStatus() interface{} { + return nil +} + func TestRefreshFactory_Create(t *testing.T) { Register("mockRefresher", &MockRefresher{}) - refresherConfig := map[string]interface{}{ - "type": "mockRefresher", + refresherConfig := RefresherConfig{ + RefresherType: "mockRefresher", } factory := refresherFactories["mockRefresher"] refresher, err := factory.Create(refresherConfig) @@ -104,8 +108,8 @@ func TestCreateRefresherFromConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - refresherConfig := map[string]interface{}{ - "type": tt.refresherType, + refresherConfig := RefresherConfig{ + RefresherType: tt.refresherType, } _, err := CreateRefresherFromConfig(refresherConfig) if tt.expectedError && err == nil { diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh.go b/pkg/keymanagementprovider/refresh/kubeRefresh.go index acd967e0d..895cb7d8d 100644 --- a/pkg/keymanagementprovider/refresh/kubeRefresh.go +++ b/pkg/keymanagementprovider/refresh/kubeRefresh.go @@ -18,28 +18,23 @@ package refresh import ( "context" - "encoding/json" "fmt" "maps" "time" - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" 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/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" ) type KubeRefresher struct { - client.Client - Request ctrl.Request - Result ctrl.Result + Provider kmp.KeyManagementProvider + ProviderType string + ProviderRefreshInterval string + Resource string + Result ctrl.Result + Status kmp.KeyManagementProviderStatus } // Register registers the kubeRefresher factory @@ -51,99 +46,45 @@ func init() { func (kr *KubeRefresher) Refresh(ctx context.Context) error { logger := logrus.WithContext(ctx) - var resource = kr.Request.Name - var keyManagementProvider configv1beta1.KeyManagementProvider - - logger.Infof("reconciling cluster key management provider '%v'", resource) - - if err := kr.Get(ctx, kr.Request.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") - } - - kr.Result = ctrl.Result{} - - return 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 := kr.List(ctx, &certificateStoreList); err != nil { - logger.Error(err, "unable to list certificate stores") - kr.Result = ctrl.Result{} - return 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.ErrorCodePluginInitFailure.WithError(err).WithDetail("Failed to create key management provider from CR") - - kmp.SetCertificateError(resource, kmpErr) - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} - return kmpErr - } - // fetch certificates and store in map - certificates, certAttributes, err := provider.GetCertificates(ctx) + 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]", resource, keyManagementProvider.Spec.Type)) - - kmp.SetCertificateError(resource, err) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} + 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 := provider.GetKeys(ctx) + 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]", resource, keyManagementProvider.Spec.Type)) - - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Request = ctrl.Request{} + 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(resource, keyManagementProvider.Spec.Type, keys, certificates) + + kmp.SaveSecrets(kr.Resource, kr.ProviderType, keys, certificates) // merge certificates and keys status into one maps.Copy(keyAttributes, certAttributes) - isFetchSuccessful = true - writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, nil, lastFetchedTime, keyAttributes) + kr.Status = keyAttributes - logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource) + logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), kr.Resource) - // returning empty result and no error to indicate we’ve successfully reconciled this object + // 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 !provider.IsRefreshable() { - kr.Request = ctrl.Request{} + if !kr.Provider.IsRefreshable() { return nil } // if interval is not set, disable refresh - if keyManagementProvider.Spec.RefreshInterval == "" { - kr.Result = ctrl.Result{} + if kr.ProviderRefreshInterval == "" { return nil } + // resource is refreshable, requeue after interval - intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval) + intervalDuration, err := time.ParseDuration(kr.ProviderRefreshInterval) if err != nil { - logger.Error(err, "unable to parse interval duration") - kr.Result = ctrl.Result{} - return err + 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) @@ -157,57 +98,16 @@ func (kr *KubeRefresher) GetResult() interface{} { return kr.Result } -// Create creates a new KubeRefresher instance -func (kr *KubeRefresher) Create(config map[string]interface{}) (Refresher, error) { - client, ok := config["client"].(client.Client) - if !ok { - return nil, fmt.Errorf("client is required in config") - } - - request, ok := config["request"].(ctrl.Request) - if !ok { - return nil, fmt.Errorf("request is required in config") - } +func (kr *KubeRefresher) GetStatus() interface{} { + return kr.Status +} +// Create creates a new KubeRefresher instance +func (kr *KubeRefresher) Create(config RefresherConfig) (Refresher, error) { return &KubeRefresher{ - Client: client, - Request: request, + Provider: config.Provider, + ProviderType: config.ProviderType, + ProviderRefreshInterval: config.ProviderRefreshInterval, + Resource: config.Resource, }, nil } - -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, err, &operationTime) - } - if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil { - logger.Error(statusErr, ",unable to update key management provider error status") - } -} - -// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time -func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, 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 -} - -// 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 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/keymanagementprovider/refresh/kubeRefreshNamespaced.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go deleted file mode 100644 index 8bd4ade20..000000000 --- a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go +++ /dev/null @@ -1,211 +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 refresh - -import ( - "context" - "encoding/json" - "fmt" - "maps" - "time" - - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - 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/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" -) - -type KubeRefresherNamespaced struct { - client.Client - Request ctrl.Request - Result ctrl.Result -} - -// Register registers the kubeRefresherNamespaced factory -func init() { - Register(KubeRefresherNamespacedType, &KubeRefresherNamespaced{}) -} - -// Refresh the certificates/keys for the key management provider by calling the GetCertificates and GetKeys methods -func (kr *KubeRefresherNamespaced) Refresh(ctx context.Context) error { - logger := logrus.WithContext(ctx) - - var resource = kr.Request.NamespacedName.String() - var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider - - logger.Infof("reconciling namespaced key management provider '%v'", resource) - - if err := kr.Get(ctx, kr.Request.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") - } - - kr.Result = ctrl.Result{} - - return 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 := kr.List(ctx, &certificateStoreList); err != nil { - logger.Error(err, "unable to list certificate stores") - kr.Result = ctrl.Result{} - return 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 { - // 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.ErrorCodePluginInitFailure.WithError(err).WithDetail("Failed to create key management provider from CR") - - kmp.SetCertificateError(resource, kmpErr) - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - - // fetch certificates and store in map - certificates, certAttributes, err := 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", resource, keyManagementProvider.Spec.Type)) - - kmp.SetCertificateError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - - // fetch keys and store in map - keys, keyAttributes, err := 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", resource, keyManagementProvider.Spec.Type)) - - kmp.SetKeyError(resource, kmpErr) - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, &kmpErr, lastFetchedTime, nil) - kr.Result = ctrl.Result{} - return kmpErr - } - kmp.SaveSecrets(resource, keyManagementProvider.Spec.Type, keys, certificates) - // merge certificates and keys status into one - maps.Copy(keyAttributes, certAttributes) - isFetchSuccessful = true - writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, nil, lastFetchedTime, keyAttributes) - - logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource) - - if !provider.IsRefreshable() { - kr.Result = ctrl.Result{} - return nil - } - - // if interval is not set, disable refresh - if keyManagementProvider.Spec.RefreshInterval == "" { - kr.Result = ctrl.Result{} - return nil - } - - intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval) - if err != nil { - logger.Error(err, "unable to parse interval duration") - kr.Result = ctrl.Result{} - return err - } - - logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration) - kr.Result = ctrl.Result{RequeueAfter: intervalDuration} - - return nil -} - -// GetResult returns the result of the refresh as ctrl.Result -func (kr *KubeRefresherNamespaced) GetResult() interface{} { - return kr.Result -} - -// Create creates a new instance of KubeRefresherNamespaced -func (kr *KubeRefresherNamespaced) Create(config map[string]interface{}) (Refresher, error) { - client, ok := config["client"].(client.Client) - if !ok { - return nil, fmt.Errorf("client is required in config") - } - - request, ok := config["request"].(ctrl.Request) - if !ok { - return nil, fmt.Errorf("request is required in config") - } - - return &KubeRefresherNamespaced{ - Client: client, - Request: request, - }, nil -} - -// writeKMProviderStatus 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") - } -} - -// updateKMProviderErrorStatus 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 -} - -// 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 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/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go deleted file mode 100644 index 944f54884..000000000 --- a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go +++ /dev/null @@ -1,378 +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 refresh - -import ( - "context" - "reflect" - "testing" - "time" - - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "github.com/ratify-project/ratify/errors" - "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" - 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/client/fake" -) - -func TestKubeRefresherNamespaced_Refresh(t *testing.T) { - tests := []struct { - name string - provider *configv1beta1.NamespacedKeyManagementProvider - request ctrl.Request - mockClient bool - expectedResult ctrl.Result - expectedError bool - }{ - { - name: "Non-refreshable", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - 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"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "Disabled", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "Refreshable", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "1m", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{RequeueAfter: time.Minute}, - expectedError: false, - }, - { - name: "Invalid Interval", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "", - RefreshInterval: "1mm", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: true, - }, - { - name: "IsNotFound", - provider: &configv1beta1.NamespacedKeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.NamespacedKeyManagementProviderSpec{ - Type: "", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, - }, - { - name: "UnableToFetchKMP", - mockClient: true, - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var client client.Client - - if tt.mockClient { - client = mocks.TestClient{} - } else { - scheme, _ := test.CreateScheme() - client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build() - } - - kr := &KubeRefresherNamespaced{ - Client: client, - Request: tt.request, - } - 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 TestKubeRefresherNamespaced_Create(t *testing.T) { - tests := []struct { - name string - config map[string]interface{} - expectedError bool - }{ - { - name: "Success", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - "request": ctrl.Request{}, - }, - expectedError: false, - }, - { - name: "ClientMissing", - config: map[string]interface{}{ - "request": ctrl.Request{}, - }, - expectedError: true, - }, - { - name: "RequestMissing", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - }, - expectedError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - kr := &KubeRefresherNamespaced{} - _, err := kr.Create(tt.config) - if tt.expectedError && err == nil { - t.Fatalf("Expected error but got nil") - } - }) - } -} - -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/keymanagementprovider/refresh/kubeRefresh_test.go b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go index 29319abcd..9875098b8 100644 --- a/pkg/keymanagementprovider/refresh/kubeRefresh_test.go +++ b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go @@ -18,168 +18,120 @@ package refresh import ( "context" + "crypto" + "crypto/x509" + "errors" "reflect" "testing" "time" - configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" - re "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/inline" - "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" - 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" + mock "github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" ) func TestKubeRefresher_Refresh(t *testing.T) { tests := []struct { - name string - provider *configv1beta1.KeyManagementProvider - request ctrl.Request - mockClient bool - expectedResult ctrl.Result - expectedError bool + 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", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - 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"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: false, + 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", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - 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", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "test-kmp", - RefreshInterval: "1m", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{RequeueAfter: time.Minute}, - 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", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "", - RefreshInterval: "1mm", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, - }, - request: ctrl.Request{ - NamespacedName: client.ObjectKey{ - Namespace: "", - Name: "kmpName", - }, - }, - expectedResult: ctrl.Result{}, - expectedError: true, + 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: "IsNotFound", - provider: &configv1beta1.KeyManagementProvider{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "kmpName", - }, - Spec: configv1beta1.KeyManagementProviderSpec{ - Type: "", - RefreshInterval: "", - Parameters: runtime.RawExtension{ - Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`), - }, - }, + 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") }, - expectedResult: ctrl.Result{}, - expectedError: false, + 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: "UnableToFetchKMP", - mockClient: 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 client client.Client - - if tt.mockClient { - client = mocks.TestClient{} + 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 { - scheme, _ := test.CreateScheme() - client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build() + factory = mock.TestKeyManagementProviderFactory{ + IsRefreshableFunc: tt.IsRefreshableFunc, + } } + provider, _ := factory.Create("", config.KeyManagementProviderConfig{}, "") + kr := &KubeRefresher{ - Client: client, - Request: tt.request, + Provider: provider, + ProviderType: tt.providerType, + ProviderRefreshInterval: tt.providerRefreshInterval, + Resource: "kmpname", } + err := kr.Refresh(context.Background()) result := kr.GetResult() if !reflect.DeepEqual(result, tt.expectedResult) { @@ -192,190 +144,93 @@ func TestKubeRefresher_Refresh(t *testing.T) { } } -func TestKubeRefresher_Create(t *testing.T) { - tests := []struct { - name string - config map[string]interface{} - expectedError bool - }{ - { - name: "Success", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - "request": ctrl.Request{}, - }, - expectedError: false, - }, - { - name: "ClientMissing", - config: map[string]interface{}{ - "request": ctrl.Request{}, - }, - expectedError: true, - }, - { - name: "RequestMissing", - config: map[string]interface{}{ - "client": &mocks.TestClient{}, - }, - expectedError: true, - }, +func TestKubeRefresher_GetResult(t *testing.T) { + kr := &KubeRefresher{ + Result: ctrl.Result{RequeueAfter: time.Minute}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - kr := &KubeRefresher{} - _, err := kr.Create(tt.config) - if tt.expectedError && err == nil { - t.Fatalf("Expected error but got nil") - } - }) - } -} + result := kr.GetResult() + expectedResult := ctrl.Result{RequeueAfter: time.Minute} -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)) + if !reflect.DeepEqual(result, expectedResult) { + t.Fatalf("Expected result %v, but got %v", expectedResult, result) } } - -// 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) +func TestKubeRefresher_GetStatus(t *testing.T) { + kr := &KubeRefresher{ + Status: keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", + }, } - if keyManagementProvider.Status.Error != "" { - t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error) + status := kr.GetStatus() + expectedStatus := keymanagementprovider.KeyManagementProviderStatus{ + "attribute1": "value1", + "attribute2": "value2", } - //make sure properties of last cached cert was updated - if len(keyManagementProvider.Status.Properties.Raw) != 0 { - t.Fatalf("Properties should be empty") + if !reflect.DeepEqual(status, expectedStatus) { + t.Fatalf("Expected status %v, but got %v", expectedStatus, status) } } - -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 +func TestKubeRefresher_Create(t *testing.T) { + tests := []struct { + name string + config RefresherConfig + expectedProviderType string + expectedRefreshInterval string + expectedResource string }{ { - 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: "Valid Config", + config: RefresherConfig{ + Provider: &mock.TestKeyManagementProvider{}, + ProviderType: "test-kmp", + ProviderRefreshInterval: "1m", + Resource: "test-resource", + }, + expectedProviderType: "test-kmp", + expectedRefreshInterval: "1m", + expectedResource: "test-resource", }, { - name: "status update failed", - isSuccess: true, - kmProvider: &configv1beta1.KeyManagementProvider{}, - reconciler: &test.MockStatusClient{ - UpdateFailed: true, + name: "Empty Config", + config: RefresherConfig{ + Provider: nil, + ProviderType: "", + ProviderRefreshInterval: "", + Resource: "", }, + expectedProviderType: "", + expectedRefreshInterval: "", + expectedResource: "", }, } - 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) + 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 tc.kmProvider.Status.IsSuccess != tc.isSuccess { - t.Fatalf("Expected isSuccess to be: %+v, actual: %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess) + if kubeRefresher.ProviderRefreshInterval != tt.expectedRefreshInterval { + t.Fatalf("Expected ProviderRefreshInterval %v, but got %v", tt.expectedRefreshInterval, kubeRefresher.ProviderRefreshInterval) } - if tc.kmProvider.Status.Error != tc.expectedErrString { - t.Fatalf("Expected Error to be: %+v, actual: %+v", tc.expectedErrString, tc.kmProvider.Status.Error) + 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 index 78e32b12d..36e05e243 100644 --- a/pkg/keymanagementprovider/refresh/refresh.go +++ b/pkg/keymanagementprovider/refresh/refresh.go @@ -18,11 +18,12 @@ package refresh import ( "context" + + "github.com/ratify-project/ratify/pkg/keymanagementprovider" ) const ( - KubeRefresherType = "kubeRefresher" - KubeRefresherNamespacedType = "kubeRefresherNamespaced" + KubeRefresherType = "kubeRefresher" ) // Refresher is an interface that defines methods to be implemented by a each refresher @@ -31,4 +32,15 @@ type Refresher interface { 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/referrerstore/oras/cosign.go b/pkg/referrerstore/oras/cosign.go index a60f458ae..4f6fc2457 100644 --- a/pkg/referrerstore/oras/cosign.go +++ b/pkg/referrerstore/oras/cosign.go @@ -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/oras.go b/pkg/referrerstore/oras/oras.go index a8462d201..64679dd0a 100644 --- a/pkg/referrerstore/oras/oras.go +++ b/pkg/referrerstore/oras/oras.go @@ -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 @@ -260,7 +260,7 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com 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 @@ -292,10 +292,10 @@ 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.ErrorCodeGetBlobContentFailure.WithError(err) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to parse the artifact metadata").WithError(err) } // push fetched content to local ORAS cache diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go index fb3efd714..86442859a 100644 --- a/pkg/verifier/cosign/cosign.go +++ b/pkg/verifier/cosign/cosign.go @@ -143,17 +143,17 @@ 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("'key' and 'rekorURL' are part of cosign legacy configuration and cannot be used with `trustPolicies`") + return nil, re.ErrorCodeConfigInvalid.WithDetail("'key' and 'rekorURL' are part of Cosign legacy configuration and cannot be used with `trustPolicies` parameter") } var trustPolicies *TrustPolicies @@ -163,7 +163,7 @@ func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierC 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, err + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Cosign Verifier").WithError(err) } legacy = false } @@ -224,18 +224,18 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co // get the reference manifest (cosign oci image) referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get reference manifest: %w", 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, fmt.Errorf("reference manifest is not an image")), nil + 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, fmt.Errorf("failed to create subject hash: %w", 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) @@ -255,23 +255,23 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co // 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, fmt.Errorf("failed to get blob content: %w", 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, fmt.Errorf("failed to parse static signature opts: %w", 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, fmt.Errorf("failed to generate static signature: %w", 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, fmt.Errorf("failed to verify with keys: %w", 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 { @@ -295,7 +295,7 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co ), nil } - errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid signatures found")) + errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid Cosign signatures found")) errorResult.Extensions = Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()} return errorResult, nil } @@ -485,7 +485,7 @@ 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 { - verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail("Verification failed").WithError(err) + verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail("Failed to validate the Cosign signature").WithError(err) return verifier.NewVerifierResult( "", name, @@ -540,14 +540,14 @@ func verifyWithKeys(ctx context.Context, keysMap map[PKKey]keymanagementprovider if pubKey.ProviderType == azurekeyvault.ProviderName { hashType, sig, err = processAKVSignature(sigEncoded, sig, pubKey.Key, payload, staticOpts) if err != nil { - return verifications, false, fmt.Errorf("failed to process AKV signature: %w", err) + 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, fmt.Errorf("failed to load public key from provider [%s] name [%s] version [%s]: %w", mapKey.Provider, mapKey.Name, mapKey.Version, err) + 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 @@ -627,17 +627,17 @@ func processAKVSignature(sigEncoded string, staticSig oci.Signature, publicKey c // 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, fmt.Errorf("RSA key check: failed to decode base64 signature: %w", err) + 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, fmt.Errorf("RSA key check: failed to decode ASN.1 signature: %w", err) + 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, fmt.Errorf("RSA key check: failed to generate static signature: %w", err) + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to generate static signature").WithError(err) } case *ecdsa.PublicKey: switch keyType.Curve { @@ -648,10 +648,10 @@ func processAKVSignature(sigEncoded string, staticSig oci.Signature, publicKey c case elliptic.P521(): hashType = crypto.SHA512 default: - return crypto.SHA256, nil, fmt.Errorf("ECDSA key check: unsupported key curve: %s", keyType.Params().Name) + 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 crypto.SHA256, nil, fmt.Errorf("unsupported public key type [%T]", publicKey) } return hashType, staticSig, nil } diff --git a/pkg/verifier/cosign/cosign_test.go b/pkg/verifier/cosign/cosign_test.go index b4e368617..aea1cf5a7 100644 --- a/pkg/verifier/cosign/cosign_test.go +++ b/pkg/verifier/cosign/cosign_test.go @@ -23,6 +23,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "encoding/base64" "fmt" "io" "slices" @@ -114,6 +115,26 @@ func TestCreate(t *testing.T) { }, 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{ @@ -407,8 +428,8 @@ func TestErrorToVerifyResult(t *testing.T) { if verifierResult.Type != "cosign" { t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Type, "cosign") } - if verifierResult.Message != "Verification failed" { - t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "Verification failed") + 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") @@ -573,7 +594,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry keys: map[PKKey]keymanagementprovider.PublicKey{}, getKeysError: true, store: &mocks.MemoryTestStore{}, - expectedResultMessagePrefix: "Verification failed", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", expectedErrorReason: "error", }, { @@ -581,8 +602,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry keys: map[PKKey]keymanagementprovider.PublicKey{}, getKeysError: false, store: &mocks.MemoryTestStore{}, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to get reference manifest: manifest not found", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "manifest not found", }, { name: "incorrect reference manifest media type error", @@ -595,8 +616,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "reference manifest is not an image", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "The artifact metadata is not an OCI image", }, { name: "failed subject descriptor fetch", @@ -609,8 +630,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to create subject hash: subject not found for sha256:5678", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "subject not found for sha256:5678", }, { name: "failed to fetch blob", @@ -636,8 +657,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to get blob content: blob not found", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "blob not found", }, { name: "invalid key type for AKV", @@ -668,8 +689,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: unsupported public key type: *ecdh.PublicKey", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "unsupported public key type [*ecdh.PublicKey]", }, { name: "invalid RSA key size for AKV", @@ -700,8 +721,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: RSA key check: unsupported key size: 128", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "RSA key check: unsupported key size: 128", }, { name: "invalid ECDSA curve type for AKV", @@ -732,8 +753,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: ECDSA key check: unsupported key curve: P-224", + 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", @@ -965,8 +986,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry "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 failed", - expectedErrorReason: "failed to parse static signature opts: failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91", + expectedResultMessagePrefix: "Failed to validate the Cosign signature:", + expectedErrorReason: "failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91", }, } @@ -1051,3 +1072,81 @@ func TestVerificationMessage(t *testing.T) { }) } } + +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 index 4858cfd47..6ba5521ed 100644 --- a/pkg/verifier/cosign/trustpolicies.go +++ b/pkg/verifier/cosign/trustpolicies.go @@ -35,14 +35,14 @@ 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("failed to create trust policies: no policies found") + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("failed to create trust policies: duplicate policy name %s", policyConfig.Name)) + 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) @@ -86,7 +86,7 @@ func (tps *TrustPolicies) GetScopedPolicy(reference string) (TrustPolicy, error) if globalPolicy != nil { return globalPolicy, nil } - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to get trust policy: no policy found for reference %s", reference)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("No policy found for the artifact %s", reference)) } // validateScopes validates the scopes in the trust policies @@ -97,16 +97,16 @@ func validateScopes(policies []TrustPolicy) error { policyName := policy.GetName() scopes := policy.GetScopes() if len(scopes) == 0 { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: no scopes defined for trust policy %s", policyName)) + 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.WithComponentType(re.Verifier).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)) + 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.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: global wildcard character %c can only be used once", GlobalWildcardCharacter)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: global wildcard character %c can only be used once", GlobalWildcardCharacter)) } hasGlobalWildcard = true continue @@ -114,15 +114,15 @@ func validateScopes(policies []TrustPolicy) error { for _, scope := range scopes { // check for empty scope if scope == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: scope defined is empty for trust policy %s", policyName)) + 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.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: invalid scope %s for trust policy %s", scope, policyName)) + 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.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: duplicate scope %s for trust policy %s", scope, policyName)) + 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 { @@ -144,7 +144,7 @@ func validateScopes(policies []TrustPolicy) error { isConflict = strings.HasPrefix(existingScope, trimmedScope) } if isConflict { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: overlapping scopes %s and %s for trust policy %s", scope, existingScope, policyName)) + 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{}{} diff --git a/pkg/verifier/cosign/trustpolicy.go b/pkg/verifier/cosign/trustpolicy.go index c49b86aa8..8d20a7320 100644 --- a/pkg/verifier/cosign/trustpolicy.go +++ b/pkg/verifier/cosign/trustpolicy.go @@ -97,7 +97,7 @@ func CreateTrustPolicy(config TrustPolicyConfig, verifierName string) (TrustPoli config.Version = DefaultTrustPolicyConfigVersion } - if err := validate(config, verifierName); err != nil { + if err := validate(config); err != nil { return nil, err } @@ -107,7 +107,7 @@ func CreateTrustPolicy(config TrustPolicyConfig, verifierName string) (TrustPoli if keyConfig.File != "" { pubKey, err := loadKeyFromPath(keyConfig.File) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: failed to load key from file %s", config.Name, keyConfig.File)).WithError(err) + 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} } @@ -155,13 +155,13 @@ func (tp *trustPolicy) GetKeys(ctx context.Context, _ string) (map[PKKey]keymana // 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.WithComponentType(re.Verifier).WithPluginName(tp.verifierName).WithDetail(fmt.Sprintf("trust policy [%s] failed to access key management provider %s, err: %s", tp.config.Name, keyConfig.Provider, kmpErr.Error())) + 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.WithComponentType(re.Verifier).WithPluginName(tp.verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key %s with version %s not found in key management provider %s", tp.config.Name, keyConfig.Name, keyConfig.Version, keyConfig.Provider)) + 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 { @@ -188,12 +188,12 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err // create the rekor client cosignOpts.RekorClient, err = rekor.NewClient(tp.config.RekorURL) if err != nil { - return cosignOpts, fmt.Errorf("failed to create Rekor client from URL %s: %w", tp.config.RekorURL, err) + 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, fmt.Errorf("failed to fetch Rekor public keys: %w", err) + 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 @@ -203,20 +203,20 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err if tp.isKeyless { roots, err := fulcio.GetRoots() if err != nil || roots == nil { - return cosignOpts, fmt.Errorf("failed to get fulcio roots: %w", err) + 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, fmt.Errorf("failed to fetch certificate transparency log public keys: %w", err) + 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, fmt.Errorf("failed to get fulcio intermediate certificates: %w", err) + 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{ @@ -234,42 +234,42 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err // validate checks if the trust policy configuration is valid // returns an error if the configuration is invalid -func validate(config TrustPolicyConfig, verifierName string) error { +func validate(config TrustPolicyConfig) error { // check if the trust policy version is supported if !slices.Contains(SupportedTrustPolicyConfigVersions, config.Version) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: unsupported version %s", config.Name, 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("missing trust policy 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: no scopes defined", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: no keys defined and keyless section not configured", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: both keys and keyless sections are defined", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key management provider name is required when not using file path", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: 'name' and 'file' cannot be configured together", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key name is required when key version is defined", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: key name is required when key version is defined", config.Name)) } } @@ -277,19 +277,19 @@ func validate(config TrustPolicyConfig, verifierName string) error { if config.Keyless != (KeylessConfig{}) { // validate certificate identity specified if config.Keyless.CertificateIdentity == "" && config.Keyless.CertificateIdentityRegExp == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: certificate identity or identity regex pattern is required", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: certificate OIDC issuer or issuer regex pattern is required", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: only one of certificate identity or identity regex pattern should be specified", config.Name)) + 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.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: only one of certificate OIDC issuer or issuer regex pattern should be specified", config.Name)) + 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)) } } diff --git a/pkg/verifier/cosign/trustpolicy_test.go b/pkg/verifier/cosign/trustpolicy_test.go index ea720c50e..65dcb84da 100644 --- a/pkg/verifier/cosign/trustpolicy_test.go +++ b/pkg/verifier/cosign/trustpolicy_test.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "crypto/x509" "fmt" + "os" "testing" ctxUtils "github.com/ratify-project/ratify/internal/context" @@ -204,6 +205,20 @@ func TestGetKeys(t *testing.T) { }, 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{ @@ -423,7 +438,7 @@ func TestValidate(t *testing.T) { for _, tt := range tc { t.Run(tt.name, func(t *testing.T) { - actual := validate(tt.policyConfig, "test-verifier") + actual := validate(tt.policyConfig) if (actual != nil) != tt.wantErr { t.Fatalf("expected %v, got %v", tt.wantErr, actual) } @@ -447,3 +462,76 @@ func TestLoadKeyFromPath(t *testing.T) { 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/notation/notation.go b/pkg/verifier/notation/notation.go index f25b21c10..e825db11d 100644 --- a/pkg/verifier/notation/notation.go +++ b/pkg/verifier/notation/notation.go @@ -237,7 +237,10 @@ func normalizeVerificationCertsStores(conf *NotationPluginVerifierConfig) error } } if isCertStoresByType && isLegacyCertStore { - return re.ErrorCodeConfigInvalid.WithDetail("The verificationCertStores is misconfigured with both legacy and new formats").WithRemediation("Please provide only one format for the VerificationCertStores. Refer to the Notation Verifier configuration guide: https://ratify.dev/docs/plugins/verifier/notation#configuration") + // 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 { diff --git a/scripts/azure-ci-test.sh b/scripts/azure-ci-test.sh index c5c7be9c5..b5ddce9ce 100755 --- a/scripts/azure-ci-test.sh +++ b/scripts/azure-ci-test.sh @@ -28,7 +28,7 @@ export KEYVAULT_NAME="${KEYVAULT_NAME:-ratify-akv-${SUFFIX}}" export USER_ASSIGNED_IDENTITY_NAME="${USER_ASSIGNED_IDENTITY_NAME:-ratify-e2e-identity-${SUFFIX}}" export LOCATION="westus2" export KUBERNETES_VERSION=${1:-1.29.2} -GATEKEEPER_VERSION=${2:-3.16.0} +GATEKEEPER_VERSION=${2:-3.17.0} TENANT_ID=$3 export RATIFY_NAMESPACE=${4:-gatekeeper-system} CERT_DIR=${5:-"~/ratify/certs"} diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml index bb1564a44..2fdfc94dd 100644 --- a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml +++ b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml @@ -9,7 +9,7 @@ spec: verificationCertStores: ca: ca-certs: - - certstore-inline + - certstore-inline trustPolicyDoc: version: "1.0" trustPolicies: