Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion .github/workflows/build-attest-dist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- 'attest/index.js'
- 'attest/package.json'
- 'attest/package-lock.json'
- '.github/workflows/build-attest-dist.yaml'
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -50,6 +51,8 @@ jobs:
if: github.event_name == 'pull_request'
env:
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BASE_REPO: ${{ github.repository }}
run: |
# SECURITY: only act if the rebuild produced exactly one
Expand Down Expand Up @@ -88,4 +91,9 @@ jobs:
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add attest/dist/index.js
git commit -m "chore(attest): rebuild dist/index.js"
git push
# The checkout is the PR head SHA, a detached HEAD, so name the
# PR branch. The lease makes the push land only while the branch
# still points at the SHA this run built from; if it moved in any
# direction or was deleted, the push is rejected.
git push --force-with-lease="refs/heads/$PR_HEAD_REF:$PR_HEAD_SHA" \
origin "HEAD:refs/heads/$PR_HEAD_REF"
36 changes: 30 additions & 6 deletions attest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,41 @@ If your environment cannot submit to the public Sigstore transparency
log (e.g. customers in the USA / EU with data-residency requirements),
use one of:

- **KMS signing**: `--kms vault://<key-id>` (HashiCorp Vault transit).
The signature stays in your control. Vault transit is the only KMS
backend implemented today.
- **KMS signing**: `kms: vault://<key-name>` (HashiCorp Vault transit).
The private key never leaves Vault; cimon sends the digest and receives
the signature. Vault transit is the only KMS backend implemented today,
and it requires cimon v1.0.28 or later.
- **Keyed signing with checked-in private key**: `sign-key: private-key.pem`.
- **Private Sigstore**: deploy your own Fulcio + Rekor and pass
`fulcio-server-url` + `rekor-server-url`.

See the inputs section below for the full list.

### KMS signing with HashiCorp Vault transit

cimon reads the Vault connection from the standard Vault environment
variables (`VAULT_ADDR`, `VAULT_TOKEN`, and optionally `VAULT_NAMESPACE`
and `VAULT_CACERT`). The transit engine must be mounted at `transit/`, and
the key must support signing (for example `ecdsa-p256` or `rsa-3072`):

```yaml
- uses: cycodelabs/cimon-action/attest@v1
env:
VAULT_ADDR: https://vault.example.com:8200
VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }}
with:
subjects: dist\my-app.msi
kms: vault://cimon-signing
fail-on-error: true
```

Verifiers need the key's public half, which Vault returns from
`transit/keys/<key-name>`:
`cimon attest verify --key public-key.pem --signed-prov provenance.intoto.jsonl.sig`.

### Timestamping key-based signatures

A signature made with `sign-key` carries no time anchor unless you add an
A signature made with `sign-key` or `kms` carries no time anchor unless you add an
RFC 3161 timestamp, which lets a verifier show it was made before the
key was rotated or revoked. Request one explicitly; `timestamp-server-url`
accepts a Sigstore timestamp authority or a standard RFC 3161 service:
Expand Down Expand Up @@ -89,12 +112,13 @@ releases accept these inputs and produce no timestamp.
| `source-path` | Path to the source checkout recorded in the attestation. Unset means automatic discovery (subject paths, run-id directory, workspace). Requires cimon v1.0.24 or later | — |
| `skip-source-tree-check` | Skip verifying the source tree matches the recorded commit | `false` |
| `sign-key` | Path to a private ECDSA/RSA/ED25519 PEM key | — |
| `kms` | KMS key URI for signing. HashiCorp Vault transit: `vault://<key-name>`, configured through `VAULT_ADDR` and `VAULT_TOKEN`. Requires cimon v1.0.28 or later | — |
| `keyless` | Use keyless (Sigstore) signing | `false` |
| `tlog-upload` | **Keyless only.** Upload signature to Rekor transparency log | `true` |
| `include-timestamp` | Request an RFC 3161 timestamp and embed it in the signature. Applies to `keyless` and `sign-key` signing | `false` |
| `include-timestamp` | Request an RFC 3161 timestamp and embed it in the signature. Applies to `keyless`, `sign-key` and `kms` signing | `false` |
| `fulcio-server-url` | **Keyless only.** Fulcio server URL | `https://fulcio.sigstore.dev` |
| `rekor-server-url` | **Keyless only.** Rekor server URL | `https://rekor.sigstore.dev` |
| `timestamp-server-url` | RFC 3161 timestamp server URL: a Sigstore timestamp authority or a standard RFC 3161 service. With `sign-key`, forwarded only when `include-timestamp: true` | — |
| `timestamp-server-url` | RFC 3161 timestamp server URL: a Sigstore timestamp authority or a standard RFC 3161 service. With `sign-key` or `kms`, forwarded only when `include-timestamp: true` | — |
| `allow-submit-data-to-public-sigstore` | **Keyless only.** Required when using public Sigstore | `false` |
| `provenance-output` | Path for unsigned provenance | `provenance.intoto.jsonl` |
| `signed-provenance-output` | Path for signed provenance | `provenance.intoto.jsonl.sig` |
Expand Down
8 changes: 6 additions & 2 deletions attest/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ inputs:
description: Input path to a private ECDSA/RSA/ED25519 key used to sign provenance statement
required: false
default: ''
kms:
description: 'KMS key used to sign provenance statement, as a provider URI. HashiCorp Vault transit: vault://<key-name>, with the Vault address and token in the VAULT_ADDR and VAULT_TOKEN environment variables. Requires cimon v1.0.28 or later.'
required: false
default: ''
Comment thread
ronens88 marked this conversation as resolved.
keyless:
description: Allow keyless signature if no signing key is provided
required: false
Expand All @@ -52,7 +56,7 @@ inputs:
required: false
default: 'true'
include-timestamp:
description: 'Request an RFC 3161 timestamp of the artifact signature from a timestamping authority. Applies to keyless and sign-key signing.'
description: 'Request an RFC 3161 timestamp of the artifact signature from a timestamping authority. Applies to keyless, sign-key and kms signing.'
required: false
default: 'false'
fulcio-server-url:
Expand All @@ -64,7 +68,7 @@ inputs:
required: false
default: 'https://rekor.sigstore.dev'
timestamp-server-url:
description: 'RFC 3161 timestamp server URL: a Sigstore timestamp authority or a standard RFC 3161 service. With sign-key, forwarded only when include-timestamp is true.'
description: 'RFC 3161 timestamp server URL: a Sigstore timestamp authority or a standard RFC 3161 service. With sign-key or kms, forwarded only when include-timestamp is true.'
required: false
allow-submit-data-to-public-sigstore:
description: 'Keyless signing only: agree to submit data to an immutable public transparency log (needed for public Sigstore). Ignored unless keyless is true.'
Expand Down
10 changes: 7 additions & 3 deletions attest/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127691,6 +127691,7 @@ function getActionConfig() {
subjects: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('subjects'),
imageRef: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('image-ref'),
signKey: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('sign-key'),
kms: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getInput('kms'),
keyless: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('keyless'),
tlogUpload: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('tlog-upload'),
includeTimestamp: _actions_core__WEBPACK_IMPORTED_MODULE_0__.getBooleanInput('include-timestamp'),
Expand Down Expand Up @@ -127793,6 +127794,7 @@ async function run(config) {
if (config.attest.signedProvenanceOutput !== '')
args.push('--output-signed-prov', config.attest.signedProvenanceOutput);
if (config.attest.signKey !== '') args.push('--key', config.attest.signKey);
if (config.attest.kms !== '') args.push('--kms', config.attest.kms);
if (config.attest.sourcePath !== '')
args.push('--source-path', config.attest.sourcePath);
if (config.attest.skipSourceTreeCheck)
Expand All @@ -127804,6 +127806,8 @@ async function run(config) {
if (config.cimon.logLevel !== '')
args.push('--log-level', config.cimon.logLevel);
if (config.report.reportJobSummary) args.push('--report-job-summary');
const keyBasedSigning =
config.attest.signKey !== '' || config.attest.kms !== '';
if (config.attest.keyless) {
args.push('--keyless');

Expand All @@ -127824,8 +127828,8 @@ async function run(config) {
`--timestamp-server-url=${config.attest.timestampServerUrl}`
);
}
} else if (config.attest.signKey !== '' && config.attest.includeTimestamp) {
// Key-based signing only timestamps on an explicit request, so the
} else if (keyBasedSigning && config.attest.includeTimestamp) {
// Key and KMS signing only timestamp on an explicit request, so the
// inputs are forwarded only when include-timestamp is true. Older
// cimon releases accept and ignore them.
args.push('--include-timestamp=true');
Expand Down Expand Up @@ -127855,7 +127859,7 @@ async function run(config) {
path__WEBPACK_IMPORTED_MODULE_5__.dirname(config.attest.provenanceOutput),
{ continueOnError: true }
);
if (config.attest.signKey !== '') {
if (keyBasedSigning) {
client.uploadArtifact(
`signed-provenance-${jobId}-${randomSuffix}`,
[config.attest.signedProvenanceOutput],
Expand Down
10 changes: 7 additions & 3 deletions attest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ function getActionConfig() {
subjects: core.getInput('subjects'),
imageRef: core.getInput('image-ref'),
signKey: core.getInput('sign-key'),
kms: core.getInput('kms'),
keyless: core.getBooleanInput('keyless'),
tlogUpload: core.getBooleanInput('tlog-upload'),
includeTimestamp: core.getBooleanInput('include-timestamp'),
Expand Down Expand Up @@ -233,6 +234,7 @@ async function run(config) {
if (config.attest.signedProvenanceOutput !== '')
args.push('--output-signed-prov', config.attest.signedProvenanceOutput);
if (config.attest.signKey !== '') args.push('--key', config.attest.signKey);
if (config.attest.kms !== '') args.push('--kms', config.attest.kms);
if (config.attest.sourcePath !== '')
args.push('--source-path', config.attest.sourcePath);
if (config.attest.skipSourceTreeCheck)
Expand All @@ -244,6 +246,8 @@ async function run(config) {
if (config.cimon.logLevel !== '')
args.push('--log-level', config.cimon.logLevel);
if (config.report.reportJobSummary) args.push('--report-job-summary');
const keyBasedSigning =
config.attest.signKey !== '' || config.attest.kms !== '';
if (config.attest.keyless) {
args.push('--keyless');

Expand All @@ -264,8 +268,8 @@ async function run(config) {
`--timestamp-server-url=${config.attest.timestampServerUrl}`
);
}
} else if (config.attest.signKey !== '' && config.attest.includeTimestamp) {
// Key-based signing only timestamps on an explicit request, so the
} else if (keyBasedSigning && config.attest.includeTimestamp) {
// Key and KMS signing only timestamp on an explicit request, so the
// inputs are forwarded only when include-timestamp is true. Older
// cimon releases accept and ignore them.
args.push('--include-timestamp=true');
Expand Down Expand Up @@ -295,7 +299,7 @@ async function run(config) {
path.dirname(config.attest.provenanceOutput),
{ continueOnError: true }
);
if (config.attest.signKey !== '') {
if (keyBasedSigning) {
client.uploadArtifact(
`signed-provenance-${jobId}-${randomSuffix}`,
[config.attest.signedProvenanceOutput],
Expand Down
Loading