Skip to content

Commit

Permalink
Add schema and tests for provenance
Browse files Browse the repository at this point in the history
Signed-off-by: Joonas Rautiola <[email protected]>
  • Loading branch information
joinemm committed Mar 20, 2024
1 parent 4e77c63 commit 725c9a1
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 9 deletions.
11 changes: 6 additions & 5 deletions src/provenance/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# SPDX-License-Identifier: Apache-2.0

""" Python script that generates SLSA v1.0 provenance file for a nix target """
"""Python script that generates SLSA v1.0 provenance file for a nix target"""

import argparse
import json
Expand Down Expand Up @@ -42,7 +42,7 @@ def get_env_metadata():
"PROVENANCE_INTERNAL_PARAMS",
]

values = [os.environ.get(name) for name in env_vars]
values = [os.environ.get(name, "") for name in env_vars]

LOG.info("Reading metadata from environment:")
for name, value in zip(env_vars, values):
Expand Down Expand Up @@ -128,10 +128,10 @@ def get_external_parameters(drv_path: str, metadata: BuildMeta) -> dict:
return {k: v for k, v in params.items() if v}


def timestamp(unix_time: int | str | None) -> str | None:
def timestamp(unix_time: int | str | None) -> str:
"""Turn unix timestamp into RFC 3339 format"""
if unix_time is None:
return None
if not unix_time:
return ""

dtime = datetime.fromtimestamp(
int(unix_time),
Expand Down Expand Up @@ -222,6 +222,7 @@ def main():

if args.out:
with open(args.out, "w", encoding="utf-8") as filepath:
LOG.info("Writing provenance file into '%s'", args.out)
filepath.write(json.dumps(schema, indent=2))
else:
print(json.dumps(schema, indent=2))
Expand Down
13 changes: 10 additions & 3 deletions tests/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ SPDX-License-Identifier: CC-BY-SA-4.0
-->

# Test resources

## CycloneDX 1.3 json schema
- cdx_bom-1.3.schema.json
- https://github.com/CycloneDX/specification/blob/9b04a94474dfcabafe7d3a9f8db6c7e5eb868adb/schema/bom-1.3.schema.json

- cdx_bom-1.3.schema.json
- <https://github.com/CycloneDX/specification/blob/9b04a94474dfcabafe7d3a9f8db6c7e5eb868adb/schema/bom-1.3.schema.json>

## SPDX 2.3 json schema

- spdx_bom-2.3.schema.json
- https://github.com/spdx/spdx-spec/blob/214f23d34ee287cb1db5b31c3d571af291e836f3/schemas/spdx-schema.json
- <https://github.com/spdx/spdx-spec/blob/214f23d34ee287cb1db5b31c3d571af291e836f3/schemas/spdx-schema.json>

## SLSA v1.0 provenance schema

- provenance-1.0.schema.json
- <https://slsa.dev/spec/v1.0/provenance#schema> translated and rewritten into jsonschema format.
170 changes: 170 additions & 0 deletions tests/resources/provenance-1.0.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://in-toto.io/Statement/v1",
"title": "SLSA Provenance v1.0",
"type": "object",
"additionalProperties": false,
"required": [
"_type",
"subject",
"predicateType",
"predicate"
],
"properties": {
"_type": {
"description": "Identifier for the schema of the Statement. Always https://in-toto.io/Statement/v1 for this version of the spec.",
"type": "string"
},
"subject": {
"description": "Set of software artifacts that the attestation applies to. Each element represents a single software artifact.",
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"description": "Identifier to distinguish this artifact from others within the subject.",
"type": "string"
},
"digest": {
"description": "Collection of cryptographic digests for the contents of this artifact.",
"type": "object"
}
}
}
},
"predicateType": {
"description": "URI identifying the type of the Predicate.",
"type": "string"
},
"predicate": {
"type": "object",
"additionalProperties": false,
"required": [
"buildDefinition",
"runDetails"
],
"properties": {
"buildDefinition": {
"type": "object",
"additionalProperties": false,
"minProperties": 4,
"properties": {
"buildType": {
"description": "Identifies the template for how to perform the build and interpret the parameters and dependencies.",
"type": "string"
},
"externalParameters": {
"description": "The parameters that are under external control, such as those set by a user or tenant of the build platform.",
"type": "object"
},
"internalParameters": {
"description": "The parameters that are under the control of the entity represented by builder.id.",
"type": "object"
},
"resolvedDependencies": {
"description": "Unordered collection of artifacts needed at build time.",
"type": "array",
"items": {
"$ref": "#/$defs/ResourceDescriptor"
}
}
}
},
"runDetails": {
"type": "object",
"additionalProperties": false,
"required": [
"builder",
"metadata",
"byproducts"
],
"properties": {
"builder": {
"description": "dentifies the build platform that executed the invocation.",
"type": "object",
"properties": {
"id": {
"description": "URI indicating the transitive closure of the trusted build platform.",
"type": "string"
},
"builderDependencies": {
"description": "Dependencies used by the orchestrator that are not run within the workload and that do not affect the build",
"type": "array",
"items": {
"$ref": "#/$defs/ResourceDescriptor"
}
},
"version": {
"description": "Map of names of components of the build platform to their version.",
"type": "object"
}
}
},
"metadata": {
"description": "Metadata about this particular execution of the build.",
"type": "object",
"properties": {
"invocationId": {
"description": "Identifies this particular build invocation",
"type": "string"
},
"startedOn": {
"description": "The timestamp of when the build started.",
"type": "string"
},
"finishedOn": {
"description": "The timestamp of when the build completed.",
"type": "string"
}
}
},
"byproducts": {
"description": "Additional artifacts generated during the build that are not considered the “output” of the build",
"type": "array",
"items": {
"$ref": "#/$defs/ResourceDescriptor"
}
}
}
}
}
}
},
"$defs": {
"ResourceDescriptor": {
"$id": "/schema/ResourceDescriptor",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"description": "Machine-readable identifier for distinguishing between descriptors.",
"type": "string"
},
"uri": {
"description": "A URI used to identify the resource or artifact globally.",
"type": "string"
},
"digest": {
"description": "A set of cryptographic digests of the contents of the resource or artifact.",
"type": "object"
},
"content": {
"description": "The contents of the resource or artifact.",
"type": "string"
},
"downloadLocation": {
"description": "The location of the described resource or artifact, if different from the uri.",
"type": "string"
},
"mediaType": {
"description": "The MIME Type (i.e., media type) of the described resource or artifact.",
"type": "string"
},
"annotations": {
"description": "This field MAY be used to provide additional information or metadata about the resource or artifact that may be useful to the consumer when evaluating the attestation against a policy.",
"type": "object"
}
}
}
}
}
46 changes: 45 additions & 1 deletion tests/test_sbomnix.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# pylint: disable=invalid-name, global-statement, redefined-outer-name
# pylint: disable=too-few-public-methods

""" Tests for sbomnix """
"""Tests for sbomnix"""

import os
import subprocess
Expand Down Expand Up @@ -46,6 +46,7 @@
SBOMNIX = SRCDIR / "sbomnix" / "main.py"
NIXGRAPH = SRCDIR / "nixgraph" / "main.py"
NIXMETA = SRCDIR / "nixmeta" / "main.py"
PROVENANCE = SRCDIR / "provenance" / "main.py"
NIX_OUTDATED = SRCDIR / "nixupdate" / "nix_outdated.py"
VULNXSCAN = SRCDIR / "vulnxscan" / "vulnxscan_cli.py"
REPOLOGY_CLI = SRCDIR / "repology" / "repology_cli.py"
Expand Down Expand Up @@ -657,6 +658,49 @@ def test_whitelist():
################################################################################


def test_provenance_help():
"""Test provenance command line argument: '-h'"""
_run_python_script([PROVENANCE, "-h"])


def test_provenance_schema():
"""Test provenance generates valid schema"""
out_path = TEST_WORK_DIR / "provenance_test.json"
_run_python_script(
[
PROVENANCE,
TEST_NIX_RESULT,
"--out",
out_path.as_posix(),
]
)
assert out_path.exists()
schema_path = MYDIR / "resources" / "provenance-1.0.schema.json"
assert schema_path.exists()
validate_json(out_path.as_posix(), schema_path)


def test_provenance_schema_recursive():
"""Test provenance generates valid schema with recursive option"""
out_path = TEST_WORK_DIR / "recursive_provenance_test.json"
_run_python_script(
[
PROVENANCE,
TEST_NIX_RESULT,
"--recursive",
"--out",
out_path.as_posix(),
]
)
assert out_path.exists()
schema_path = MYDIR / "resources" / "provenance-1.0.schema.json"
assert schema_path.exists()
validate_json(out_path.as_posix(), schema_path)


################################################################################


class JSONSchemaRetrieve:
"""Cached retriever that can be used with jsonschema.validate"""

Expand Down

0 comments on commit 725c9a1

Please sign in to comment.