diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 0af3612f8..920d6f43d 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -70,7 +70,7 @@ jobs: environment: azure-test steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index 5fbb3d0c8..b2b69de3f 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/clean-dev-package.yml b/.github/workflows/clean-dev-package.yml index 98cc5cfff..ce9e17ac3 100644 --- a/.github/workflows/clean-dev-package.yml +++ b/.github/workflows/clean-dev-package.yml @@ -13,7 +13,7 @@ jobs: packages: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 82ddd78ea..35955608d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml index a202673ea..a5700a861 100644 --- a/.github/workflows/e2e-aks.yml +++ b/.github/workflows/e2e-aks.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml index 324de264f..c4c64ea19 100644 --- a/.github/workflows/e2e-cli.yml +++ b/.github/workflows/e2e-cli.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit @@ -35,7 +35,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit @@ -64,7 +64,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml index 57a04b69a..4736406d5 100644 --- a/.github/workflows/e2e-k8s.yml +++ b/.github/workflows/e2e-k8s.yml @@ -26,7 +26,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index dd5bec53a..c40ca040a 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml index e711f94f8..34f04f251 100644 --- a/.github/workflows/high-availability.yml +++ b/.github/workflows/high-availability.yml @@ -30,7 +30,7 @@ jobs: DAPR_VERSION: ["1.13.2"] steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml index 3aecd8b89..e8869cc5b 100644 --- a/.github/workflows/pr-to-main.yml +++ b/.github/workflows/pr-to-main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml index cfb578923..34df4e587 100644 --- a/.github/workflows/publish-charts.yml +++ b/.github/workflows/publish-charts.yml @@ -13,7 +13,7 @@ jobs: contents: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/publish-cosign-sample.yml b/.github/workflows/publish-cosign-sample.yml index 925284a8c..ee656a0f7 100644 --- a/.github/workflows/publish-cosign-sample.yml +++ b/.github/workflows/publish-cosign-sample.yml @@ -20,7 +20,7 @@ jobs: id-token: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml index c5dc1ef6a..60b0d8ce8 100644 --- a/.github/workflows/publish-dev-assets.yml +++ b/.github/workflows/publish-dev-assets.yml @@ -17,7 +17,7 @@ jobs: environment: azure-publish steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit - name: Checkout diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml index 32a893d36..17b49127c 100644 --- a/.github/workflows/publish-package.yml +++ b/.github/workflows/publish-package.yml @@ -16,7 +16,7 @@ jobs: contents: read steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit - name: Checkout diff --git a/.github/workflows/publish-sample.yml b/.github/workflows/publish-sample.yml index b21e8840e..3dfd19456 100644 --- a/.github/workflows/publish-sample.yml +++ b/.github/workflows/publish-sample.yml @@ -19,7 +19,7 @@ jobs: packages: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml index 6e26af5cb..930f9a343 100644 --- a/.github/workflows/quick-start.yml +++ b/.github/workflows/quick-start.yml @@ -30,7 +30,7 @@ jobs: KUBERNETES_VERSION: ["1.29.2"] steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 13f91853e..85e0f217d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: contents: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/run-full-validation.yml b/.github/workflows/run-full-validation.yml index 71db17f86..930400c43 100644 --- a/.github/workflows/run-full-validation.yml +++ b/.github/workflows/run-full-validation.yml @@ -58,7 +58,7 @@ jobs: environment: azure-test steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml index 7c0ff23c4..66e274256 100644 --- a/.github/workflows/scan-vulns.yaml +++ b/.github/workflows/scan-vulns.yaml @@ -23,7 +23,7 @@ jobs: timeout-minutes: 15 steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit @@ -39,7 +39,7 @@ jobs: timeout-minutes: 15 steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 970cd1182..63d2cc90c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -30,7 +30,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml index fd44a65d1..0837d5cdf 100644 --- a/.github/workflows/sync-gh-pages.yml +++ b/.github/workflows/sync-gh-pages.yml @@ -17,7 +17,7 @@ jobs: repository-projects: write steps: - name: Harden Runner - uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + uses: step-security/harden-runner@446798f8213ac2e75931c1b0769676d927801858 # v2.10.0 with: egress-policy: audit diff --git a/api/unversioned/keymanagementprovider_types.go b/api/unversioned/keymanagementprovider_types.go index 9b3a77db9..b347b9692 100644 --- a/api/unversioned/keymanagementprovider_types.go +++ b/api/unversioned/keymanagementprovider_types.go @@ -32,6 +32,10 @@ type KeyManagementProviderSpec struct { // Name of the key management provider Type string `json:"type,omitempty"` + // Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + // Parameters of the key management provider Parameters runtime.RawExtension `json:"parameters,omitempty"` } diff --git a/api/unversioned/namespacedkeymanagementprovider_types.go b/api/unversioned/namespacedkeymanagementprovider_types.go index 70dcf557c..cdccfe7f1 100644 --- a/api/unversioned/namespacedkeymanagementprovider_types.go +++ b/api/unversioned/namespacedkeymanagementprovider_types.go @@ -33,6 +33,10 @@ type NamespacedKeyManagementProviderSpec struct { // Name of the key management provider Type string `json:"type,omitempty"` + // Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + // +kubebuilder:default="" + RefreshInterval string `json:"refreshInterval,omitempty"` + // +kubebuilder:pruning:PreserveUnknownFields // Parameters of the key management provider Parameters runtime.RawExtension `json:"parameters,omitempty"` diff --git a/api/unversioned/namespacedverifier_types.go b/api/unversioned/namespacedverifier_types.go index 7e196a233..994e9d1f8 100644 --- a/api/unversioned/namespacedverifier_types.go +++ b/api/unversioned/namespacedverifier_types.go @@ -27,19 +27,22 @@ type NamespacedVerifierSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // Parameters for this verifier diff --git a/api/unversioned/verifier_types.go b/api/unversioned/verifier_types.go index 74b8bdf73..fbdd69b43 100644 --- a/api/unversioned/verifier_types.go +++ b/api/unversioned/verifier_types.go @@ -26,19 +26,22 @@ import ( type VerifierSpec struct { // Important: Run "make" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name,omitempty"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes,omitempty"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // Parameters for this verifier diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go index 467a815ca..9be9f8190 100644 --- a/api/v1alpha1/zz_generated.conversion.go +++ b/api/v1alpha1/zz_generated.conversion.go @@ -642,6 +642,7 @@ func Convert_v1alpha1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, func autoConvert_unversioned_VerifierSpec_To_v1alpha1_VerifierSpec(in *unversioned.VerifierSpec, out *VerifierSpec, s conversion.Scope) error { out.Name = in.Name + // WARNING: in.Type requires manual conversion: does not exist in peer-type // WARNING: in.Version requires manual conversion: does not exist in peer-type out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address diff --git a/api/v1beta1/namespacedverifier_types.go b/api/v1beta1/namespacedverifier_types.go index 31e1c8e9f..17809a456 100644 --- a/api/v1beta1/namespacedverifier_types.go +++ b/api/v1beta1/namespacedverifier_types.go @@ -29,19 +29,22 @@ type NamespacedVerifierSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields diff --git a/api/v1beta1/verifier_types.go b/api/v1beta1/verifier_types.go index 5d4bf0974..b273a4898 100644 --- a/api/v1beta1/verifier_types.go +++ b/api/v1beta1/verifier_types.go @@ -25,19 +25,22 @@ import ( type VerifierSpec struct { // Important: Run "make install-crds" to regenerate code after modifying this file - // Name of the verifier + // Name of the verifier. Deprecated Name string `json:"name"` + // Type of the verifier. Optional + Type string `json:"type,omitempty"` + // Version of the verifier plugin. Optional Version string `json:"version,omitempty"` // The type of artifact this verifier handles ArtifactTypes string `json:"artifactTypes"` - // # Optional. URL/file path + // URL/file path. Optional Address string `json:"address,omitempty"` - // OCI Artifact source to download the plugin from, optional + // OCI Artifact source to download the plugin from. Optional Source *PluginSource `json:"source,omitempty"` // +kubebuilder:pruning:PreserveUnknownFields diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index fafb65bab..0b69afaf9 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -569,6 +569,7 @@ func Convert_unversioned_KeyManagementProviderList_To_v1beta1_KeyManagementProvi func autoConvert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProviderSpec(in *KeyManagementProviderSpec, out *unversioned.KeyManagementProviderSpec, s conversion.Scope) error { out.Type = in.Type + out.RefreshInterval = in.RefreshInterval out.Parameters = in.Parameters return nil } @@ -580,6 +581,7 @@ func Convert_v1beta1_KeyManagementProviderSpec_To_unversioned_KeyManagementProvi func autoConvert_unversioned_KeyManagementProviderSpec_To_v1beta1_KeyManagementProviderSpec(in *unversioned.KeyManagementProviderSpec, out *KeyManagementProviderSpec, s conversion.Scope) error { out.Type = in.Type + out.RefreshInterval = in.RefreshInterval out.Parameters = in.Parameters return nil } @@ -673,6 +675,7 @@ func Convert_unversioned_NamespacedKeyManagementProviderList_To_v1beta1_Namespac func autoConvert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_NamespacedKeyManagementProviderSpec(in *NamespacedKeyManagementProviderSpec, out *unversioned.NamespacedKeyManagementProviderSpec, s conversion.Scope) error { out.Type = in.Type + out.RefreshInterval = in.RefreshInterval out.Parameters = in.Parameters return nil } @@ -684,6 +687,7 @@ func Convert_v1beta1_NamespacedKeyManagementProviderSpec_To_unversioned_Namespac func autoConvert_unversioned_NamespacedKeyManagementProviderSpec_To_v1beta1_NamespacedKeyManagementProviderSpec(in *unversioned.NamespacedKeyManagementProviderSpec, out *NamespacedKeyManagementProviderSpec, s conversion.Scope) error { out.Type = in.Type + out.RefreshInterval = in.RefreshInterval out.Parameters = in.Parameters return nil } @@ -983,6 +987,7 @@ func Convert_unversioned_NamespacedVerifierList_To_v1beta1_NamespacedVerifierLis func autoConvert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpec(in *NamespacedVerifierSpec, out *unversioned.NamespacedVerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address @@ -998,6 +1003,7 @@ func Convert_v1beta1_NamespacedVerifierSpec_To_unversioned_NamespacedVerifierSpe func autoConvert_unversioned_NamespacedVerifierSpec_To_v1beta1_NamespacedVerifierSpec(in *unversioned.NamespacedVerifierSpec, out *NamespacedVerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address @@ -1319,6 +1325,7 @@ func Convert_unversioned_VerifierList_To_v1beta1_VerifierList(in *unversioned.Ve func autoConvert_v1beta1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, out *unversioned.VerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address @@ -1334,6 +1341,7 @@ func Convert_v1beta1_VerifierSpec_To_unversioned_VerifierSpec(in *VerifierSpec, func autoConvert_unversioned_VerifierSpec_To_v1beta1_VerifierSpec(in *unversioned.VerifierSpec, out *VerifierSpec, s conversion.Scope) error { out.Name = in.Name + out.Type = in.Type out.Version = in.Version out.ArtifactTypes = in.ArtifactTypes out.Address = in.Address diff --git a/cmd/ratify/cmd/cmd_test.go b/cmd/ratify/cmd/cmd_test.go index 9016198ab..f2f1b2850 100644 --- a/cmd/ratify/cmd/cmd_test.go +++ b/cmd/ratify/cmd/cmd_test.go @@ -36,8 +36,8 @@ func TestVerify(t *testing.T) { // TODO: make ratify cli more unit testable // unit test should not have dependency for real image - if !strings.Contains(err.Error(), "plugin not found") { - t.Errorf("error expected") + if !strings.Contains(err.Error(), "PLUGIN_NOT_FOUND") { + t.Fatalf("expected containing: %s, but got: %s", "PLUGIN_NOT_FOUND", err.Error()) } } diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml index 784bcb5f5..b61b07734 100644 --- a/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_namespacedverifiers.yaml @@ -48,20 +48,20 @@ spec: description: NamespacedVerifierSpec defines the desired state of NamespacedVerifier properties: address: - description: '# Optional. URL/file path' + description: URL/file path. Optional type: string artifactTypes: description: The type of artifact this verifier handles type: string name: - description: Name of the verifier + description: Name of the verifier. Deprecated type: string parameters: description: Parameters for this verifier type: object x-kubernetes-preserve-unknown-fields: true source: - description: OCI Artifact source to download the plugin from, optional + description: OCI Artifact source to download the plugin from. Optional properties: artifact: description: OCI Artifact source to download the plugin from @@ -72,6 +72,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + type: + description: Type of the verifier. Optional + type: string version: description: Version of the verifier plugin. Optional type: string diff --git a/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml index a23d9819f..8d08f76e5 100644 --- a/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml +++ b/config/crd/bases/config.ratify.deislabs.io_verifiers.yaml @@ -107,20 +107,20 @@ spec: description: VerifierSpec defines the desired state of Verifier properties: address: - description: '# Optional. URL/file path' + description: URL/file path. Optional type: string artifactTypes: description: The type of artifact this verifier handles type: string name: - description: Name of the verifier + description: Name of the verifier. Deprecated type: string parameters: description: Parameters for this verifier type: object x-kubernetes-preserve-unknown-fields: true source: - description: OCI Artifact source to download the plugin from, optional + description: OCI Artifact source to download the plugin from. Optional properties: artifact: description: OCI Artifact source to download the plugin from @@ -131,6 +131,9 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true type: object + type: + description: Type of the verifier. Optional + type: string version: description: Version of the verifier plugin. Optional type: string diff --git a/pkg/controllers/clusterresource/verifier_controller.go b/pkg/controllers/clusterresource/verifier_controller.go index 2b7245926..f752fa154 100644 --- a/pkg/controllers/clusterresource/verifier_controller.go +++ b/pkg/controllers/clusterresource/verifier_controller.go @@ -17,6 +17,7 @@ package clusterresource import ( "context" + "fmt" configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" re "github.com/ratify-project/ratify/errors" @@ -82,12 +83,13 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{}, nil } -// creates a verifier reference from CRD spec and add store to map +// creates a verifier reference from CR spec and add verifier to map func verifierAddOrReplace(spec configv1beta1.VerifierSpec, objectName string) error { - verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, spec.Name, spec.ArtifactTypes, spec.Source) + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, cutils.GetVerifierType(spec), spec.ArtifactTypes, spec.Source) if err != nil { - logrus.Error(err) - return err + errMsg := fmt.Sprintf("Unable to apply cluster-wide resource %s of Verifier kind", objectName) + logrus.Error(err, errMsg) + return re.ErrorCodeConfigInvalid.WithDetail(errMsg).WithError(err) } return cutils.UpsertVerifier(spec.Version, spec.Address, constants.EmptyNamespace, objectName, verifierConfig) diff --git a/pkg/controllers/clusterresource/verifier_controller_test.go b/pkg/controllers/clusterresource/verifier_controller_test.go index 850a1fb69..96cfb5956 100644 --- a/pkg/controllers/clusterresource/verifier_controller_test.go +++ b/pkg/controllers/clusterresource/verifier_controller_test.go @@ -19,7 +19,6 @@ import ( "context" "errors" "os" - "strings" "testing" configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" @@ -124,12 +123,12 @@ func TestVerifierAdd_WithParameters(t *testing.T) { func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { resetVerifierMap() - var resource = "invalidplugin" - expectedMsg := "plugin not found" + resource := "invalidplugin" + expectedMsg := "PLUGIN_NOT_FOUND: Verifier plugin pluginnotfound not found: failed to find plugin \"pluginnotfound\" in paths [test/path]: Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured." var testVerifierSpec = getInvalidVerifierSpec() err := verifierAddOrReplace(testVerifierSpec, resource) - if !strings.Contains(err.Error(), expectedMsg) { + if err.Error() != expectedMsg { t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) } } diff --git a/pkg/controllers/namespaceresource/verifier_controller.go b/pkg/controllers/namespaceresource/verifier_controller.go index e78bd8d48..a2926efe9 100644 --- a/pkg/controllers/namespaceresource/verifier_controller.go +++ b/pkg/controllers/namespaceresource/verifier_controller.go @@ -17,6 +17,7 @@ package namespaceresource import ( "context" + "fmt" configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" "github.com/ratify-project/ratify/internal/constants" @@ -83,10 +84,11 @@ func (r *VerifierReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // creates a verifier reference from CRD spec and add store to map func verifierAddOrReplace(spec configv1beta1.NamespacedVerifierSpec, objectName string, namespace string) error { - verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, spec.Name, spec.ArtifactTypes, spec.Source) + verifierConfig, err := cutils.SpecToVerifierConfig(spec.Parameters.Raw, objectName, cutils.GetVerifierType(spec), spec.ArtifactTypes, spec.Source) if err != nil { - logrus.Error(err) - return err + errMsg := fmt.Sprintf("Unable to apply the resource %s of NamespacedVerifier kind in the namespace %s", objectName, namespace) + logrus.Error(err, errMsg) + return re.ErrorCodeConfigInvalid.WithDetail(errMsg).WithError(err) } return cutils.UpsertVerifier(spec.Version, spec.Address, namespace, objectName, verifierConfig) diff --git a/pkg/controllers/namespaceresource/verifier_controller_test.go b/pkg/controllers/namespaceresource/verifier_controller_test.go index ac433a53c..5c65f1645 100644 --- a/pkg/controllers/namespaceresource/verifier_controller_test.go +++ b/pkg/controllers/namespaceresource/verifier_controller_test.go @@ -19,7 +19,6 @@ import ( "context" "errors" "os" - "strings" "testing" configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" @@ -142,12 +141,12 @@ func TestVerifierAdd_WithParameters(t *testing.T) { func TestVerifierAddOrReplace_PluginNotFound(t *testing.T) { resetVerifierMap() - var resource = "invalidplugin" - expectedMsg := "plugin not found" + resource := "invalidplugin" + expectedMsg := "PLUGIN_NOT_FOUND: Verifier plugin pluginnotfound not found: failed to find plugin \"pluginnotfound\" in paths [test/path]: Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured." var testVerifierSpec = getInvalidVerifierSpec() err := verifierAddOrReplace(testVerifierSpec, resource, testNamespace) - if !strings.Contains(err.Error(), expectedMsg) { + if err.Error() != expectedMsg { t.Fatalf("TestVerifierAddOrReplace_PluginNotFound expected msg: '%v', actual %v", expectedMsg, err.Error()) } } diff --git a/pkg/controllers/utils/verifier.go b/pkg/controllers/utils/verifier.go index 0b2557996..43a9e858d 100644 --- a/pkg/controllers/utils/verifier.go +++ b/pkg/controllers/utils/verifier.go @@ -15,8 +15,10 @@ package utils import ( "encoding/json" + "fmt" configv1beta1 "github.com/ratify-project/ratify/api/v1beta1" + re "github.com/ratify-project/ratify/errors" vc "github.com/ratify-project/ratify/pkg/verifier/config" vf "github.com/ratify-project/ratify/pkg/verifier/factory" "github.com/ratify-project/ratify/pkg/verifier/types" @@ -56,8 +58,9 @@ func SpecToVerifierConfig(raw []byte, verifierName, verifierType, artifactTypes if string(raw) != "" { if err := json.Unmarshal(raw, &verifierConfig); err != nil { - logrus.Error(err, "unable to decode verifier parameters", "Parameters.Raw", raw) - return vc.VerifierConfig{}, err + errMsg := fmt.Sprintf("Unable to recognize the parameters of the Verifier resource %s", string(raw)) + logrus.Error(err, errMsg) + return vc.VerifierConfig{}, re.ErrorCodeConfigInvalid.WithError(err).WithDetail(errMsg).WithRemediation("Please update the Verifier parameters and try again. Refer to the Verifier configuration guide: https://ratify.dev/docs/reference/custom%20resources/verifiers") } } verifierConfig[types.Name] = verifierName @@ -69,3 +72,22 @@ func SpecToVerifierConfig(raw []byte, verifierName, verifierType, artifactTypes return verifierConfig, nil } + +// GetVerifierType returns verifier type and is backward compatible with the deprecated name field +func GetVerifierType(verifierSpec interface{}) string { + switch spec := verifierSpec.(type) { + case configv1beta1.VerifierSpec: + if spec.Type == "" { + return spec.Name + } + return spec.Type + case configv1beta1.NamespacedVerifierSpec: + if spec.Type == "" { + return spec.Name + } + return spec.Type + default: + logrus.Error("unable to assert verifierSpec type", spec) + } + return "" +} diff --git a/pkg/controllers/utils/verifier_test.go b/pkg/controllers/utils/verifier_test.go index 8ccf9a6d7..349653a07 100644 --- a/pkg/controllers/utils/verifier_test.go +++ b/pkg/controllers/utils/verifier_test.go @@ -119,3 +119,46 @@ func TestSpecToVerifierConfig(t *testing.T) { func resetVerifierMap() { controllers.NamespacedVerifiers = verifiers.NewActiveVerifiers() } + +func TestGetType(t *testing.T) { + tests := []struct { + name string + input interface{} + expected string + }{ + { + name: "cluster verifier spec with name", + input: configv1beta1.VerifierSpec{Name: "clusterV"}, + expected: "clusterV", + }, + { + name: "cluster verifier spec with type", + input: configv1beta1.VerifierSpec{Type: "clusterV"}, + expected: "clusterV", + }, + { + name: "namespaced verifier spec with name", + input: configv1beta1.NamespacedVerifierSpec{Name: "namespacedV"}, + expected: "namespacedV", + }, + { + name: "namespaced verifier spec with type", + input: configv1beta1.NamespacedVerifierSpec{Type: "namespacedV"}, + expected: "namespacedV", + }, + { + name: "verifier spec with no name or type", + input: "", + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + output := GetVerifierType(tt.input) + if tt.expected != output { + t.Fatalf("GetType() expected %v, actual %v", tt.expected, output) + } + }) + } +} diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go index e5d8ec299..b364bee30 100644 --- a/pkg/executor/core/executor.go +++ b/pkg/executor/core/executor.go @@ -61,7 +61,7 @@ type Executor struct { // VerifySubject verifies the subject and returns results. func (executor Executor) VerifySubject(ctx context.Context, verifyParameters e.VerifyParameters) (types.VerifyResult, error) { if executor.PolicyEnforcer == nil { - return types.VerifyResult{}, errors.ErrorCodePolicyProviderNotFound.WithComponentType(errors.Executor) + return types.VerifyResult{}, errors.ErrorCodePolicyProviderNotFound.WithDetail("Policy configuration not found") } result, err := executor.verifySubjectInternal(ctx, verifyParameters) if err != nil { @@ -83,7 +83,7 @@ func (executor Executor) verifySubjectInternal(ctx context.Context, verifyParame } if executor.PolicyEnforcer.GetPolicyType(ctx) == pt.ConfigPolicy { if len(verifierReports) == 0 { - return types.VerifyResult{}, errors.ErrorCodeNoVerifierReport.WithComponentType(errors.Executor).WithDescription() + return types.VerifyResult{}, errors.ErrorCodeNoVerifierReport.WithDetail(fmt.Sprintf("No verification results for the artifact %s. Ensure verifiers are properly configured and that artifact metadata is attached", verifyParameters.Subject)) } } // If it requires embedded Rego Policy Engine make the decision, execute @@ -176,7 +176,7 @@ func (executor Executor) verifyReferenceForJSONPolicy(ctx context.Context, subje verifierStartTime := time.Now() verifyResult, err := verifier.Verify(ctx, subjectRef, referenceDesc, referrerStore) if err != nil { - verifierErr := errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace) + verifierErr := errors.ErrorCodeVerifyReferenceFailure.WithError(err) verifyResult = vr.NewVerifierResult("", verifier.Name(), verifier.Type(), "", false, &verifierErr, nil) } @@ -224,7 +224,7 @@ func (executor Executor) verifyReferenceForRegoPolicy(ctx context.Context, subje verifierStartTime := time.Now() verifierResult, err := verifier.Verify(errCtx, subjectRef, referenceDesc, referrerStore) if err != nil { - verifierErr := errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace) + verifierErr := errors.ErrorCodeVerifyReferenceFailure.WithError(err) verifierReport = vt.CreateVerifierResult(verifier.Name(), verifier.Type(), "", false, &verifierErr) } else { verifierReport = vt.NewVerifierResult(verifierResult) @@ -288,7 +288,7 @@ func (executor Executor) addNestedReports(ctx context.Context, referenceDes ocis for _, report := range reports.VerifierReports { nestedReport, err := types.NewNestedVerifierReport(report) if err != nil { - return errors.ErrorCodeExecutorFailure.WithError(err).WithComponentType(errors.Executor) + return errors.ErrorCodeExecutorFailure.WithError(err) } nestedReports = append(nestedReports, nestedReport) } diff --git a/pkg/keymanagementprovider/keymanagementprovider.go b/pkg/keymanagementprovider/keymanagementprovider.go index bf77c2cd5..584f50828 100644 --- a/pkg/keymanagementprovider/keymanagementprovider.go +++ b/pkg/keymanagementprovider/keymanagementprovider.go @@ -136,7 +136,7 @@ func setCertificatesInMap(resource string, certs map[KMPMapKey][]*x509.Certifica // GetCertificatesFromMap gets the certificates from the map and returns an empty map of certificate arrays if not found or an error happened. func GetCertificatesFromMap(ctx context.Context, resource string) (map[KMPMapKey][]*x509.Certificate, error) { if !hasAccessToProvider(ctx, resource) { - return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("namespace: [%s] does not have access to key management provider: %s", ctxUtils.GetNamespace(ctx), resource)).WithRemediation(fmt.Sprintf("Ensure the key management provider: %s is created under namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) + return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("Resources in namespace [%s] do not have access to key management provider [%s]", ctxUtils.GetNamespace(ctx), resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) } if err, ok := certificateErrMap.Load(resource); ok && err != nil { return map[KMPMapKey][]*x509.Certificate{}, err.(error) @@ -144,7 +144,7 @@ func GetCertificatesFromMap(ctx context.Context, resource string) (map[KMPMapKey if certs, ok := certificatesMap.Load(resource); ok { return certs.(map[KMPMapKey][]*x509.Certificate), nil } - return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("failed to access non-existent key management provider: %s", resource)) + return map[KMPMapKey][]*x509.Certificate{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("The key management provider [%s] does not exist", resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) } // DeleteResourceFromMap deletes the certificates, keys and errors from the map @@ -186,7 +186,7 @@ func GetKeysFromMap(ctx context.Context, resource string) (map[KMPMapKey]PublicK // A cluster-wide operation can access cluster-wide provider // A namespaced operation can only fetch the provider in the same namespace or cluster-wide provider. if !hasAccessToProvider(ctx, resource) { - return map[KMPMapKey]PublicKey{}, fmt.Errorf("namespace: [%s] does not have access to key management provider: %s", ctxUtils.GetNamespace(ctx), resource) + return map[KMPMapKey]PublicKey{}, errors.ErrorCodeForbidden.WithDetail(fmt.Sprintf("The resources in namespace [%s] do not have access to key management provider [%s]", ctxUtils.GetNamespace(ctx), resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider [%s] is created in the namespace [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) } if err, ok := keyErrMap.Load(resource); ok && err != nil { return map[KMPMapKey]PublicKey{}, err.(error) @@ -194,7 +194,7 @@ func GetKeysFromMap(ctx context.Context, resource string) (map[KMPMapKey]PublicK if keys, ok := keyMap.Load(resource); ok { return keys.(map[KMPMapKey]PublicKey), nil } - return map[KMPMapKey]PublicKey{}, fmt.Errorf("failed to access non-existent key management provider: %s", resource) + return map[KMPMapKey]PublicKey{}, errors.ErrorCodeNotFound.WithDetail(fmt.Sprintf("The key management provider [%s] does not exist", resource)).WithRemediation(fmt.Sprintf("Make sure the key management provider: %s is created in the namespace: [%s] or as a cluster-wide resource.", resource, ctxUtils.GetNamespace(ctx))) } // SetCertificateError sets the error while fetching certificates from key management provider. diff --git a/pkg/referrerstore/oras/cosign.go b/pkg/referrerstore/oras/cosign.go index a60f458ae..4f6fc2457 100644 --- a/pkg/referrerstore/oras/cosign.go +++ b/pkg/referrerstore/oras/cosign.go @@ -46,7 +46,7 @@ func getCosignReferences(ctx context.Context, subjectReference common.Reference, return nil, nil } evictOnError(ctx, err, subjectReference.Original) - return nil, re.ErrorCodeRepositoryOperationFailure.WithError(err).WithComponentType(re.ReferrerStore) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail(fmt.Sprintf("Failed to validate existence of Cosign signature of the artifact: %+v", subjectReference)).WithError(err) } references = append(references, ocispecs.ReferenceDescriptor{ @@ -64,7 +64,7 @@ func getCosignReferences(ctx context.Context, subjectReference common.Reference, func attachedImageTag(subjectReference common.Reference, tagSuffix string) (string, error) { // sha256:d34db33f -> sha256-d34db33f.suffix if subjectReference.Digest.String() == "" { - return "", re.ErrorCodeReferenceInvalid.WithComponentType(re.ReferrerStore).WithDetail("Cosign subject digest is empty") + return "", re.ErrorCodeReferenceInvalid.WithDetail("The digest of the artifact is empty") } tagStr := strings.ReplaceAll(subjectReference.Digest.String(), ":", "-") + tagSuffix return fmt.Sprintf("%s:%s", subjectReference.Path, tagStr), nil diff --git a/pkg/referrerstore/oras/oras.go b/pkg/referrerstore/oras/oras.go index 0a8f146e5..64679dd0a 100644 --- a/pkg/referrerstore/oras/oras.go +++ b/pkg/referrerstore/oras/oras.go @@ -208,7 +208,7 @@ func (store *orasStore) GetConfig() *config.StoreConfig { func (store *orasStore) ListReferrers(ctx context.Context, subjectReference common.Reference, _ []string, _ string, subjectDesc *ocispecs.SubjectDescriptor) (referrerstore.ListReferrersResult, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return referrerstore.ListReferrersResult{}, re.ErrorCodeCreateRepositoryFailure.WithError(err).WithComponentType(re.ReferrerStore) + return referrerstore.ListReferrersResult{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } // resolve subject descriptor if not provided @@ -260,7 +260,7 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return nil, err + return nil, re.ErrorCodeGetBlobContentFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } // create a dummy Descriptor to check the local store cache @@ -292,10 +292,10 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com blobDesc, rc, err := repository.Blobs().FetchReference(ctx, ref) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return nil, err + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to fetch the artifact metadata from the registry").WithError(err) } if blobContent, err = io.ReadAll(rc); err != nil { - return nil, re.ErrorCodeGetBlobContentFailure.WithError(err) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to parse the artifact metadata").WithError(err) } // push fetched content to local ORAS cache @@ -313,7 +313,7 @@ func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference com func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReference common.Reference, referenceDesc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeCreateRepositoryFailure.NewError(re.ReferrerStore, storeName, re.EmptyLink, err, nil, re.HideStackTrace) + return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to the remote registry").WithError(err) } var manifestBytes []byte // check if manifest exists in local ORAS cache @@ -336,12 +336,12 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen manifestReader, err := repository.Fetch(ctx, referenceDesc.Descriptor) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.NewError(re.ReferrerStore, storeName, re.EmptyLink, err, nil, re.HideStackTrace) + return ocispecs.ReferenceManifest{}, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to fetch the artifact metadata from the registry").WithError(err) } manifestBytes, err = io.ReadAll(manifestReader) if err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeManifestInvalid.WithError(err).WithPluginName(storeName).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeManifestInvalid.WithDetail("Failed to parse the artifact metadata").WithError(err) } // push fetched manifest to local ORAS cache @@ -360,15 +360,15 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen if referenceDesc.Descriptor.MediaType == oci.MediaTypeImageManifest { var imageManifest oci.Manifest if err := json.Unmarshal(manifestBytes, &imageManifest); err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithError(err).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithDetail("Failed to parse artifact metadata of mediatype `application/vnd.oci.image.manifest.v1+json`").WithError(err).WithRemediation("Please check if the artifact metadata was created correctly.") } referenceManifest = commonutils.OciManifestToReferenceManifest(imageManifest) } else if referenceDesc.Descriptor.MediaType == ocispecs.MediaTypeArtifactManifest { if err := json.Unmarshal(manifestBytes, &referenceManifest); err != nil { - return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithError(err).WithComponentType(re.ReferrerStore) + return ocispecs.ReferenceManifest{}, re.ErrorCodeDataDecodingFailure.WithDetail("Failed to parse artifact metadata of mediatype `application/vnd.oci.artifact.manifest.v1+json`").WithError(err).WithRemediation("Please check if the artifact metadata was created correctly.") } } else { - return ocispecs.ReferenceManifest{}, fmt.Errorf("unsupported manifest media type: %s", referenceDesc.Descriptor.MediaType) + return ocispecs.ReferenceManifest{}, re.ErrorCodeGetReferenceManifestFailure.WithDetail(fmt.Sprintf("Unsupported artifact metadata of media type %s", referenceDesc.Descriptor.MediaType)).WithRemediation("Please check if the artifact metadata was created correctly.") } return referenceManifest, nil @@ -377,13 +377,13 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen func (store *orasStore) GetSubjectDescriptor(ctx context.Context, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) { repository, err := store.createRepository(ctx, store, subjectReference) if err != nil { - return nil, re.ErrorCodeCreateRepositoryFailure.WithError(err).WithComponentType(re.ReferrerStore).WithPluginName(storeName) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail("Failed to connect to remote registry").WithError(err) } desc, err := repository.Resolve(ctx, subjectReference.Original) if err != nil { evictOnError(ctx, err, subjectReference.Original) - return nil, re.ErrorCodeRepositoryOperationFailure.WithError(err).WithPluginName(storeName) + return nil, re.ErrorCodeRepositoryOperationFailure.WithDetail(fmt.Sprintf("Unable to resolve the reference: %s", subjectReference.Original)).WithError(err) } return &ocispecs.SubjectDescriptor{Descriptor: desc}, nil diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go index fb3efd714..86442859a 100644 --- a/pkg/verifier/cosign/cosign.go +++ b/pkg/verifier/cosign/cosign.go @@ -143,17 +143,17 @@ func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierC logger.GetLogger(context.Background(), logOpt).Debugf("creating cosign verifier with config %v, namespace '%v'", verifierConfig, namespace) verifierName, hasName := verifierConfig[types.Name].(string) if !hasName { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("missing name in verifier config") + return nil, re.ErrorCodeConfigInvalid.WithDetail("The name field is required in the Cosign Verifier configuration") } config, err := parseVerifierConfig(verifierConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName) + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to create the Cosign Verifier").WithError(err) } // if key or rekorURL is provided, trustPolicies should not be provided if (config.KeyRef != "" || config.RekorURL != "") && len(config.TrustPolicies) > 0 { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("'key' and 'rekorURL' are part of cosign legacy configuration and cannot be used with `trustPolicies`") + return nil, re.ErrorCodeConfigInvalid.WithDetail("'key' and 'rekorURL' are part of Cosign legacy configuration and cannot be used with `trustPolicies` parameter") } var trustPolicies *TrustPolicies @@ -163,7 +163,7 @@ func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierC logger.GetLogger(context.Background(), logOpt).Debugf("legacy cosign verifier configuration not found, creating trust policies") trustPolicies, err = CreateTrustPolicies(config.TrustPolicies, verifierName) if err != nil { - return nil, err + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Cosign Verifier").WithError(err) } legacy = false } @@ -224,18 +224,18 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co // get the reference manifest (cosign oci image) referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get reference manifest: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to get Cosign signature metadata for %s", referenceDescriptor.Digest)).WithError(err)), nil } // manifest must be an OCI Image if referenceManifest.MediaType != imgspec.MediaTypeImageManifest { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("reference manifest is not an image")), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("The artifact metadata is not an OCI image")), nil } // get the subject image descriptor subjectDesc, err := referrerStore.GetSubjectDescriptor(ctx, subjectReference) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to create subject hash: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Cosign signature of the artifact: %+v", subjectReference)).WithError(err)), nil } // create the hash of the subject image descriptor (used as the hashed payload) @@ -255,23 +255,23 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co // fetch the blob content of the signature from the referrer store blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get blob content: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeGetBlobContentFailure.WithDetail(fmt.Sprintf("Failed to get Cosign signature with digest %s", blob.Digest)).WithError(err)), nil } // convert the blob to a static signature staticOpts, err := staticLayerOpts(blob) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to parse static signature opts: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to parse Cosign signature with digest %s", blob.Digest)).WithError(err)), nil } sig, err := static.NewSignature(blobBytes, blob.Annotations[static.SignatureAnnotationKey], staticOpts...) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to generate static signature: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature").WithError(err)), nil } if len(keysMap) > 0 { // if keys are found, perform verification with keys var verifications []cosignExtension verifications, hasValidSignature, err = verifyWithKeys(ctx, keysMap, sig, blob.Annotations[static.SignatureAnnotationKey], blobBytes, staticOpts, &cosignOpts, subjectDescHash) if err != nil { - return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to verify with keys: %w", err)), nil + return errorToVerifyResult(v.name, v.verifierType, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature with keys").WithError(err)), nil } extensionListEntry.Verifications = append(extensionListEntry.Verifications, verifications...) } else { @@ -295,7 +295,7 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co ), nil } - errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid signatures found")) + errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid Cosign signatures found")) errorResult.Extensions = Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()} return errorResult, nil } @@ -485,7 +485,7 @@ func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) { // ErrorToVerifyResult returns a verifier result with the error message and isSuccess set to false func errorToVerifyResult(name string, verifierType string, err error) verifier.VerifierResult { - verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail("Verification failed").WithError(err) + verifierErr := re.ErrorCodeVerifyReferenceFailure.WithDetail("Failed to validate the Cosign signature").WithError(err) return verifier.NewVerifierResult( "", name, @@ -540,14 +540,14 @@ func verifyWithKeys(ctx context.Context, keysMap map[PKKey]keymanagementprovider if pubKey.ProviderType == azurekeyvault.ProviderName { hashType, sig, err = processAKVSignature(sigEncoded, sig, pubKey.Key, payload, staticOpts) if err != nil { - return verifications, false, fmt.Errorf("failed to process AKV signature: %w", err) + return verifications, false, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to validate the Cosign signature generated by Azure Key Vault").WithError(err) } } // return the correct verifier based on public key type and bytes verifier, err := signature.LoadVerifier(pubKey.Key, hashType) if err != nil { - return verifications, false, fmt.Errorf("failed to load public key from provider [%s] name [%s] version [%s]: %w", mapKey.Provider, mapKey.Name, mapKey.Version, err) + return verifications, false, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Failed to load public key from provider [%s] name [%s] version [%s]", mapKey.Provider, mapKey.Name, mapKey.Version)).WithError(err) } cosignOpts.SigVerifier = verifier // verify signature with cosign options + perform bundle verification @@ -627,17 +627,17 @@ func processAKVSignature(sigEncoded string, staticSig oci.Signature, publicKey c // EC verifiers in cosign have built in ASN.1 decoding, but RSA verifiers do not base64DecodedBytes, err := base64.StdEncoding.DecodeString(sigEncoded) if err != nil { - return crypto.SHA256, nil, fmt.Errorf("RSA key check: failed to decode base64 signature: %w", err) + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to decode base64 signature").WithError(err) } // decode ASN.1 signature to raw signature if it is ASN.1 encoded decodedSigBytes, err := decodeASN1Signature(base64DecodedBytes) if err != nil { - return crypto.SHA256, nil, fmt.Errorf("RSA key check: failed to decode ASN.1 signature: %w", err) + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to decode ASN.1 signature").WithError(err) } encodedBase64SigBytes := base64.StdEncoding.EncodeToString(decodedSigBytes) staticSig, err = static.NewSignature(payloadBytes, encodedBase64SigBytes, staticOpts...) if err != nil { - return crypto.SHA256, nil, fmt.Errorf("RSA key check: failed to generate static signature: %w", err) + return crypto.SHA256, nil, re.ErrorCodeVerifyPluginFailure.WithDetail("RSA key check: failed to generate static signature").WithError(err) } case *ecdsa.PublicKey: switch keyType.Curve { @@ -648,10 +648,10 @@ func processAKVSignature(sigEncoded string, staticSig oci.Signature, publicKey c case elliptic.P521(): hashType = crypto.SHA512 default: - return crypto.SHA256, nil, fmt.Errorf("ECDSA key check: unsupported key curve: %s", keyType.Params().Name) + return crypto.SHA256, nil, fmt.Errorf("ECDSA key check: unsupported key curve [%s]", keyType.Params().Name) } default: - return crypto.SHA256, nil, fmt.Errorf("unsupported public key type: %T", publicKey) + return crypto.SHA256, nil, fmt.Errorf("unsupported public key type [%T]", publicKey) } return hashType, staticSig, nil } diff --git a/pkg/verifier/cosign/cosign_test.go b/pkg/verifier/cosign/cosign_test.go index b4e368617..aea1cf5a7 100644 --- a/pkg/verifier/cosign/cosign_test.go +++ b/pkg/verifier/cosign/cosign_test.go @@ -23,6 +23,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "encoding/base64" "fmt" "io" "slices" @@ -114,6 +115,26 @@ func TestCreate(t *testing.T) { }, wantErr: false, }, + { + name: "duplicate trust policies in config", + config: config.VerifierConfig{ + "name": "test", + "artifactTypes": "testtype", + "trustPolicies": []TrustPolicyConfig{ + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + { + Name: "test", + Keyless: KeylessConfig{CertificateIdentity: testIdentity, CertificateOIDCIssuer: testIssuer}, + Scopes: []string{"*"}, + }, + }, + }, + wantErr: true, + }, { name: "invalid config with legacy and trust policies", config: config.VerifierConfig{ @@ -407,8 +428,8 @@ func TestErrorToVerifyResult(t *testing.T) { if verifierResult.Type != "cosign" { t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Type, "cosign") } - if verifierResult.Message != "Verification failed" { - t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "Verification failed") + if verifierResult.Message != "Failed to validate the Cosign signature" { + t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "Failed to validate the Cosign signature") } if verifierResult.ErrorReason != "test error" { t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.ErrorReason, "test error") @@ -573,7 +594,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry keys: map[PKKey]keymanagementprovider.PublicKey{}, getKeysError: true, store: &mocks.MemoryTestStore{}, - expectedResultMessagePrefix: "Verification failed", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", expectedErrorReason: "error", }, { @@ -581,8 +602,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry keys: map[PKKey]keymanagementprovider.PublicKey{}, getKeysError: false, store: &mocks.MemoryTestStore{}, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to get reference manifest: manifest not found", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "manifest not found", }, { name: "incorrect reference manifest media type error", @@ -595,8 +616,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "reference manifest is not an image", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "The artifact metadata is not an OCI image", }, { name: "failed subject descriptor fetch", @@ -609,8 +630,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to create subject hash: subject not found for sha256:5678", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "subject not found for sha256:5678", }, { name: "failed to fetch blob", @@ -636,8 +657,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry }, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to get blob content: blob not found", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "blob not found", }, { name: "invalid key type for AKV", @@ -668,8 +689,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: unsupported public key type: *ecdh.PublicKey", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "unsupported public key type [*ecdh.PublicKey]", }, { name: "invalid RSA key size for AKV", @@ -700,8 +721,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: RSA key check: unsupported key size: 128", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "RSA key check: unsupported key size: 128", }, { name: "invalid ECDSA curve type for AKV", @@ -732,8 +753,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry blobDigest: validSignatureBlob, }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to verify with keys: failed to process AKV signature: ECDSA key check: unsupported key curve: P-224", + expectedResultMessagePrefix: "Failed to validate the Cosign signature", + expectedErrorReason: "ECDSA key check: unsupported key curve [P-224]", }, { name: "valid hash: 256 keysize: 2048 RSA key AKV", @@ -965,8 +986,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry "sha256:d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6": []byte(`{"critical":{"identity":{"docker-reference":"wabbitnetworks.azurecr.io/test/cosign-image"},"image":{"docker-manifest-digest":"sha256:623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"},"type":"cosign container image signature"},"optional":null}`), }, }, - expectedResultMessagePrefix: "Verification failed", - expectedErrorReason: "failed to parse static signature opts: failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91", + expectedResultMessagePrefix: "Failed to validate the Cosign signature:", + expectedErrorReason: "failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91", }, } @@ -1051,3 +1072,81 @@ func TestVerificationMessage(t *testing.T) { }) } } + +func TestProcessAKVSignature_RSAKey(t *testing.T) { + tests := []struct { + name string + keySize int + encodedSig string + expectedErr bool + expectedHashType crypto.Hash + expectedSigOut bool + }{ + { + name: "RSA 2048 bits", + keySize: 256, + expectedErr: false, + expectedHashType: crypto.SHA256, + expectedSigOut: true, + }, + { + name: "RSA 3072 bits", + keySize: 384, + expectedErr: false, + expectedHashType: crypto.SHA384, + expectedSigOut: true, + }, + { + name: "RSA 4096 bits", + keySize: 512, + expectedErr: false, + expectedHashType: crypto.SHA512, + expectedSigOut: true, + }, + { + name: "Unsupported key size", + keySize: 128, + expectedErr: true, + }, + { + name: "Invalid base64 encoded signature", + keySize: 256, + encodedSig: "ThisIsNot@ValidBase64%String!", + expectedErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a mock RSA public key + privateKey, err := rsa.GenerateKey(rand.Reader, tt.keySize*8) + if err != nil { + t.Fatalf("Failed to generate RSA key: %v", err) + } + rsaPublicKey := &privateKey.PublicKey + + // Mock the signature as base64 encoded string + sig := "dummy_signature" + encodedSig := base64.StdEncoding.EncodeToString([]byte(sig)) + if tt.encodedSig != "" { + encodedSig = tt.encodedSig + } + + // Process the signature + hashType, sigOut, err := processAKVSignature(encodedSig, nil, rsaPublicKey, []byte("test payload"), []static.Option{}) + + if tt.expectedErr { + if err == nil { + t.Fatalf("Expected error but got nil") + } + } else { + if hashType != tt.expectedHashType { + t.Fatalf("Expected hash type %v but got %v", tt.expectedHashType, hashType) + } + if tt.expectedSigOut != (sigOut != nil) { + t.Fatalf("Expected signature output to be %v but got %v", tt.expectedSigOut, sigOut) + } + } + }) + } +} diff --git a/pkg/verifier/cosign/trustpolicies.go b/pkg/verifier/cosign/trustpolicies.go index 4858cfd47..6ba5521ed 100644 --- a/pkg/verifier/cosign/trustpolicies.go +++ b/pkg/verifier/cosign/trustpolicies.go @@ -35,14 +35,14 @@ var validScopeRegex = regexp.MustCompile(`^[a-z0-9\.\-:@\/]*\*?$`) // CreateTrustPolicies creates a set of trust policies from the given configuration func CreateTrustPolicies(configs []TrustPolicyConfig, verifierName string) (*TrustPolicies, error) { if len(configs) == 0 { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("failed to create trust policies: no policies found") + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to create trust policies: policy configuration not found").WithRemediation("Ensure that the trust policy configuration is correct.") } policies := make([]TrustPolicy, 0, len(configs)) names := make(map[string]struct{}) for _, policyConfig := range configs { if _, ok := names[policyConfig.Name]; ok { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("failed to create trust policies: duplicate policy name %s", policyConfig.Name)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: duplicate policy name %s", policyConfig.Name)).WithRemediation("Ensure that trust policy names are unique.") } names[policyConfig.Name] = struct{}{} policy, err := CreateTrustPolicy(policyConfig, verifierName) @@ -86,7 +86,7 @@ func (tps *TrustPolicies) GetScopedPolicy(reference string) (TrustPolicy, error) if globalPolicy != nil { return globalPolicy, nil } - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to get trust policy: no policy found for reference %s", reference)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("No policy found for the artifact %s", reference)) } // validateScopes validates the scopes in the trust policies @@ -97,16 +97,16 @@ func validateScopes(policies []TrustPolicy) error { policyName := policy.GetName() scopes := policy.GetScopes() if len(scopes) == 0 { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: no scopes defined for trust policy %s", policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: scope parameter is required for trust policy %s", policyName)) } // check for global wildcard character along with other scopes in the same policy if len(scopes) > 1 && slices.Contains(scopes, string(GlobalWildcardCharacter)) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: global wildcard character %c cannot be used with other scopes within the same trust policy %s", GlobalWildcardCharacter, policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: global wildcard character %c cannot be used with other scopes within the same trust policy %s", GlobalWildcardCharacter, policyName)) } // check for duplicate global wildcard characters across policies if slices.Contains(scopes, string(GlobalWildcardCharacter)) { if hasGlobalWildcard { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: global wildcard character %c can only be used once", GlobalWildcardCharacter)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: global wildcard character %c can only be used once", GlobalWildcardCharacter)) } hasGlobalWildcard = true continue @@ -114,15 +114,15 @@ func validateScopes(policies []TrustPolicy) error { for _, scope := range scopes { // check for empty scope if scope == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: scope defined is empty for trust policy %s", policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: scope value cannot be empty in trust policy %s", policyName)) } // check scope is formatted correctly if !validScopeRegex.MatchString(scope) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: invalid scope %s for trust policy %s", scope, policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: invalid scope %s for trust policy %s", scope, policyName)) } // check for duplicate scopes if _, ok := scopesMap[scope]; ok { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: duplicate scope %s for trust policy %s", scope, policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: duplicate scope %s for trust policy %s", scope, policyName)) } // check wildcard overlaps for existingScope := range scopesMap { @@ -144,7 +144,7 @@ func validateScopes(policies []TrustPolicy) error { isConflict = strings.HasPrefix(existingScope, trimmedScope) } if isConflict { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to create trust policies: overlapping scopes %s and %s for trust policy %s", scope, existingScope, policyName)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create trust policies: overlapping scopes %s and %s for trust policy %s", scope, existingScope, policyName)) } } scopesMap[scope] = struct{}{} diff --git a/pkg/verifier/cosign/trustpolicy.go b/pkg/verifier/cosign/trustpolicy.go index c49b86aa8..8d20a7320 100644 --- a/pkg/verifier/cosign/trustpolicy.go +++ b/pkg/verifier/cosign/trustpolicy.go @@ -97,7 +97,7 @@ func CreateTrustPolicy(config TrustPolicyConfig, verifierName string) (TrustPoli config.Version = DefaultTrustPolicyConfigVersion } - if err := validate(config, verifierName); err != nil { + if err := validate(config); err != nil { return nil, err } @@ -107,7 +107,7 @@ func CreateTrustPolicy(config TrustPolicyConfig, verifierName string) (TrustPoli if keyConfig.File != "" { pubKey, err := loadKeyFromPath(keyConfig.File) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: failed to load key from file %s", config.Name, keyConfig.File)).WithError(err) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: failed to load the key from file %s", config.Name, keyConfig.File)).WithError(err).WithRemediation("Ensure that the key file path is correct and public key is correctly saved.") } keyMap[PKKey{Provider: fileProviderName, Name: keyConfig.File}] = keymanagementprovider.PublicKey{Key: pubKey, ProviderType: fileProviderName} } @@ -155,13 +155,13 @@ func (tp *trustPolicy) GetKeys(ctx context.Context, _ string) (map[PKKey]keymana // get the key management provider resource which contains a map of keys kmpResource, kmpErr := keymanagementprovider.GetKeysFromMap(ctx, keyConfig.Provider) if kmpErr != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(tp.verifierName).WithDetail(fmt.Sprintf("trust policy [%s] failed to access key management provider %s, err: %s", tp.config.Name, keyConfig.Provider, kmpErr.Error())) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: failed to access key management provider %s", tp.config.Name, keyConfig.Provider)).WithError(kmpErr) } // get a specific key from the key management provider resource if keyConfig.Name != "" { pubKey, exists := kmpResource[keymanagementprovider.KMPMapKey{Name: keyConfig.Name, Version: keyConfig.Version}] if !exists { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(tp.verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key %s with version %s not found in key management provider %s", tp.config.Name, keyConfig.Name, keyConfig.Version, keyConfig.Provider)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy [%s]: key %s with version %s not found in key management provider %s", tp.config.Name, keyConfig.Name, keyConfig.Version, keyConfig.Provider)) } keyMap[PKKey{Provider: keyConfig.Provider, Name: keyConfig.Name, Version: keyConfig.Version}] = pubKey } else { @@ -188,12 +188,12 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err // create the rekor client cosignOpts.RekorClient, err = rekor.NewClient(tp.config.RekorURL) if err != nil { - return cosignOpts, fmt.Errorf("failed to create Rekor client from URL %s: %w", tp.config.RekorURL, err) + return cosignOpts, re.ErrorCodeConfigInvalid.WithDetail(fmt.Errorf("Failed to create Rekor client from URL %s", tp.config.RekorURL)).WithRemediation("Ensure that the Rekor URL is valid.").WithError(err) } // Fetches the Rekor public keys from the Rekor server cosignOpts.RekorPubKeys, err = cosign.GetRekorPubs(ctx) if err != nil { - return cosignOpts, fmt.Errorf("failed to fetch Rekor public keys: %w", err) + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to fetch Rekor public keys").WithRemediation(fmt.Sprintf("Please check if the Rekor server %s is available", tp.config.RekorURL)).WithError(err) } } else { cosignOpts.IgnoreTlog = true @@ -203,20 +203,20 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err if tp.isKeyless { roots, err := fulcio.GetRoots() if err != nil || roots == nil { - return cosignOpts, fmt.Errorf("failed to get fulcio roots: %w", err) + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to get fulcio root").WithError(err).WithRemediation("Please check if Fulcio is available") } cosignOpts.RootCerts = roots if tp.config.Keyless.CTLogVerify != nil && *tp.config.Keyless.CTLogVerify { cosignOpts.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx) if err != nil { - return cosignOpts, fmt.Errorf("failed to fetch certificate transparency log public keys: %w", err) + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to fetch certificate transparency log public keys").WithError(err).WithRemediation("Please check if TUF root is available") } } else { cosignOpts.IgnoreSCT = true } cosignOpts.IntermediateCerts, err = fulcio.GetIntermediates() if err != nil { - return cosignOpts, fmt.Errorf("failed to get fulcio intermediate certificates: %w", err) + return cosignOpts, re.ErrorCodeVerifyPluginFailure.WithDetail("Failed to get fulcio intermediate certificates").WithError(err).WithRemediation("Please check if Fulcio is available") } // Set the certificate identity and issuer for keyless verification cosignOpts.Identities = []cosign.Identity{ @@ -234,42 +234,42 @@ func (tp *trustPolicy) GetCosignOpts(ctx context.Context) (cosign.CheckOpts, err // validate checks if the trust policy configuration is valid // returns an error if the configuration is invalid -func validate(config TrustPolicyConfig, verifierName string) error { +func validate(config TrustPolicyConfig) error { // check if the trust policy version is supported if !slices.Contains(SupportedTrustPolicyConfigVersions, config.Version) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: unsupported version %s", config.Name, config.Version)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: unsupported version %s", config.Name, config.Version)).WithRemediation(fmt.Sprintf("Supported versions are: %v", SupportedTrustPolicyConfigVersions)) } if config.Name == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("missing trust policy name") + return re.ErrorCodeConfigInvalid.WithDetail("name parameter is required in trust policy configuration").WithRemediation("Please provide a name for the trust policy.") } if len(config.Scopes) == 0 { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: no scopes defined", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("scopes parameter is required in trust policy configuration %s", config.Name)).WithRemediation("Please provide at least one scope for the trust policy.") } // keys or keyless must be defined if len(config.Keys) == 0 && config.Keyless == (KeylessConfig{}) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: no keys defined and keyless section not configured", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("keys or keyless parameter is required in trust policy configuration %s", config.Name)) } // only one of keys or keyless can be defined if len(config.Keys) > 0 && config.Keyless != (KeylessConfig{}) { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: both keys and keyless sections are defined", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Only one of keys or keyless parameter is required in trust policy configuration %s", config.Name)) } for _, keyConfig := range config.Keys { // check if the key is defined by file path or by key management provider if keyConfig.File == "" && keyConfig.Provider == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key management provider name is required when not using file path", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: key management provider name is required when not using file path", config.Name)) } // both file path and key management provider cannot be defined together if keyConfig.File != "" && keyConfig.Provider != "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: 'name' and 'file' cannot be configured together", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: 'name' and 'file' cannot be configured together", config.Name)) } // key name is required when key version is defined if keyConfig.Version != "" && keyConfig.Name == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: key name is required when key version is defined", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: key name is required when key version is defined", config.Name)) } } @@ -277,19 +277,19 @@ func validate(config TrustPolicyConfig, verifierName string) error { if config.Keyless != (KeylessConfig{}) { // validate certificate identity specified if config.Keyless.CertificateIdentity == "" && config.Keyless.CertificateIdentityRegExp == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: certificate identity or identity regex pattern is required", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: certificate identity or identity regex pattern is required", config.Name)) } // validate certificate OIDC issuer specified if config.Keyless.CertificateOIDCIssuer == "" && config.Keyless.CertificateOIDCIssuerRegExp == "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: certificate OIDC issuer or issuer regex pattern is required", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: certificate OIDC issuer or issuer regex pattern is required", config.Name)) } // validate only expression or value is specified for certificate identity if config.Keyless.CertificateIdentity != "" && config.Keyless.CertificateIdentityRegExp != "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: only one of certificate identity or identity regex pattern should be specified", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: only one of certificate identity or identity regex pattern should be specified", config.Name)) } // validate only expression or value is specified for certificate OIDC issuer if config.Keyless.CertificateOIDCIssuer != "" && config.Keyless.CertificateOIDCIssuerRegExp != "" { - return re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail(fmt.Sprintf("trust policy %s failed: only one of certificate OIDC issuer or issuer regex pattern should be specified", config.Name)) + return re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid trust policy %s: only one of certificate OIDC issuer or issuer regex pattern should be specified", config.Name)) } } diff --git a/pkg/verifier/cosign/trustpolicy_test.go b/pkg/verifier/cosign/trustpolicy_test.go index ea720c50e..65dcb84da 100644 --- a/pkg/verifier/cosign/trustpolicy_test.go +++ b/pkg/verifier/cosign/trustpolicy_test.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "crypto/x509" "fmt" + "os" "testing" ctxUtils "github.com/ratify-project/ratify/internal/context" @@ -204,6 +205,20 @@ func TestGetKeys(t *testing.T) { }, wantErr: true, }, + { + name: "access nonexistent key from KMP", + cfg: TrustPolicyConfig{ + Name: "test", + Scopes: []string{"*"}, + Keys: []KeyConfig{ + { + Provider: "ns/kmp", + Name: "nonexistent", + }, + }, + }, + wantErr: true, + }, { name: "valid KMP", cfg: TrustPolicyConfig{ @@ -423,7 +438,7 @@ func TestValidate(t *testing.T) { for _, tt := range tc { t.Run(tt.name, func(t *testing.T) { - actual := validate(tt.policyConfig, "test-verifier") + actual := validate(tt.policyConfig) if (actual != nil) != tt.wantErr { t.Fatalf("expected %v, got %v", tt.wantErr, actual) } @@ -447,3 +462,76 @@ func TestLoadKeyFromPath(t *testing.T) { t.Fatalf("expected ecdsa.PublicKey, got %v", keyType) } } + +func TestGetCosignOpts(t *testing.T) { + testCases := []struct { + name string + tlogVerify bool + rekorURL string + rekorPubKeyEnv string + isKeyless bool + CTLogVerify bool + CTLogPubKeyEnv string + expectedErr bool + }{ + { + name: "invalid rekor url", + tlogVerify: true, + rekorURL: string([]byte{0x7f}), + expectedErr: true, + }, + { + name: "failed to get rekor public key", + tlogVerify: true, + rekorURL: "https://rekor.sigstore.dev", + rekorPubKeyEnv: "invalid", + expectedErr: true, + }, + { + name: "failed to get CT log public key", + tlogVerify: false, + isKeyless: true, + CTLogVerify: true, + CTLogPubKeyEnv: "invalid", + expectedErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + if tc.rekorPubKeyEnv != "" { + val := os.Getenv("SIGSTORE_REKOR_PUBLIC_KEY") + os.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", tc.rekorPubKeyEnv) + t.Cleanup(func() { + os.Setenv("SIGSTORE_REKOR_PUBLIC_KEY", val) + }) + } + + if tc.CTLogPubKeyEnv != "" { + val := os.Getenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE") + os.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", tc.CTLogPubKeyEnv) + t.Cleanup(func() { + os.Setenv("SIGSTORE_CT_LOG_PUBLIC_KEY_FILE", val) + }) + } + + tp := trustPolicy{ + config: TrustPolicyConfig{ + TLogVerify: &tc.tlogVerify, + RekorURL: tc.rekorURL, + Keyless: KeylessConfig{ + CTLogVerify: &tc.CTLogVerify, + }, + }, + isKeyless: tc.isKeyless, + } + _, err := tp.GetCosignOpts(context.Background()) + if tc.expectedErr { + if err == nil { + t.Fatalf("expected error, got nil") + } + } + }) + } +} diff --git a/pkg/verifier/factory/factory.go b/pkg/verifier/factory/factory.go index a23bdcfde..fdc6e57a8 100644 --- a/pkg/verifier/factory/factory.go +++ b/pkg/verifier/factory/factory.go @@ -53,12 +53,12 @@ func Register(name string, factory VerifierFactory) { // namespace is only applicable in K8s environment, namespace is appended to the certstore of the truststore so it is uniquely identifiable in a cluster env // the first element of pluginBinDir will be used as the plugin directory func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersion string, pluginBinDir []string, namespace string) (verifier.ReferenceVerifier, error) { - // in cli mode both `type` and `name`` are read from config, if `type` is not specified, `name` is used as `type` + // in cli mode both `type` and `name` are read from config, if `type` is not specified, `name` is used as `type` var verifierTypeStr string if value, ok := verifierConfig[types.Name]; ok { verifierTypeStr = value.(string) } else { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("failed to find verifier name in the verifier config with key %s", "name")) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("The name field is required in the Verifier configuration: %+v", verifierConfig)) } if value, ok := verifierConfig[types.Type]; ok { @@ -66,7 +66,7 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio } if strings.ContainsRune(verifierTypeStr, os.PathSeparator) { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail(fmt.Sprintf("invalid plugin name for a verifier: %s", verifierTypeStr)) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Invalid name [%s] in the Verifier configuration, [%v] is disallowed", verifierTypeStr, os.PathSeparator)) } // if source is specified, download the plugin @@ -74,13 +74,13 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio if featureflag.DynamicPlugins.Enabled { source, err := pluginCommon.ParsePluginSource(source) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, "", re.EmptyLink, err, "failed to parse plugin source", re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail("Failed to parse the plugin source").WithError(err) } targetPath := path.Join(pluginBinDir[0], verifierTypeStr) err = pluginCommon.DownloadPlugin(source, targetPath) if err != nil { - return nil, re.ErrorCodeDownloadPluginFailure.NewError(re.Verifier, "", re.EmptyLink, err, "failed to download plugin", re.HideStackTrace) + return nil, re.ErrorCodeDownloadPluginFailure.WithDetail("Failed to download the plugin from the source").WithError(err) } logrus.Infof("downloaded verifier plugin %s from %s to %s", verifierTypeStr, source.Artifact, targetPath) } else { @@ -94,7 +94,7 @@ func CreateVerifierFromConfig(verifierConfig config.VerifierConfig, configVersio } if _, err := pluginCommon.FindInPaths(verifierTypeStr, pluginBinDir); err != nil { - return nil, re.ErrorCodePluginNotFound.NewError(re.Verifier, "", re.EmptyLink, err, "plugin not found", re.HideStackTrace) + return nil, re.ErrorCodePluginNotFound.WithDetail(fmt.Sprintf("Verifier plugin %s not found", verifierTypeStr)).WithError(err).WithRemediation("Please ensure that the correct type is specified for the built-in Verifier configuration or the custom Verifier plugin is configured.") } pluginVersion := configVersion @@ -113,11 +113,11 @@ func CreateVerifiersFromConfig(verifiersConfig config.VerifiersConfig, defaultPl err := validateVerifiersConfig(&verifiersConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithError(err) + return nil, re.ErrorCodeConfigInvalid.WithError(err) } if len(verifiersConfig.Verifiers) == 0 { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithDetail("verifiers config should have at least one verifier") + return nil, re.ErrorCodeConfigInvalid.WithDetail("The configuration for verifier.plugins must include at least one plugin") } verifiers := make([]verifier.ReferenceVerifier, 0) @@ -131,7 +131,7 @@ func CreateVerifiersFromConfig(verifiersConfig config.VerifiersConfig, defaultPl for _, verifierConfig := range verifiersConfig.Verifiers { verifier, err := CreateVerifierFromConfig(verifierConfig, verifiersConfig.Version, verifiersConfig.PluginBinDirs, namespace) if err != nil { - return nil, re.ErrorCodePluginInitFailure.WithComponentType(re.Verifier).WithError(err) + return nil, re.ErrorCodePluginInitFailure.WithError(err) } verifiers = append(verifiers, verifier) } diff --git a/pkg/verifier/factory/factory_test.go b/pkg/verifier/factory/factory_test.go index 273e76b00..7b839e0e5 100644 --- a/pkg/verifier/factory/factory_test.go +++ b/pkg/verifier/factory/factory_test.go @@ -22,6 +22,7 @@ import ( "github.com/ratify-project/ratify/internal/constants" "github.com/ratify-project/ratify/pkg/common" + "github.com/ratify-project/ratify/pkg/featureflag" "github.com/ratify-project/ratify/pkg/ocispecs" "github.com/ratify-project/ratify/pkg/referrerstore" @@ -103,6 +104,21 @@ func TestCreateVerifiersFromConfig_BuiltInVerifiers_ReturnsExpected(t *testing.T } } +func TestCreateVerifiersFromConfig_InvalidConfig_ReturnsErr(t *testing.T) { + verifierConfig := map[string]interface{}{ + "name": "test-verifier-0", + } + verifiersConfig := config.VerifiersConfig{ + Verifiers: []config.VerifierConfig{verifierConfig}, + } + + _, err := CreateVerifiersFromConfig(verifiersConfig, "test/dir", constants.EmptyNamespace) + + if err == nil { + t.Fatalf("expected to have an error") + } +} + func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) { dirPath, err := utils.CreatePlugin("sample") if err != nil { @@ -137,3 +153,83 @@ func TestCreateVerifiersFromConfig_PluginVerifiers_ReturnsExpected(t *testing.T) t.Fatalf("type assertion failed expected a plugin in verifier") } } + +func TestCreateVerifiersFromConfig_EmptyVerifiers_ReturnsErr(t *testing.T) { + verifiersConfig := config.VerifiersConfig{} + + _, err := CreateVerifiersFromConfig(verifiersConfig, "test/dir", "") + + if err == nil { + t.Fatalf("expected to have an error") + } +} + +func TestCreateVerifierFromConfig(t *testing.T) { + tests := []struct { + name string + verifierConfig config.VerifierConfig + configVersion string + pluginBinDir []string + namespace string + dynamicPluginEnabled bool + expectedErr bool + }{ + { + name: "missing name", + verifierConfig: config.VerifierConfig{}, + expectedErr: true, + }, + { + name: "verifier type contains path separator", + verifierConfig: config.VerifierConfig{ + "name": "test/verifier", + }, + expectedErr: true, + }, + { + name: "external verifier plugin not found", + verifierConfig: config.VerifierConfig{ + "name": "not-found", + }, + pluginBinDir: []string{"test/path"}, + expectedErr: true, + }, + { + name: "parse plugin source failed", + verifierConfig: config.VerifierConfig{ + "name": "test-verifier", + "source": "invalid", + }, + dynamicPluginEnabled: true, + expectedErr: true, + }, + { + name: "download plugin failed", + verifierConfig: config.VerifierConfig{ + "name": "test-verifier", + "source": map[string]interface{}{ + "artifact": "invalid", + }, + }, + pluginBinDir: []string{"test/path"}, + dynamicPluginEnabled: true, + expectedErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if tt.dynamicPluginEnabled { + dynamicVal := featureflag.DynamicPlugins.Enabled + t.Cleanup(func() { featureflag.DynamicPlugins.Enabled = dynamicVal }) + featureflag.DynamicPlugins.Enabled = true + } + + _, err := CreateVerifierFromConfig(tt.verifierConfig, tt.configVersion, tt.pluginBinDir, tt.namespace) + if tt.expectedErr != (err != nil) { + t.Fatalf("expected error %v, actual %v", tt.expectedErr, err) + } + }) + } +} diff --git a/pkg/verifier/notation/certstoresbytype.go b/pkg/verifier/notation/certstoresbytype.go index a2ab8454e..a5a9228bf 100644 --- a/pkg/verifier/notation/certstoresbytype.go +++ b/pkg/verifier/notation/certstoresbytype.go @@ -73,18 +73,18 @@ func newCertStoreByType(confInNewFormat verificationCertStores) (certStores, err s[certStoreType(certstoretype)] = make(map[string][]string) parsedStoreData, ok := storeData.(map[string]interface{}) if !ok { - return nil, fmt.Errorf("certStores: %s assertion to type verificationCertStores failed", storeData) + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) } for storeName, certProviderList := range parsedStoreData { var certProviderNames []string parsedCertProviders, ok := certProviderList.([]interface{}) if !ok { - return nil, fmt.Errorf("certProviderList: %s assertion to type []interface{} failed", certProviderList) + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) } for _, certProvider := range parsedCertProviders { certProviderName, ok := certProvider.(string) if !ok { - return nil, fmt.Errorf("certProvider: %s assertion to type string failed", certProvider) + return nil, fmt.Errorf("the verificationCertStores configuration is invalid: %+v", confInNewFormat) } certProviderNames = append(certProviderNames, certProviderName) } diff --git a/pkg/verifier/notation/notation.go b/pkg/verifier/notation/notation.go index 624273db1..e825db11d 100644 --- a/pkg/verifier/notation/notation.go +++ b/pkg/verifier/notation/notation.go @@ -93,7 +93,7 @@ func init() { } func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.VerifierConfig, pluginDirectory string, namespace string) (verifier.ReferenceVerifier, error) { - logger.GetLogger(context.Background(), logOpt).Debugf("creating notation with config %v, namespace '%v'", verifierConfig, namespace) + logger.GetLogger(context.Background(), logOpt).Debugf("creating Notation verifier with config %v, namespace '%v'", verifierConfig, namespace) verifierName := fmt.Sprintf("%s", verifierConfig[types.Name]) verifierTypeStr := "" if _, ok := verifierConfig[types.Type]; ok { @@ -101,12 +101,12 @@ func (f *notationPluginVerifierFactory) Create(_ string, verifierConfig config.V } conf, err := parseVerifierConfig(verifierConfig, namespace) if err != nil { - return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName) + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } verifyService, err := getVerifierService(conf, pluginDirectory) if err != nil { - return nil, re.ErrorCodePluginInitFailure.WithComponentType(re.Verifier).WithPluginName(verifierName).WithError(err) + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) } artifactTypes := strings.Split(conf.ArtifactTypes, ",") @@ -143,22 +143,21 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, subjectDesc, err := store.GetSubjectDescriptor(ctx, subjectReference) if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetSubjectDescriptorFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to resolve subject: %+v", subjectReference), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature of the artifact: %+v", subjectReference)).WithError(err) } referenceManifest, err := store.GetReferenceManifest(ctx, subjectReference, referenceDescriptor) if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetReferenceManifestFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to resolve reference manifest: %+v", referenceDescriptor), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature: %+v", referenceDescriptor)).WithError(err) } if len(referenceManifest.Blobs) != 1 { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyPluginFailure.WithDetail(fmt.Sprintf("Notation signature manifest requires exactly one signature envelope blob, got %d", len(referenceManifest.Blobs))).WithRemediation(fmt.Sprintf("Please inspect the artifact [%s@%s] is correctly signed by Notation signer", subjectReference.Path, referenceDescriptor.Digest.String())) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Notation signature manifest requires exactly one signature envelope blob, got %d", len(referenceManifest.Blobs))).WithRemediation(fmt.Sprintf("Please inspect the artifact [%s@%s] is correctly signed by Notation signer", subjectReference.Path, referenceDescriptor.Digest.String())) } - blobDesc := referenceManifest.Blobs[0] refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest) if err != nil { - return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeGetBlobContentFailure.NewError(re.ReferrerStore, store.Name(), re.EmptyLink, err, fmt.Sprintf("failed to get blob content of digest: %s", blobDesc.Digest), re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature of the artifact: %+v", subjectReference)).WithError(err) } // TODO: notation verify API only accepts digested reference now. @@ -166,7 +165,7 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, subjectRef := fmt.Sprintf("%s@%s", subjectReference.Path, subjectReference.Digest.String()) outcome, err := v.verifySignature(ctx, subjectRef, blobDesc.MediaType, subjectDesc.Descriptor, refBlob) if err != nil { - return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyPluginFailure.NewError(re.Verifier, v.name, re.NotationTsgLink, err, "failed to verify signature of digest", re.HideStackTrace) + return verifier.VerifierResult{IsSuccess: false, Extensions: extensions}, re.ErrorCodeVerifyReferenceFailure.WithDetail(fmt.Sprintf("Failed to validate the Notation signature: %+v", referenceDescriptor)).WithError(err) } // Note: notation verifier already validates certificate chain is not empty. @@ -174,7 +173,7 @@ func (v *notationPluginVerifier) Verify(ctx context.Context, extensions["Issuer"] = cert.Issuer.String() extensions["SN"] = cert.Subject.String() - return verifier.NewVerifierResult("", v.name, v.verifierType, "Signature verification success", true, nil, extensions), nil + return verifier.NewVerifierResult("", v.name, v.verifierType, "Notation signature verification success", true, nil, extensions), nil } func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory string) (notation.Verifier, error) { @@ -182,7 +181,11 @@ func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory stri if err != nil { return nil, err } - return notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory)) + verifier, err := notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory)) + if err != nil { + return nil, re.ErrorCodePluginInitFailure.WithDetail("Failed to create the Notation Verifier").WithError(err) + } + return verifier, nil } func (v *notationPluginVerifier) verifySignature(ctx context.Context, subjectRef, mediaType string, subjectDesc oci.Descriptor, refBlob []byte) (*notation.VerificationOutcome, error) { @@ -196,16 +199,15 @@ func (v *notationPluginVerifier) verifySignature(ctx context.Context, subjectRef } func parseVerifierConfig(verifierConfig config.VerifierConfig, _ string) (*NotationPluginVerifierConfig, error) { - verifierName := verifierConfig[types.Name].(string) conf := &NotationPluginVerifierConfig{} verifierConfigBytes, err := json.Marshal(verifierConfig) if err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, nil, re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to parse the Notation Verifier configuration: %+v", verifierConfig)).WithError(err) } if err := json.Unmarshal(verifierConfigBytes, &conf); err != nil { - return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, fmt.Sprintf("failed to unmarshal to notationPluginVerifierConfig from: %+v.", verifierConfig), re.HideStackTrace) + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to parse the Notation Verifier configuration: %+v", verifierConfig)).WithError(err) } defaultCertsDir := paths.Join(homedir.Get(), ratifyconfig.ConfigFileDir, defaultCertPath) @@ -235,7 +237,10 @@ func normalizeVerificationCertsStores(conf *NotationPluginVerifierConfig) error } } if isCertStoresByType && isLegacyCertStore { - return re.ErrorCodeConfigInvalid.NewError(re.Verifier, conf.Name, re.EmptyLink, nil, "both old VerificationCertStores and new VerificationCertStores are provided, please provide only one", re.HideStackTrace) + // showing configuration content in the log with error details for better user experience + err := re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("The verificationCertStores is misconfigured with both legacy and new formats: %+v", conf)).WithRemediation("Please provide only one format for the VerificationCertStores. Refer to the Notation Verifier configuration guide: https://ratify.dev/docs/plugins/verifier/notation#configuration") + logger.GetLogger(context.Background(), logOpt).Error(err) + return err } else if !isCertStoresByType && isLegacyCertStore { legacyCertStore, err := normalizeLegacyCertStore(conf) if err != nil { diff --git a/pkg/verifier/notation/truststore.go b/pkg/verifier/notation/truststore.go index c7bceb443..e9490cb71 100644 --- a/pkg/verifier/notation/truststore.go +++ b/pkg/verifier/notation/truststore.go @@ -41,7 +41,7 @@ type trustStore struct { func newTrustStore(certPaths []string, verificationCertStores verificationCertStores) (*trustStore, error) { certStores, err := newCertStoreByType(verificationCertStores) if err != nil { - return nil, err + return nil, re.ErrorCodeConfigInvalid.WithDetail(fmt.Sprintf("Failed to create the trust store from the verificationCertStores parameter in Notation Verifier configuration: %+v", verificationCertStores)).WithError(err).WithRemediation("Please check the value of the verificationCertStores parameter according to the Notation Verifier configuration guide: https://ratify.dev/docs/plugins/verifier/notation#configuration") } store := &trustStore{ certPaths: certPaths, diff --git a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml index bb1564a44..2fdfc94dd 100644 --- a/test/bats/tests/config/config_v1beta1_verifier_notation.yaml +++ b/test/bats/tests/config/config_v1beta1_verifier_notation.yaml @@ -9,7 +9,7 @@ spec: verificationCertStores: ca: ca-certs: - - certstore-inline + - certstore-inline trustPolicyDoc: version: "1.0" trustPolicies: