Skip to content
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

Change to Signed Statements for Hashed Payloads #5

Merged
merged 33 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5bfcfa2
Merge pull request #1 from digicert/main
SteveLasker Apr 23, 2024
79e1adc
Merge branch 'digicert:main' into main
SteveLasker Apr 25, 2024
1e4b486
Add receipt retreival, cleanup debugging info
SteveLasker Apr 25, 2024
6de9849
Fix token file reference
SteveLasker Apr 25, 2024
4d6b0c9
Add sample for the SCITT Action
SteveLasker Apr 25, 2024
21be315
Add skip-receipt
SteveLasker May 1, 2024
e149a00
Add skip-receipt
SteveLasker May 1, 2024
11f0671
Add skip-receipt
SteveLasker May 1, 2024
6bf1d26
Fix parameter reference for skip-receipt
SteveLasker May 1, 2024
f1f971b
Add file error handling
SteveLasker May 1, 2024
61cae44
Fix parameter reference for skip-receipt
SteveLasker May 1, 2024
e68f927
Test curl errors
SteveLasker May 1, 2024
807f1c7
Test curl errors
SteveLasker May 1, 2024
130ae96
Change to signed statements of hashed payloads
SteveLasker Jul 10, 2024
06515b1
Merge hashed payloads to digicert signed statements
SteveLasker Jul 11, 2024
f2865d5
Update numbered to named variables
SteveLasker Jul 11, 2024
722a428
Fix param ordering
SteveLasker Jul 11, 2024
0e58879
Enable debugging
SteveLasker Jul 11, 2024
6e0cb1d
Name parameters
SteveLasker Jul 11, 2024
8040333
Name parameters
SteveLasker Jul 11, 2024
d695e56
Add receipt handling
SteveLasker Jul 11, 2024
2d1ea86
Update HEADER parameters for private use
SteveLasker Jul 12, 2024
71fd9d1
Readme sample updates
SteveLasker Jul 12, 2024
1a2ab05
Comment out debugging output
SteveLasker Jul 12, 2024
5128af5
Change return to exit for error handling
SteveLasker Jul 17, 2024
7a9abb7
Add more error handling
SteveLasker Jul 17, 2024
2fad494
Add COSE Type
SteveLasker Jul 17, 2024
2c845da
Fix call to generate a receipt
SteveLasker Jul 17, 2024
59681ee
Add PREVIEW heading
SteveLasker Jul 17, 2024
c996926
Add error handling for check-operation-status
SteveLasker Jul 19, 2024
886858f
Add error handling for check-operation-status
SteveLasker Jul 19, 2024
e098789
Remove duplicative lines
SteveLasker Jul 19, 2024
27fe95b
PR Feedback
SteveLasker Jul 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
# GitHub Action for creating and registering SCITT statements with Software Trust Manager and DataTrails

This GitHub Action provides the ability to create and sign [SCITT](https://datatracker.ietf.org/wg/scitt/about/) statements using code signing keys protected by DigiCert [Software Trust Manager](https://www.digicert.com/software-trust-manager) and submit these statements to the transparency service operated by [DataTrails](https://www.datatrails.ai/).
This GitHub Action provides the ability to create and sign [SCITT](https://datatracker.ietf.org/wg/scitt/about/) statements using code signing keys protected by DigiCert [Software Trust Manager](https://www.digicert.com/software-trust-manager) and register these statements to a SCITT transparency service operated by [DataTrails](https://www.datatrails.ai/).

**NOTE:**:
This SCITT GitHub Action is in Preview, pending adoption of the [SCITT Reference APIs (SCRAPI)](https://datatracker.ietf.org/doc/draft-ietf-scitt-scrapi/).
To use a production supported implementation, please contact [DataTrails](https://www.datatrails.ai/contactus/) for more info.

## Getting Started

1. Generate a keypair and corresponding end-entity certificate in [Software Trust Manager](https://www.digicert.com/software-trust-manager)
2. [Create an account](https://app.datatrails.ai/signup) at DataTrails and [create an access token](https://docs.datatrails.ai/developers/developer-patterns/getting-access-tokens-using-app-registrations/)
1. [Create an account](https://app.datatrails.ai/signup) at DataTrails and [create an access token](https://docs.datatrails.ai/developers/developer-patterns/getting-access-tokens-using-app-registrations/)
1. Configure a GitHub SCITT Action, with the following [Inputs](#action-inputs) and [Example](#example-usage)

## Action Inputs

## `datatrails-client_id`
### `content-type`

**Required** The payload content type (iana mediaType) to be registered on the SCITT Service (eg: application/spdx+json, application/vnd.cyclonedx+json, Scan Result, Attestation)

### `datatrails-client_id`

**Required** The `CLIENT_ID` used to access the DataTrails SCITT APIs

## `datatrails-secret`
### `datatrails-secret`

**Required** The `SECRET` used to access the DataTrails SCITT APIs

## `subject`
### `payload-file`

**Required** Unique ID for the collection of statements about an artifact. For more info, see `subject` in the [IETF SCITT Terminology](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-architecture#name-terminology).
**Required** The payload file to be registered on the SCITT Service (eg: SBOM, Scan Result, Attestation)

### `payload`
### `payload-location`

**Required** The payload file to be registered on the SCITT Service (SBOM, Scan Result, Attestation, etc.)
**Optional** Location the content of the payload may be stored.

### `content-type`
### `receipt-file`

**Required** The payload content type (IANA media type) to be registered on the SCITT Service. For example: `application/spdx+json`
**Optional** The filename to save the cbor receipt
**Default** 'receipt.cbor'

### `signed-statement-file`

**Optional** A required file representing the signed SCITT Statement that will be registered with the SCITT Transparency Service. The parameter is optional, as it provides a default file name.
**Optional** A required file representing the signed SCITT Statement that will be registered with the SCITT Transparency Service.
The parameter is optional, as it provides a default file name.
See [Signed Statement Issuance and Registration](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-architecture#name-signed-statement-issuance-a)
**Default** 'signed-statement.cbor'

### `skip-receipt`

**Optional** To skip receipt retrieval, set to 1
**Default** '0'

### `subject`

**Required** Unique ID for the collection of statements about an artifact.
For more info, see `subject` in the [IETF SCITT Terminology](https://datatracker.ietf.org/doc/html/draft-ietf-scitt-architecture#name-terminology).

## Secrets

This action requires secrets containing credentials and keypair information be configured. Specifically, the following secrets are required:
This action requires secrets containing credentials and keypair information be configured.
Specifically, the following secrets are required:

### DIGICERT_STM_CERTIFICATE_ID

Expand Down Expand Up @@ -107,16 +129,24 @@ jobs:
# A sample compliance file. Replace with an SBOM, in-toto statement, image for content authenticity, ...
run: |
echo '{"author": "fred", "title": "my biography", "reviews": "mixed"}' > ./buildOutput/attestation.json
- name: Register as a SCITT Signed Statement
# Register the Signed Statement with DataTrails SCITT APIs
- name: Upload Attestation
id: upload-attestation
uses: actions/upload-artifact@v4
with:
name: attestation.json
path: ./buildOutput/attestation.json
- name: Sign & Register as a SCITT Signed Statement
# Register the DigiCert Signed Statement with the DataTrails SCITT APIs
id: register-compliance-scitt-signed-statement
uses: digicert/scitt-action@v0.2
uses: digicert/scitt-action@v0.3
with:
content-type: "application/vnd.unknown.attestation+json"
datatrails-client_id: ${{ env.DATATRAILS_CLIENT_ID }}
datatrails-secret: ${{ env.DATATRAILS_SECRET }}
subject: ${{ github.server_url }}/${{ github.repository }}@${{ github.sha }}
payload: "./buildOutput/attestation.json"
content-type: "application/vnd.unknown.attestation+json"
payload-file: "./buildOutput/attestation.json"
payload-location: ${{ steps.upload-attestation.outputs.artifact-url }}
subject: "ghcr.io/${{ github.repository }}:${{ github.sha }}"
skip-receipt: "0"
- name: upload-signed-statement
uses: actions/upload-artifact@v4
with:
Expand Down
37 changes: 23 additions & 14 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
name: 'DataTrails SCITT API'
description: 'Register, Get Receipts and Query Feeds from the DataTrails SCITT API'
inputs:
content-type:
description: 'The payload content type (iana mediaType) to be registered on the SCITT Service (eg: application/spdx+json, application/vnd.cyclonedx+json, Scan Result, Attestation)'
required: true
datatrails-client_id:
description: 'The CLIENT_ID used to access the DataTrails SCITT APIs'
required: true
datatrails-secret:
description: 'The SECRET used to access the DataTrails SCITT APIs'
required: true
subject:
description: 'Unique ID for the collection of statements about an artifact'
required: true
payload:
payload-file:
description: 'The payload file to be registered on the SCITT Service (eg: SBOM, Scan Result, Attestation)'
required: true
content-type:
description: 'The payload content type (iana mediaType) to be registered on the SCITT Service (eg: application/spdx+json, application/vnd.cyclonedx+json, Scan Result, Attestation)'
required: true
payload-location:
description: 'Optional location the content of the payload may be stored.'
required: false
receipt-file:
description: 'The filename to save the cbor receipt'
required: false
default: 'receipt.cbor'
signed-statement-file:
description: 'File representing the signed SCITT Statement that will be registered on SCITT.'
required: false
default: 'signed-statement.cbor'
receipt-file:
description: 'The file to save the cbor receipt'
skip-receipt:
description: 'To skip receipt retrieval, set to 1'
required: false
default: 'receipt.cbor'
default: '0'
subject:
description: 'Unique ID for the collection of statements about an artifact'
required: true
outputs:
token: # id of output
description: 'the token used to authenticate'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.content-type }}
- ${{ inputs.datatrails-client_id }}
- ${{ inputs.datatrails-secret }}
- ${{ inputs.subject }}
- ${{ inputs.payload }}
- ${{ inputs.content-type }}
- ${{ inputs.signed-statement-file }}
- ${{ inputs.payload-file }}
- ${{ inputs.payload-location}}
- ${{ inputs.receipt-file }}
- ${{ inputs.signed-statement-file }}
- ${{ inputs.skip-receipt }}
- ${{ inputs.subject }}
66 changes: 39 additions & 27 deletions scitt-scripts/check_operation_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import os
import argparse
import logging
import sys

from time import sleep as time_sleep

Expand All @@ -10,7 +12,7 @@

# all timeouts and durations are in seconds
REQUEST_TIMEOUT = 30
POLL_TIMEOUT = 360
POLL_TIMEOUT = 60
POLL_INTERVAL = 10


Expand Down Expand Up @@ -39,10 +41,7 @@ def get_operation_status(operation_id: str, headers: dict) -> dict:

while True:
response = requests.get(url, timeout=30, headers=headers)
# print("***response:", flush=True)
# print(response, flush=True)
# print(response.json, flush=True)
# print("***response:", flush=True)

if response.status_code == 200:
break
elif response.status_code == 400:
Expand All @@ -53,34 +52,41 @@ def get_operation_status(operation_id: str, headers: dict) -> dict:
return response.json()


def poll_operation_status(operation_id: str, headers: dict) -> str:
def poll_operation_status(
operation_id: str, headers: dict, logger: logging.Logger
) -> str:
"""
polls for the operation status to be 'succeeded'.
"""

poll_attempts: int = int(POLL_TIMEOUT / POLL_INTERVAL)

logger.info("starting to poll for operation status 'succeeded'")

for _ in range(poll_attempts):
operation_status = get_operation_status(operation_id, headers)
# print("***operation_status:", flush=True)
# print(operation_status, flush=True)
# print("***operation_status:", flush=True)

# pylint: disable=fixme
# TODO: ensure get_operation_status handles error cases from the rest request
if "status" in operation_status and operation_status["status"] == "succeeded":
return operation_status["entryID"]
try:
operation_status = get_operation_status(operation_id, headers)

# pylint: disable=fixme
# TODO: ensure get_operation_status handles error cases from the rest request
if (
"status" in operation_status
and operation_status["status"] == "succeeded"
):
return operation_status["entryID"]

except requests.HTTPError as e:
logger.debug("failed getting operation status, error: %s", e)

time_sleep(POLL_INTERVAL)

raise TimeoutError("signed statement not registered within polling duration.")
raise TimeoutError("signed statement not registered within polling duration")


def main():
"""Polls for the signed statement to be registered"""

# print("*****in-main*****", flush=True)

parser = argparse.ArgumentParser(
description="Polls for the signed statement to be registered"
)
Expand Down Expand Up @@ -108,21 +114,27 @@ def main():
default=default_token_file_name,
)

# log level
parser.add_argument(
"--log-level",
type=str,
help="log level. for any individual poll errors use DEBUG, defaults to WARNING",
default="WARNING",
)

args = parser.parse_args()

# print("args.token_file_name:", flush=True)
# print(args.token_file_name, flush=True)
logger = logging.getLogger("check operation status")
logging.basicConfig(level=logging.getLevelName(args.log_level))

headers = get_token_from_file(args.token_file_name)
# print("headers:", flush=True)
# print(headers, flush=True)

# print("operation_id:", flush=True)
# print(args.operation_id, flush=True)

entry_id = poll_operation_status(args.operation_id, headers)
print(entry_id, flush=True)

try:
entry_id = poll_operation_status(args.operation_id, headers, logger)
print(entry_id)
except TimeoutError as e:
print(e, file=sys.stderr)
sys.exit(1)

if __name__ == "__main__":
main()
Loading