Skip to content

Commit

Permalink
Change to Signed Statements for Hashed Payloads (#5)
Browse files Browse the repository at this point in the history
* Add receipt retreival, cleanup debugging info

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Fix token file reference

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add sample for the SCITT Action

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add skip-receipt

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add skip-receipt

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Fix parameter reference for skip-receipt

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add file error handling

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Fix parameter reference for skip-receipt

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Test curl errors

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Test curl errors

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Change to signed statements of hashed payloads

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Merge hashed payloads to digicert signed statements

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Update numbered to named variables

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Fix param ordering

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Enable debugging

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Name parameters

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Name parameters

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add receipt handling

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Update HEADER parameters for private use

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Readme sample updates

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Comment out debugging output

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Change return to exit for error handling

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add more error handling

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add COSE Type

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Fix call to generate a receipt

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add PREVIEW heading

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add error handling for check-operation-status

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Add error handling for check-operation-status

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* Remove duplicative lines

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

* PR Feedback

Signed-off-by: steve lasker <stevenlasker@hotmail.com>

---------

Signed-off-by: steve lasker <stevenlasker@hotmail.com>
  • Loading branch information
SteveLasker authored Jul 20, 2024
1 parent 2f8020e commit 08d08ee
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 110 deletions.
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

0 comments on commit 08d08ee

Please sign in to comment.