From b32db85b4bc3f76d5d63de9c23d6d6a46559be09 Mon Sep 17 00:00:00 2001 From: Josh Duffney Date: Thu, 12 Sep 2024 08:37:08 -0500 Subject: [PATCH] fix: missing status update in KMP controller (#1761) --- .github/workflows/build-pr.yml | 16 +- .github/workflows/cache-cleanup.yml | 6 +- .github/workflows/clean-dev-package.yml | 16 +- .github/workflows/codeql.yml | 7 +- .github/workflows/e2e-aks.yml | 12 +- .github/workflows/e2e-cli.yml | 15 +- .github/workflows/e2e-k8s.yml | 12 +- .github/workflows/golangci-lint.yml | 2 +- .github/workflows/high-availability.yml | 2 +- .github/workflows/pr-to-main.yml | 2 +- .github/workflows/publish-charts.yml | 2 +- .github/workflows/publish-cosign-sample.yml | 10 +- .github/workflows/publish-dev-assets.yml | 8 +- .github/workflows/publish-sample.yml | 18 +- .github/workflows/release.yml | 72 +-- .github/workflows/run-full-validation.yml | 8 +- .github/workflows/scan-vulns.yaml | 2 +- .github/workflows/scorecards.yml | 6 +- .github/workflows/sync-gh-pages.yml | 8 +- .../keymanagementprovider_controller.go | 131 ++++- .../keymanagementprovider_controller_test.go | 537 +++++++++++++++--- .../keymanagementprovider_controller.go | 132 ++++- .../keymanagementprovider_controller_test.go | 517 ++++++++++++++--- pkg/keymanagementprovider/mocks/client.go | 41 +- pkg/keymanagementprovider/mocks/factory.go | 12 +- pkg/keymanagementprovider/mocks/types.go | 22 +- pkg/keymanagementprovider/refresh/factory.go | 16 +- .../refresh/factory_test.go | 14 +- .../refresh/kubeRefresh.go | 164 ++---- .../refresh/kubeRefreshNamespaced.go | 211 ------- .../refresh/kubeRefreshNamespaced_test.go | 378 ------------ .../refresh/kubeRefresh_test.go | 435 +++++--------- pkg/keymanagementprovider/refresh/refresh.go | 16 +- 33 files changed, 1538 insertions(+), 1312 deletions(-) delete mode 100644 pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go delete mode 100644 pkg/keymanagementprovider/refresh/kubeRefreshNamespaced_test.go diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index c2a31ba42..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') }} @@ -26,7 +26,7 @@ jobs: matrix: KUBERNETES_VERSION: ["1.29.2"] GATEKEEPER_VERSION: ["3.17.0"] - uses: ./.github/workflows/e2e-k8s.yml + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_VERSION }} @@ -39,10 +39,10 @@ jobs: matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] - uses: ./.github/workflows/e2e-k8s.yml + 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" @@ -60,9 +60,9 @@ jobs: 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 @@ -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 a248774bf..46042f7f1 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -10,7 +10,7 @@ permissions: jobs: cleanup: runs-on: ubuntu-latest - steps: + steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: @@ -19,7 +19,7 @@ jobs: - 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 e355094ca..0a53bd8d0 100644 --- a/.github/workflows/clean-dev-package.yml +++ b/.github/workflows/clean-dev-package.yml @@ -10,7 +10,7 @@ jobs: cleanup-packages: runs-on: ubuntu-latest permissions: - packages: write + packages: write steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 @@ -19,15 +19,15 @@ jobs: - 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 4d15ab240..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 diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index 90799a297..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.17.0' + default: "3.17.0" type: string jobs: @@ -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 895d77f26..67038445c 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -29,8 +29,7 @@ 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: @@ -89,8 +88,8 @@ 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@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: @@ -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 22eaa69fc..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.17.0' + default: "3.17.0" type: string jobs: @@ -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 769e06009..cb827af40 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -21,7 +21,7 @@ jobs: - 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 af3ec3b2f..631f9bb6f 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -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 909d7b24c..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: diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index 44b774b5e..850838750 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -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 2dd20426f..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,7 +18,7 @@ jobs: contents: write packages: write id-token: write - steps: + steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: @@ -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 0bc23c8da..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 @@ -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-sample.yml b/.github/workflows/publish-sample.yml index 91cd14a4a..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@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/release.yml b/.github/workflows/release.yml index ab898be3f..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@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.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 f6d03c15b..3f2464cbc 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -28,7 +28,7 @@ jobs: matrix: KUBERNETES_VERSION: ["1.28.12", "1.29.2"] GATEKEEPER_VERSION: ["3.15.0", "3.16.0", "3.17.0"] - uses: ./.github/workflows/e2e-k8s.yml + uses: ./.github/workflows/e2e-k8s.yml with: k8s_version: ${{ matrix.KUBERNETES_VERSION }} gatekeeper_version: ${{ matrix.GATEKEEPER_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 @@ -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 105eed160..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 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2180ee329..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,7 +27,7 @@ jobs: id-token: write actions: read contents: read - + steps: - name: Harden Runner uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 @@ -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 9af80879f..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 @@ -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/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/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/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 }