diff --git a/library/rego/sbom-validation.rego b/library/rego/sbom-validation.rego new file mode 100644 index 000000000..2bf8cda52 --- /dev/null +++ b/library/rego/sbom-validation.rego @@ -0,0 +1,61 @@ + +# Copyright The Ratify Authors. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package ratify.policy + +# This template defines policy for SBOM validation. +# It checks the following: +# - There is at least one SBOM report that was verified +# - Only considers ONE SBOM report +# - The SBOM is valid (isSuccess = true) +# - The SBOM has a valid notary project signature (if require_signature = true) + +import future.keywords.if +import future.keywords.in + +default require_signature := false # change to true to require notary project signature on SBOM +default valid := false + +valid { + not failed_verify(input) +} + +failed_verify(reports) { + not process_sboms(reports) +} + +process_sboms(subject_result) if { + # collect verifier reports from sbom verifier + sbom_results := [res | subject_result.verifierReports[i].verifierReports[j].type == "sbom"; res := subject_result.verifierReports[i].verifierReports[j]] + count(sbom_results) > 0 + # validate SBOM contents for ONLY the first report found + process_sbom(sbom_results[0]) +} + +process_sbom(report) if { + report.isSuccess == true + valid_signatures(report) +} + +valid_signatures(_) := true { + require_signature == false +} + +valid_signatures(report) := true { + require_signature + count(report.nestedResults) > 0 + some nestedResult in report.nestedResults + nestedResult.artifactType == "application/vnd.cncf.notary.signature" + nestedResult.isSuccess +} diff --git a/library/rego/vulnerability-report-validation.rego b/library/rego/vulnerability-report-validation.rego index 98aedca8d..09d3f0913 100644 --- a/library/rego/vulnerability-report-validation.rego +++ b/library/rego/vulnerability-report-validation.rego @@ -19,8 +19,6 @@ import future.keywords.every # This template defines policy for vulnerability report validation. # It checks the following: -# - If there are any system errors -# - If there are errors for any of the images # - There is at least one vulnerability report that was verified # - Only considers the latest vulnerability report # - The latest vulnerability report is valid (isSuccess = true) @@ -40,7 +38,7 @@ failed_verify(reports) { process_vuln_reports(subject_result) if { # collect verifier reports from vulnerabilityreport verifier - vuln_results := [res | subject_result.verifierReports[i].verifierReports[j].name == "vulnerabilityreport"; res := subject_result.verifierReports[i].verifierReports[j]] + vuln_results := [res | subject_result.verifierReports[i].verifierReports[j].type == "vulnerabilityreport"; res := subject_result.verifierReports[i].verifierReports[j]] count(vuln_results) > 0 # calculate the timestamp between current time and creation time timestamp_diff_results_map := {diff_in_ns: i | diff_in_ns := time.now_ns() - time.parse_rfc3339_ns(vuln_results[i].extensions["createdAt"])} diff --git a/library/sbom-validation/samples/constraint.yaml b/library/sbom-validation/samples/constraint.yaml new file mode 100644 index 000000000..b12c8854d --- /dev/null +++ b/library/sbom-validation/samples/constraint.yaml @@ -0,0 +1,11 @@ +apiVersion: constraints.gatekeeper.sh/v1beta1 +kind: SbomValidation +metadata: + name: sbom-validation-constraint +spec: + enforcementAction: deny + match: + kinds: + - apiGroups: [""] + kinds: ["Pod"] + namespaces: ["default"] \ No newline at end of file diff --git a/library/sbom-validation/template.yaml b/library/sbom-validation/template.yaml index 68cb92a6b..0306c9adf 100644 --- a/library/sbom-validation/template.yaml +++ b/library/sbom-validation/template.yaml @@ -12,6 +12,20 @@ spec: rego: | package sbomvalidation + # This template defines policy for SBOM validation. + # It checks the following: + # - If there are any system errors + # - If there are errors for any of the images + # - There is at least one SBOM report that was verified + # - Only considers ONE SBOM report + # - The SBOM is valid (isSuccess = true) + # - The SBOM has a valid notary project signature (if require_signature = true)s + + import future.keywords.if + import future.keywords.in + + default require_signature := false # change to true to require notary project signature on SBOM + # Get data from Ratify remote_data := response { images := [img | img = input.review.object.spec.containers[_].image] @@ -31,28 +45,44 @@ spec: general_violation[{"result": result}] { err := remote_data.system_error err != "" - result := sprintf("System error calling external data provider: %s", [err]) + result := sprintf("System error calling external data provider for SBOM verification: %s", [err]) } # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 - result := sprintf("Error validating one or more images: %s", remote_data.errors) + result := sprintf("Error validating one or more images for SBOM verification: %s", remote_data.errors) } - + # Check if the success criteria is true general_violation[{"result": result}] { subject_validation := remote_data.responses[_] - subject_validation[1].isSuccess == false - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + subject_result := subject_validation[1] + not process_sboms(subject_result) + result := sprintf("Subject failed SBOM verification: %s", [subject_validation[0]]) } - - # Check for failed sbom validation - general_violation[{"result": result}] { - subject_results := remote_data.responses[_] - subject_result := subject_results[1] - sbom_results := [res | subject_result.verifierReports[i].name == "sbom"; res := subject_result.verifierReports[i]] - sbom_result := sbom_results[_] - sbom_result.isSuccess == false - result = sprintf("Subject %s failed sbom validation: %s", [subject_results[0], sbom_result.message]) + + process_sboms(subject_result) if { + # collect verifier reports from sbom verifier + sbom_results := [res | subject_result.verifierReports[i].type == "sbom"; res := subject_result.verifierReports[i]] + count(sbom_results) > 0 + # validate SBOM contents for ONLY the first report found + process_sbom(sbom_results[0]) + } + + process_sbom(report) if { + report.isSuccess == true + valid_signatures(report) + } + + valid_signatures(_) := true { + require_signature == false + } + + valid_signatures(report) := true { + require_signature + count(report.nestedResults) > 0 + some nestedResult in report.nestedResults + nestedResult.artifactType == "application/vnd.cncf.notary.signature" + nestedResult.isSuccess } diff --git a/library/vulnerability-report-validation/template.yaml b/library/vulnerability-report-validation/template.yaml index b12dab136..b09b5fb15 100644 --- a/library/vulnerability-report-validation/template.yaml +++ b/library/vulnerability-report-validation/template.yaml @@ -51,13 +51,13 @@ spec: general_violation[{"result": result}] { err := remote_data.system_error err != "" - result := sprintf("System error calling external data provider: %s", [err]) + result := sprintf("System error calling external data provider for vulnerability report verification: %s", [err]) } # Check if there are errors for any of the images general_violation[{"result": result}] { count(remote_data.errors) > 0 - result := sprintf("Error validating one or more images: %s", remote_data.errors) + result := sprintf("Error validating one or more images for vulnerability report verification: %s", remote_data.errors) } # Check if the success criteria is true @@ -65,12 +65,12 @@ spec: subject_validation := remote_data.responses[_] subject_result := subject_validation[1] not process_vuln_reports(subject_result) - result := sprintf("Subject failed verification: %s", [subject_validation[0]]) + result := sprintf("Subject failed vulnerability report verification: %s", [subject_validation[0]]) } process_vuln_reports(subject_result) if { # collect verifier reports from vulnerabilityreport verifier - vuln_results := [res | subject_result.verifierReports[i].name == "vulnerabilityreport"; res := subject_result.verifierReports[i]] + vuln_results := [res | subject_result.verifierReports[i].type == "vulnerabilityreport"; res := subject_result.verifierReports[i]] count(vuln_results) > 0 # calculate the timestamp between current time and creation time timestamp_diff_results_map := {diff_in_ns: i | diff_in_ns := time.now_ns() - time.parse_rfc3339_ns(vuln_results[i].extensions["createdAt"])} diff --git a/plugins/verifier/cosign/cosign.go b/plugins/verifier/cosign/cosign.go index a70a0b2c6..af0ef6582 100644 --- a/plugins/verifier/cosign/cosign.go +++ b/plugins/verifier/cosign/cosign.go @@ -96,7 +96,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } - verifierType := "" + verifierType := input.Config.Name if input.Config.Type != "" { verifierType = input.Config.Type } diff --git a/plugins/verifier/licensechecker/licensechecker.go b/plugins/verifier/licensechecker/licensechecker.go index ec7c1dcb9..f38ca9037 100644 --- a/plugins/verifier/licensechecker/licensechecker.go +++ b/plugins/verifier/licensechecker/licensechecker.go @@ -59,7 +59,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc if err != nil { return nil, err } - verifierType := "" + verifierType := input.Name if input.Type != "" { verifierType = input.Type } diff --git a/plugins/verifier/sample/sample.go b/plugins/verifier/sample/sample.go index 7426b1998..51ce8f658 100644 --- a/plugins/verifier/sample/sample.go +++ b/plugins/verifier/sample/sample.go @@ -55,7 +55,7 @@ func VerifyReference(args *skel.CmdArgs, _ common.Reference, referenceDescriptor if err != nil { return nil, err } - verifierType := "" + verifierType := input.Name if input.Type != "" { verifierType = input.Type } diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go index 5da724b41..df93746b7 100644 --- a/plugins/verifier/sbom/sbom.go +++ b/plugins/verifier/sbom/sbom.go @@ -74,7 +74,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } - verifierType := "" + verifierType := input.Name if input.Type != "" { verifierType = input.Type } diff --git a/plugins/verifier/schemavalidator/schema_validator.go b/plugins/verifier/schemavalidator/schema_validator.go index 2dd5fec02..a5ab0edd4 100644 --- a/plugins/verifier/schemavalidator/schema_validator.go +++ b/plugins/verifier/schemavalidator/schema_validator.go @@ -58,7 +58,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } - verifierType := "" + verifierType := input.Name if input.Type != "" { verifierType = input.Type } diff --git a/plugins/verifier/vulnerabilityreport/vulnerability_report.go b/plugins/verifier/vulnerabilityreport/vulnerability_report.go index a3eb495c0..912721d8e 100644 --- a/plugins/verifier/vulnerabilityreport/vulnerability_report.go +++ b/plugins/verifier/vulnerabilityreport/vulnerability_report.go @@ -82,7 +82,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe if err != nil { return nil, err } - verifierType := "" + verifierType := input.Name if input.Type != "" { verifierType = input.Type }