-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Marcela Melara <[email protected]>
- Loading branch information
0 parents
commit caf62c3
Showing
29 changed files
with
1,284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
cli/scai-venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Software Supply Chain Attribute Integrity | ||
|
||
The Software Supply Chain Attribute Integrity, or SCAI (pronounced "sky"), specification proposes a data | ||
format for capturing functional attribute and integrity information about software artifacts and their supply | ||
chain. SCAI data can be associated with executable binaries, statically- or dynamically-linked libraries, | ||
software packages, container images, software toolchains, and compute environments. | ||
|
||
As such, SCAI is intended to be implemented as part of an existing software supply chain attestation | ||
framework by software development tools or services (e.g., builders, CI/CD pipelines, software analysis tools) | ||
seeking to capture more granular information about the attributes and behavior of the software artifacts they | ||
produce. That is, SCAI assumes that implementers will have appropriate processes and tooling in place for | ||
capturing other types of software supply chain metadata, which can be extended to add support for SCAI. | ||
|
||
For more details and examples, see the full [specification document](). | ||
|
||
## Schema | ||
|
||
SCAI provides pluggable [schema](https://github.com/intel-sandbox/mmelara.supply-chain-attribute-integrity/tree/main/schema) to be used in conjunction | ||
with existing software supply chain metadata schema. | ||
|
||
Currently supported frameworks: | ||
* [in-toto attestation](https://github.com/in-toto/attestation/tree/main/spec) | ||
|
||
## Documentation | ||
|
||
All documentation can be found under [docs/](https://github.com/intel-sandbox/mmelara.supply-chain-attribute-integrity/tree/main/docs). | ||
|
||
## Usage | ||
|
||
The general flow is to first generate one or more Attribute | ||
Assertions and then generate a SCAI Report. The | ||
[examples](https://github.com/intel-sandbox/mmelara.supply-chain-attribute-integrity/tree/main/examples) show | ||
how SCAI metadata is generated in a few different use cases. | ||
|
||
Note, that the CLI tools do not current generate **signed** | ||
SCAI Reports. | ||
|
||
#### CLI Environment Setup | ||
|
||
To run the SCAI CLI tools and examples, the following packages | ||
are required on a minimal Ubuntu system. We assume Ubuntu 20.04. | ||
|
||
``` | ||
sudo apt install git python3 python3-dev python3-venv virtualenv build-essential | ||
``` | ||
|
||
Then, set up the Python virtualenv for the SCAI CLI tools. | ||
|
||
``` | ||
make -C cli | ||
``` | ||
|
||
#### Basic CLI Invocation | ||
|
||
``` | ||
cd cli | ||
source scai-venv/bin/activate | ||
``` | ||
|
||
To generate a basic attribute assertion: | ||
``` | ||
./scai-attr-assertion -a <attribute string> -o <output filename> [-e <evidence filename>] [-c <conditions string>] | ||
``` | ||
|
||
To generate a basic SCAI Report with in-toto Link metadata: | ||
``` | ||
./scai-report -i <input artifact filenames> -a <attribute assertion filenames> -c <command to execute as string> | ||
``` | ||
|
||
For a full list of CLI tool options, invoke with the `-h` option. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Copyright 2020 Intel Corporation | ||
# | ||
# 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. | ||
|
||
DSTDIR=scai-venv | ||
|
||
PY_VERSION=${shell python3 --version | sed 's/Python \(3\.[0-9]\).*/\1/'} | ||
PYTHON_DIR=$(DSTDIR)/lib/python$(PY_VERSION)/site-packages/ | ||
|
||
all : environment | ||
|
||
$(PYTHON_DIR) : | ||
@echo INSTALL SCAI API | ||
python3 -m venv $(DSTDIR) | ||
. $(abspath $(DSTDIR)/bin/activate) && pip install --upgrade pip | ||
. $(abspath $(DSTDIR)/bin/activate) && pip install --upgrade wheel | ||
. $(abspath $(DSTDIR)/bin/activate) && pip install --upgrade in-toto | ||
. $(abspath $(DSTDIR)/bin/activate) && pip install ../python | ||
|
||
$(DSTDIR) : | ||
@echo CREATE SCAI VENV DIRECTORY $(DSTDIR) | ||
mkdir -p $(DSTDIR) | ||
|
||
environment: $(DSTDIR) $(PYTHON_DIR) | ||
|
||
clean: | ||
@echo REMOVE SCAI VENV AND PYTHON LIB DIRS | ||
@rm -rf $(DSTDIR) __pycache__ | ||
@cd ../python; rm -rf build dist *.egg-info | ||
|
||
.phony : all | ||
.phony : clean | ||
.phony : environment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#!/usr/bin/env python | ||
# Copyright 2022 Intel Corporation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
""" | ||
<Program Name> | ||
scai-attr-assertion | ||
<Author> | ||
Marcela Melara <[email protected]> | ||
<Copyright> | ||
See LICENSE for licensing information. | ||
<Purpose> | ||
Command-line interface for generating ITE-9 SCAI Attribute Assertions. | ||
""" | ||
|
||
import argparse | ||
import subprocess | ||
import json | ||
import os | ||
import attr | ||
|
||
from scai.attribute_assertion import AttributeAssertion | ||
from scai.object_reference import ObjectReference | ||
from scai.utility import load_json_file | ||
|
||
from securesystemslib.util import get_file_hashes | ||
|
||
def Main(): | ||
parser = argparse.ArgumentParser(allow_abbrev=False) | ||
|
||
parser.add_argument('-a', '--attribute', help='Attribute keyword', type=str, required=True) | ||
parser.add_argument('-c', '--conditions', help='Conditions string (arbitrary JSON encoding)', type=json.loads) | ||
parser.add_argument('-e', '--evidence', help='Filename of json-encoded evidence descriptor', type=str) | ||
parser.add_argument('-t', '--target', help='Filename of target artifact', type=str) | ||
parser.add_argument('--target-name', help='Name for target artifact', type=str) | ||
parser.add_argument('--target-type', help='Type of target artifact', type=str) | ||
parser.add_argument('--target-location', help='Location URI of target artifact', type=str) | ||
parser.add_argument('-o', '--outfile', help='Filename to write out this assertion object', type=str, required=True) | ||
parser.add_argument('--target-dir', help='Directory for searching target files', type=str) | ||
parser.add_argument('--evidence-dir', help='Directory for searching evidence files', type=str) | ||
parser.add_argument('--conditions-dir', help='Directory for searching conditions files', type=str) | ||
parser.add_argument('--out-dir', help='Directory for storing generated files', type=str) | ||
parser.add_argument('--pretty-print', help='Flag to pretty-print all json before storing', action='store_true') | ||
|
||
options = parser.parse_args() | ||
|
||
# Create assertion dict | ||
assertion_dict = { "attribute": options.attribute } | ||
|
||
# Read conditions | ||
if options.conditions: | ||
assertion_dict['conditions'] = options.conditions # FIXME: this should be a file | ||
|
||
# Read evidencee | ||
if options.evidence: | ||
evidence_dict = load_json_file(options.evidence, search_path=options.evidence_dir) | ||
|
||
# this validates the obj ref format | ||
evidence = ObjectReference.read(evidence_dict) | ||
assertion_dict['evidence'] = evidence.to_dict() | ||
|
||
# Create target reference (optional) | ||
if options.target: | ||
target_dict = '.' | ||
if options.target_dict: | ||
target_dict = options.target_dict | ||
|
||
target_file = target_dict + '/' + options.target | ||
target_digest_dict = get_file_hashes(target_file) | ||
|
||
target_dict = {} | ||
target_dict['name'] = options.target | ||
target_dict['digest'] = target_digest_dict | ||
|
||
if options.target_name: | ||
target_dict['name'] = options.target_name | ||
|
||
if options.target_location: | ||
target_dict['locationURI'] = options.target_location | ||
|
||
if options.target_type: | ||
target_dict['objectType'] = options.target_type | ||
|
||
# this validates the obj ref format | ||
target = ObjectReference.read(target_dict) | ||
|
||
assertion_dict['target'] = target.to_dict() | ||
|
||
# this validates the assertion format | ||
assertion = AttributeAssertion.read(assertion_dict) | ||
assertion_dict = assertion.to_dict() | ||
|
||
# Write out the assertions file | ||
out_dir = '.' | ||
if options.out_dir: | ||
out_dir = options.out_dir | ||
|
||
outfile = options.outfile | ||
if not outfile.endswith('.json'): | ||
outfile += '.json' | ||
|
||
indent = 0 | ||
if options.pretty_print: | ||
indent = 4 | ||
|
||
assertion_file = out_dir + '/' + outfile | ||
with open(assertion_file, 'w+') as afile : | ||
afile.write(json.dumps(assertion_dict, indent=indent)) | ||
|
||
print('Wrote attribute assertion to %s' % assertion_file) | ||
|
||
if __name__ == "__main__": | ||
Main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright 2021 Intel Corporation | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
""" | ||
<Program Name> | ||
scai-report | ||
<Author> | ||
Marcela Melara <[email protected]> | ||
<Copyright> | ||
See LICENSE for licensing information. | ||
<Purpose> | ||
Command-line interface for generating ITE-9 SCAI Reports. | ||
""" | ||
|
||
import argparse | ||
import subprocess | ||
import json | ||
import os | ||
import attr | ||
|
||
from scai.report import Report, SCAI_REPORT_TYPE | ||
from scai.attribute_assertion import AttributeAssertion | ||
from scai.object_reference import ObjectReference | ||
from scai.utility import load_json_file, parse_pem_file | ||
|
||
from in_toto.runlib import record_artifacts_as_dict, in_toto_run | ||
|
||
def Main(): | ||
parser = argparse.ArgumentParser(allow_abbrev=False) | ||
|
||
parser.add_argument('-i', '--input-artifacts', type=str, help='Filenames of input artifacts', nargs='+', required=True) | ||
parser.add_argument('-o', '--output-artifacts', type=str, help='Filenames of output artifacts', nargs='+') | ||
parser.add_argument('-a', '--attribute-assertions', help='Filename of JSON files for output artifact attribute assertions', nargs='+', type=str, required=True) | ||
parser.add_argument('-p', '--producer-attributes', help='Filename of JSON files for producer attribute assertions', nargs='+', type=str) | ||
parser.add_argument('-c', '--command', help='Command to invoke the tool', type=str, required=True) | ||
parser.add_argument('--metadata-dir', help='Directory for searching/storing metadata files', type=str) | ||
parser.add_argument('--artifact-dir', help='Directory for searching/storing input/output artifacts', type=str) | ||
parser.add_argument('--pretty-print', help='Flag to pretty-print all json before storing', action='store_true') | ||
|
||
options = parser.parse_args() | ||
|
||
print('Generating SCAI Attribute Report for operation: %s' % options.command) | ||
|
||
# assume all SCAI verifiable objects are in the same location | ||
# still use list for compatibility with file util functions | ||
metadata_dir = '.' | ||
if options.metadata_dir: | ||
metadata_dir = options.metadata_dir | ||
|
||
# Execute the command and get the operation metadata | ||
# in in-toto Link format | ||
command = options.command.split(' ') | ||
command_list = [os.path.expanduser(arg) if arg.startswith('~') else arg for arg in command] | ||
|
||
intoto_link = in_toto_run('test', options.input_artifacts, options.output_artifacts, command_list, record_streams=True, base_path=options.artifact_dir) | ||
intoto_link_dict = attr.asdict(intoto_link.signed) | ||
intoto_link_dict['_type'] = 'https://in-toto.io/Link/v0.2' | ||
|
||
# Generate the producer metadata for the SCAI Report | ||
producer_metadata = {} | ||
|
||
# Load the producer attribute assertions | ||
if options.producer_attributes: | ||
producer_assertions_list = [] | ||
for a in options.producer_attributes: | ||
assertion_dict = load_json_file(a, metadata_dir) | ||
assertion = AttributeAssertion.read(assertion_dict) | ||
del assertion_dict['_type'] | ||
producer_assertions_list.append(assertion_dict) | ||
|
||
producer_metadata['attributes'] = producer_assertions_list | ||
|
||
# Load the subject attribute assertions | ||
subject_assertions_list = [] | ||
for a in options.attribute_assertions: | ||
assertion_dict = load_json_file(a, metadata_dir) | ||
assertion = AttributeAssertion.read(assertion_dict) | ||
subject_assertions_list.append(assertion_dict) | ||
|
||
scai_report = Report(subjectAttributes=subject_assertions_list, producer=producer_metadata) | ||
scai_report_dict = attr.asdict(scai_report) | ||
del scai_report_dict['_type'] | ||
|
||
# Write the SCAI metadata to file | ||
# in-toto attestation format | ||
statement = {} # inner in-toto attesation layer | ||
statement['_type'] = 'https://in-toto.io/Statement/v0.1' | ||
|
||
# though in-toto supports bundling multiple subjects | ||
# per statement, we want single-subject statements | ||
for pname, pdigest in intoto_link_dict['products'].items(): | ||
subjects = [{'name': pname, 'digest': pdigest}] | ||
|
||
statement['subject'] = subjects | ||
statement['predicateType'] = SCAI_REPORT_TYPE | ||
statement['predicate'] = scai_report_dict | ||
statement_json = json.dumps(statement) | ||
|
||
report_file = metadata_dir + '/' + pname + '-scai.st' | ||
|
||
with open(report_file, 'w+') as rfile : | ||
if options.pretty_print: | ||
rfile.write(json.dumps(statement, indent=4)) | ||
else: | ||
rfile.write(json.dumps(statement)) | ||
|
||
print('Wrote in-toto statement for SCAI predicate: %s' % report_file) | ||
|
||
# Generate the ITE-9 in-toto Link statement for the command invocation | ||
link_subjects = [] | ||
for pname, pdigest in intoto_link_dict['products'].items(): | ||
link_subjects.append({'name': pname, 'digest': pdigest}) | ||
|
||
del intoto_link_dict['products'] | ||
|
||
statement['subject'] = link_subjects | ||
statement['predicateType'] = intoto_link_dict['_type'] | ||
|
||
del intoto_link_dict['_type'] | ||
|
||
statement['predicate'] = intoto_link_dict | ||
statement_json = json.dumps(statement) | ||
|
||
report_file = metadata_dir + '/invocation-intoto.st' | ||
|
||
with open(report_file, 'w+') as rfile : | ||
if options.pretty_print: | ||
rfile.write(json.dumps(statement, indent=4)) | ||
else: | ||
rfile.write(json.dumps(statement)) | ||
|
||
print('Wrote in-toto statement for Link predicate: %s' % report_file) | ||
|
||
if __name__ == "__main__": | ||
Main() |
Oops, something went wrong.