diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 90541b62b..388db24df 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: with: go-version: "1.22" - name: Initialize CodeQL - uses: github/codeql-action/init@2d790406f505036ef40ecba973cc774a50395aac # tag=v3.25.13 + uses: github/codeql-action/init@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # tag=v3.25.14 with: languages: go - name: Run tidy @@ -46,4 +46,4 @@ jobs: - name: Build CLI run: make build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2d790406f505036ef40ecba973cc774a50395aac # tag=v3.25.13 + uses: github/codeql-action/analyze@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # tag=v3.25.14 diff --git a/.github/workflows/publish-cosign-sample.yml b/.github/workflows/publish-cosign-sample.yml index 1a0e99aca..b1957923b 100644 --- a/.github/workflows/publish-cosign-sample.yml +++ b/.github/workflows/publish-cosign-sample.yml @@ -38,7 +38,7 @@ jobs: KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} - name: Log in to GHCR - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index 8f806675c..febca49ba 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -41,7 +41,7 @@ jobs: echo ::set-output name=baseref::${REPOSITORYBASE} echo ::set-output name=crdref::${REPOSITORYCRD} - name: docker login - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 7c65f2266..d191b43d7 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -40,7 +40,7 @@ jobs: run: | echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: docker login - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/publish-sample.yml b/.github/workflows/publish-sample.yml index d9aa34672..fc887ca8b 100644 --- a/.github/workflows/publish-sample.yml +++ b/.github/workflows/publish-sample.yml @@ -28,7 +28,7 @@ jobs: echo "REPOSITORY=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_ENV - name: Log in to the GHCR - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 84c42bce7..fe95075f4 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -53,6 +53,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # tag=v3.25.13 + uses: github/codeql-action/upload-sarif@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # tag=v3.25.14 with: sarif_file: results.sarif diff --git a/docs/design/Config Policy Provider Refactor.md b/docs/design/Config Policy Provider Refactor.md index c3fab1c6b..edd1496d1 100644 --- a/docs/design/Config Policy Provider Refactor.md +++ b/docs/design/Config Policy Provider Refactor.md @@ -245,7 +245,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validation[1].nestedReports) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -309,7 +309,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validation[1].nestedReports) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -367,7 +367,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validate[1]) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } libs: - | @@ -466,7 +466,7 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] failed_verify(subject_validate[1]) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } # check if there is an invalid subject diff --git a/docs/design/kmp-periodic-retrieval.md b/docs/design/kmp-periodic-retrieval.md new file mode 100644 index 000000000..20bec5f33 --- /dev/null +++ b/docs/design/kmp-periodic-retrieval.md @@ -0,0 +1,104 @@ +# Periodic Key & Certificate Retrieval for KMP + +Author: Josh Duffney (@duffney) + +Tracked issues in scope: + +- https://github.com/ratify-project/ratify/issues/1131 + +Proposal ref: + +- https://github.com/ratify-project/ratify/blob/dev/docs/proposals/Automated-Certificate-and-Key-Updates.md + +## Problem Statement + +In v1.2.0 and earlier, Ratify does not support automatic refreshing, requiring manual updates to the KMP resource to accommodate key and certificate changes. This also means that Ratify continues to use the cached versions of keys or certificates, leading to potential issues such as: + +**Signature Verification Failures**: When images are signed with a newly rotated key version, Ratify’s cached key version fails to verify these signatures. + +**Persisting Old Images**: Signature verification may fail for older images if Ratify only caches the latest key version. + +**Usage of Disabled Keys**: Disabled keys, perhaps due to compromise, may still be used by Ratify from its cache, posing security risks. + +Manual updates to KMP resources to accommodate key rotation are cumbersome and prone to misconfigurations, potentially causing image verification failures and service downtime. + +## Proposed Solution + +To address these challenges, this proposal suggests automating the update process of KMP resources in Ratify. This can be achieved by implementing a requeue mechanism on the KMP resource at a user defined interval. + +## Implementation Details + +Kubernetes has a built-in feature that allows the controller's reconcile methods to be requeued, which is responsible for populating the certificate and key values from the providers in Ratify's KMP resources. This is achieved by passing {Requeue: true, RequeueAfter: interval} to the ctrl.Request returned by the KMP controller's reconcile method. + +However, not all providers support being refreshed. For example, an Inline provider would not benefit from being requeued after the resource is created. To address this, the KMP interface will be updated with an isRefreshable method. This allows the provider author to indicate whether the provider supports refreshing the certificates and keys for the resource. + +A new spec field called interval will be added to the keymanagementprovider_types.go file to determine when the refresh will occur. The interval can be specified as Xs, Xm, Xh, etc., indicating how often the KMP resource should refresh its certificates and keys. + +Refreshing the resources involves calling the getCertificate and getKeys methods of the provider configured for each resource. If a resource is not refreshable, these methods will only be called once to set up the resource and populate the in-memory maps containing the provider's keys and certificates. If the provider is refreshable, the get methods will be called again each time the interval triggers. + +An example of this implementation can be found below: + +```go +// keymanagementprovider_controller.go +func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + //Do not requeue + return ctrl.Result{} + //Requeue + return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil +} +``` + +```go +type KeyManagementProvider interface { + // Returns an array of certificates and the provider specific cert attributes + GetCertificates(ctx context.Context) (map[KMPMapKey][]*x509.Certificate, KeyManagementProviderStatus, error) + // Returns an array of keys and the provider specific key attributes + GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error) + // Returns if the provider supports refreshing of certificates & keys + IsRefreshable() bool +} +``` + +```yml +## Example KMP Resource +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: +name: keymanagementprovider-akv +spec: + type: azurekeyvault + interval: "1m" # defines the requeue interval of the resource. Aslo supports 1s,1m,1h formats. + parameters: + vaultURI: https://${AKV_NAME}.vault.azure.net/ + keys: + - name: ${KEY_NAME} + tenantID: ${TENANT_ID} + clientID: ${IDENTITY_CLIENT_ID} +--- +apiVersion: config.ratify.deislabs.io/v1beta1 +kind: KeyManagementProvider +metadata: +name: keymanagementprovider-inline +spec: + type: inline + parameters: + contentType: key + value: ${Public_Key} +``` + +## Dev Work Items + +Suggested steps to implement the proposed solution: + +- Add `isRefreshable` method to the KMP interface +- Implement a `refresh` interface that encapsulates the reconcile logic +- Add an `Interval` field to the KMP CRD spec that supports the format "Xs,Xm,Xh" + +## Open Questions + +- How frequently should Ratify fetch the latest key or certificate versions from the KMS? +- How should Ratify support manual updates to the KMP cache? + +## Future Considerations + +- Event-Driven Key & Certificate Retrieval: Implementing an event-driven mechanism to fetch the latest key or certificate versions from the KMS based on specific triggers or events. diff --git a/library/default/template.yaml b/library/default/template.yaml index 50d0077a2..a37192135 100644 --- a/library/default/template.yaml +++ b/library/default/template.yaml @@ -11,7 +11,7 @@ spec: - target: admission.k8s.gatekeeper.sh rego: | package ratifyverification - + # Get data from Ratify remote_data := response { images := [img | img = input.review.object.spec.containers[_].image] @@ -26,23 +26,23 @@ spec: violation[{"msg": msg}] { general_violation[{"result": msg}] } - + # Check if there are any system errors general_violation[{"result": result}] { err := remote_data.system_error err != "" result := sprintf("System error calling external data provider: %s", [err]) } - + # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 result := sprintf("Error validating one or more images: %s", remote_data.errors) } - + # Check if the success criteria is true general_violation[{"result": result}] { subject_validation := remote_data.responses[_] subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } diff --git a/library/multi-tenancy-validation/template.yaml b/library/multi-tenancy-validation/template.yaml index 425de64fd..577c709e7 100644 --- a/library/multi-tenancy-validation/template.yaml +++ b/library/multi-tenancy-validation/template.yaml @@ -44,5 +44,5 @@ spec: general_violation[{"result": result}] { subject_validation := remote_data.responses[_] subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } diff --git a/library/notation-issuer-validation/template.yaml b/library/notation-issuer-validation/template.yaml index 44caf1cb7..d0f04dac6 100644 --- a/library/notation-issuer-validation/template.yaml +++ b/library/notation-issuer-validation/template.yaml @@ -17,7 +17,7 @@ spec: - target: admission.k8s.gatekeeper.sh rego: | package notationissuervalidation - + # Get data from Ratify remote_data := response { images := [img | img = input.review.object.spec.containers[_].image] @@ -32,25 +32,25 @@ spec: violation[{"msg": msg}] { general_violation[{"result": msg}] } - + # Check if there are any system errors general_violation[{"result": result}] { err := remote_data.system_error err != "" result := sprintf("System error calling external data provider: %s", [err]) } - + # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 result := sprintf("Error validating one or more images: %s", remote_data.errors) } - + # Check if the success criteria is true general_violation[{"result": result}] { subject_validation := remote_data.responses[_] subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } # Check that signature result for Issuer exists @@ -62,7 +62,7 @@ spec: count(issuer_results) == 0 result := sprintf("Subject %s has no signatures for certificate with Issuer: %s", [subject_results[0], input.parameters.issuer]) } - + # Check for valid signature general_violation[{"result": result}] { subject_results := remote_data.responses[_] diff --git a/library/notation-nested-validation/template.yaml b/library/notation-nested-validation/template.yaml index 39db14e82..97d1553ae 100644 --- a/library/notation-nested-validation/template.yaml +++ b/library/notation-nested-validation/template.yaml @@ -57,7 +57,7 @@ spec: subject_validation := remote_data.responses[_] subject_result := subject_validation[1] failed_verify(subject_result) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Failed to verify the artifact: %s", [subject_validation[0]]) } failed_verify(reports) if { @@ -85,4 +85,3 @@ spec: ] number := count(sigs) } - \ No newline at end of file