-
Notifications
You must be signed in to change notification settings - Fork 1
docs: explain how CycloneDX and SPDX SBOM attestations differ #420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ec5339a
03c12db
560b7bc
1ef31d2
7e6855c
0f4d1b5
fe991d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -306,6 +306,66 @@ Currently, we support the following types of evidence: | |||||||||||||||||||||||||||||||||
| Nothing in the SBOM is checked against the artifact. It is recorded as reported, so the | ||||||||||||||||||||||||||||||||||
| attestation says what the SBOM claims, not whether the claim is true. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| **What you get depends on the tool, not just the format.** Kosli records what the document | ||||||||||||||||||||||||||||||||||
| declares, and tools fill the same fields differently. Three differences catch people out. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| *The subject's digest is often absent from CycloneDX.* Kosli fills `subject.sha256` from | ||||||||||||||||||||||||||||||||||
| the subject's SHA-256 checksum. Each format spells that differently: `hashes` in | ||||||||||||||||||||||||||||||||||
| CycloneDX, and `checksums` in SPDX, written `PackageChecksum` in the tag-value form. Syft's | ||||||||||||||||||||||||||||||||||
| SPDX output fills it. Snyk's and Syft's CycloneDX output does not: both leave `hashes` | ||||||||||||||||||||||||||||||||||
| empty and put the digest in `version`, where it reads as a version string rather than a | ||||||||||||||||||||||||||||||||||
| checksum. Kosli does not infer a checksum from a version, so `subject.sha256` is empty for | ||||||||||||||||||||||||||||||||||
| those two, and the digest they wrote is in `subject.version`. That is the tool's choice | ||||||||||||||||||||||||||||||||||
| rather than a limit of CycloneDX, so check what yours writes instead of assuming either | ||||||||||||||||||||||||||||||||||
| way. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| *A digest that is present is not automatically the artifact's.* The subject identifies what | ||||||||||||||||||||||||||||||||||
| the generator scanned. Point one at a tag and it records whatever that tag resolved to on | ||||||||||||||||||||||||||||||||||
| that machine. For a multi-architecture image that is a single architecture, and it can be a | ||||||||||||||||||||||||||||||||||
| local image id rather than a registry digest. Kosli does not check the subject against the | ||||||||||||||||||||||||||||||||||
| artifact, so a digest that is present can still belong to something else. Compare the two | ||||||||||||||||||||||||||||||||||
| only where your pipeline pointed the generator at the exact artifact it attests. Otherwise | ||||||||||||||||||||||||||||||||||
| check it in the pipeline, where the build can fail, rather than in a policy. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| *Package counts are not comparable between formats.* `package_count` counts what each | ||||||||||||||||||||||||||||||||||
| format calls a package. A CycloneDX component with `type: file` is skipped, while the SPDX | ||||||||||||||||||||||||||||||||||
| package describing that same file is counted. Syft reports one package for | ||||||||||||||||||||||||||||||||||
| `kosli_Linux_arm64.rpm` in CycloneDX and two for the same file in SPDX. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Only [Rego](/policy-reference/rego_policy#input-data) can read these fields. Environment | ||||||||||||||||||||||||||||||||||
| policy expressions get the artifact's name and fingerprint, nothing from inside an | ||||||||||||||||||||||||||||||||||
| attestation. Evaluation copies an attestation's own fields onto its status entry, so the | ||||||||||||||||||||||||||||||||||
| summary sits under `attestation_data`: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ```rego | ||||||||||||||||||||||||||||||||||
| package policy | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import rego.v1 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| sbom_attestation_name := data.params.sbom_attestation_name | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| default allow := false | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| sbom_lists_packages(artifact) if { | ||||||||||||||||||||||||||||||||||
| sbom := artifact.attestations_statuses[sbom_attestation_name] | ||||||||||||||||||||||||||||||||||
| sbom.attestation_data.document.package_count > 0 | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| allow if { | ||||||||||||||||||||||||||||||||||
| every artifact in input.trail.compliance_status.artifacts_statuses { | ||||||||||||||||||||||||||||||||||
| sbom_lists_packages(artifact) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+354
to
+358
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion — the policy denies without saying why, on a page whose subject is rules that give no signal.
Suggested change
Worth an |
||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||
|
AlexKantor87 marked this conversation as resolved.
AlexKantor87 marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Pass the attestation name in [`--params`](/policy-reference/rego_policy#params). Without it | ||||||||||||||||||||||||||||||||||
| the alias is undefined and no rule using it runs. A trail-scoped SBOM sits at | ||||||||||||||||||||||||||||||||||
| `input.trail.compliance_status.attestations_statuses[sbom_attestation_name].attestation_data.document`. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| If you narrow the input with `kosli evaluate trail --attestations`, name the SBOM there too, | ||||||||||||||||||||||||||||||||||
| dot-qualified as `<artifact>.<name>` for an artifact-scoped one. Anything left out is absent | ||||||||||||||||||||||||||||||||||
| from the input, and a rule reading it does not match rather than failing. | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+335
to
+367
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improvement — this is a complete, runnable Rego policy plus The Meanwhile What earns its place here is the part about what the attestation contains: the three tool/format differences (309-333), the two field paths, and the sentence that environment policy expressions cannot reach them. Suggest keeping those, replacing the code block with a link to the new example, and moving 361-367 with it. |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| The CLI refuses an SBOM file larger than 9 MiB, which leaves room for the attestation | ||||||||||||||||||||||||||||||||||
| itself within the 10 MB the server accepts. We are working on raising this. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -143,6 +143,8 @@ The policy receives `input.trail`, a single trail object. | |||||
|
|
||||||
| <ParamField path="input.trail.compliance_status.attestations_statuses" type="object"> | ||||||
| Map of attestation name to attestation status object. Each object contains the attestation's data, including type-specific fields enriched via `--attestations`. For example, a `pull-request` attestation includes a `pull_requests` array, each with an `approvers` array and a `url` string. | ||||||
|
|
||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion — "
The link you dropped last round also has a working anchor after all —
Suggested change
|
||||||
| Some types put their summary under `attestation_data` rather than on the object itself. An `sbom` attestation keeps it at `attestation_data.document`, which holds `package_count` and a `subject` object with `name`, `version`, `purl` and `sha256`. `purl` and `sha256` are often empty, because they only carry what the tool that wrote the SBOM recorded. | ||||||
| </ParamField> | ||||||
|
|
||||||
| <ParamField path="input.trail.compliance_status.artifacts_statuses" type="object"> | ||||||
|
|
@@ -256,7 +258,7 @@ artifact_within_threshold(artifact) if { | |||||
| } | ||||||
|
|
||||||
| trail_is_compliant(trail) if { | ||||||
| every name, artifact in trail.compliance_status.artifacts_statuses { | ||||||
| every artifact in trail.compliance_status.artifacts_statuses { | ||||||
| artifact_within_threshold(artifact) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.