1
0
Fork 0

Merge pull request #1865 from actions/bdehamer/multi-subject

`@actions/attest`: Support multi-subject attestations
pull/1870/head
Brian DeHamer 2024-11-01 09:33:11 -07:00 committed by GitHub
commit 43ce96d373
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 67 additions and 33 deletions

View File

@ -32,8 +32,7 @@ async function run() {
const ghToken = core.getInput('gh-token');
const attestation = await attest({
subjectName: 'my-artifact-name',
subjectDigest: { 'sha256': '36ab4667...'},
subjects: [{name: 'my-artifact-name', digest: { 'sha256': '36ab4667...'}}],
predicateType: 'https://in-toto.io/attestation/release',
predicate: { . . . },
token: ghToken
@ -49,11 +48,12 @@ The `attest` function supports the following options:
```typescript
export type AttestOptions = {
// The name of the subject to be attested.
subjectName: string
// The digest of the subject to be attested. Should be a map of digest
// algorithms to their hex-encoded values.
subjectDigest: Record<string, string>
// Deprecated. Use 'subjects' instead.
subjectName?: string
// Deprecated. Use 'subjects' instead.
subjectDigest?: Record<string, string>
// Collection of subjects to be attested
subjects?: Subject[]
// URI identifying the content type of the predicate being attested.
predicateType: string
// Predicate to be attested.
@ -68,6 +68,13 @@ export type AttestOptions = {
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
}
export type Subject = {
// Name of the subject.
name: string
// Digests of the subject. Should be a map of digest algorithms to their hex-encoded values.
digest: Record<string, string>
}
```
### `attestProvenance`
@ -105,12 +112,13 @@ The `attestProvenance` function supports the following options:
```typescript
export type AttestProvenanceOptions = {
// The name of the subject to be attested.
subjectName: string
// The digest of the subject to be attested. Should be a map of digest
// algorithms to their hex-encoded values.
subjectDigest: Record<string, string>
// GitHub token for writing attestations.
// Deprecated. Use 'subjects' instead.
subjectName?: string
// Deprecated. Use 'subjects' instead.
subjectDigest?: Record<string, string>
// Collection of subjects to be attested
subjects?: Subject[]
// URI identifying the content type of the predicate being attested.
token: string
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".

View File

@ -0,0 +1,16 @@
import {attest} from '../src/attest'
describe('attest', () => {
describe('when no subject information is provided', () => {
it('throws an error', async () => {
const options = {
predicateType: 'foo',
predicate: {bar: 'baz'},
token: 'token'
}
expect(attest(options)).rejects.toThrowError(
'Must provide either subjectName and subjectDigest or subjects'
)
})
})
})

View File

@ -17,7 +17,7 @@ describe('buildIntotoStatement', () => {
}
it('returns an intoto statement', () => {
const statement = buildIntotoStatement(subject, predicate)
const statement = buildIntotoStatement([subject], predicate)
expect(statement).toMatchSnapshot()
})
})

View File

@ -115,8 +115,7 @@ describe('provenance functions', () => {
describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
subjects: [{name: subjectName, digest: subjectDigest}],
token: 'token',
sigstore: 'github'
})
@ -143,8 +142,7 @@ describe('provenance functions', () => {
it('attests provenance', async () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
subjects: [{name: subjectName, digest: subjectDigest}],
token: 'token'
})
@ -178,8 +176,7 @@ describe('provenance functions', () => {
describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
subjects: [{name: subjectName, digest: subjectDigest}],
token: 'token',
sigstore: 'public-good'
})
@ -206,8 +203,7 @@ describe('provenance functions', () => {
it('attests provenance', async () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
subjects: [{name: subjectName, digest: subjectDigest}],
token: 'token'
})

View File

@ -14,11 +14,16 @@ const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
* Options for attesting a subject / predicate.
*/
export type AttestOptions = {
// The name of the subject to be attested.
subjectName: string
// The digest of the subject to be attested. Should be a map of digest
// algorithms to their hex-encoded values.
subjectDigest: Record<string, string>
/**
* @deprecated Use `subjects` instead.
**/
subjectName?: string
/**
* @deprecated Use `subjects` instead.
**/
subjectDigest?: Record<string, string>
// Subjects to be attested.
subjects?: Subject[]
// Content type of the predicate being attested.
predicateType: string
// Predicate to be attested.
@ -42,15 +47,24 @@ export type AttestOptions = {
* @returns A promise that resolves to the attestation.
*/
export async function attest(options: AttestOptions): Promise<Attestation> {
const subject: Subject = {
name: options.subjectName,
digest: options.subjectDigest
let subjects: Subject[]
if (options.subjects) {
subjects = options.subjects
} else if (options.subjectName && options.subjectDigest) {
subjects = [{name: options.subjectName, digest: options.subjectDigest}]
} else {
throw new Error(
'Must provide either subjectName and subjectDigest or subjects'
)
}
const predicate: Predicate = {
type: options.predicateType,
params: options.predicate
}
const statement = buildIntotoStatement(subject, predicate)
const statement = buildIntotoStatement(subjects, predicate)
// Sign the provenance statement
const payload: Payload = {

View File

@ -20,12 +20,12 @@ export type InTotoStatement = {
* @returns The constructed in-toto statement.
*/
export const buildIntotoStatement = (
subject: Subject,
subjects: Subject[],
predicate: Predicate
): InTotoStatement => {
return {
_type: INTOTO_STATEMENT_V1_TYPE,
subject: [subject],
subject: subjects,
predicateType: predicate.type,
predicate: predicate.params
}