mirror of https://github.com/actions/toolkit
Merge pull request #1865 from actions/bdehamer/multi-subject
`@actions/attest`: Support multi-subject attestationspull/1870/head
commit
43ce96d373
|
@ -32,8 +32,7 @@ async function run() {
|
||||||
const ghToken = core.getInput('gh-token');
|
const ghToken = core.getInput('gh-token');
|
||||||
|
|
||||||
const attestation = await attest({
|
const attestation = await attest({
|
||||||
subjectName: 'my-artifact-name',
|
subjects: [{name: 'my-artifact-name', digest: { 'sha256': '36ab4667...'}}],
|
||||||
subjectDigest: { 'sha256': '36ab4667...'},
|
|
||||||
predicateType: 'https://in-toto.io/attestation/release',
|
predicateType: 'https://in-toto.io/attestation/release',
|
||||||
predicate: { . . . },
|
predicate: { . . . },
|
||||||
token: ghToken
|
token: ghToken
|
||||||
|
@ -49,11 +48,12 @@ The `attest` function supports the following options:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export type AttestOptions = {
|
export type AttestOptions = {
|
||||||
// The name of the subject to be attested.
|
// Deprecated. Use 'subjects' instead.
|
||||||
subjectName: string
|
subjectName?: string
|
||||||
// The digest of the subject to be attested. Should be a map of digest
|
// Deprecated. Use 'subjects' instead.
|
||||||
// algorithms to their hex-encoded values.
|
subjectDigest?: Record<string, string>
|
||||||
subjectDigest: Record<string, string>
|
// Collection of subjects to be attested
|
||||||
|
subjects?: Subject[]
|
||||||
// URI identifying the content type of the predicate being attested.
|
// URI identifying the content type of the predicate being attested.
|
||||||
predicateType: string
|
predicateType: string
|
||||||
// Predicate to be attested.
|
// Predicate to be attested.
|
||||||
|
@ -68,6 +68,13 @@ export type AttestOptions = {
|
||||||
// Whether to skip writing the attestation to the GH attestations API.
|
// Whether to skip writing the attestation to the GH attestations API.
|
||||||
skipWrite?: boolean
|
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`
|
### `attestProvenance`
|
||||||
|
@ -105,12 +112,13 @@ The `attestProvenance` function supports the following options:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export type AttestProvenanceOptions = {
|
export type AttestProvenanceOptions = {
|
||||||
// The name of the subject to be attested.
|
// Deprecated. Use 'subjects' instead.
|
||||||
subjectName: string
|
subjectName?: string
|
||||||
// The digest of the subject to be attested. Should be a map of digest
|
// Deprecated. Use 'subjects' instead.
|
||||||
// algorithms to their hex-encoded values.
|
subjectDigest?: Record<string, string>
|
||||||
subjectDigest: Record<string, string>
|
// Collection of subjects to be attested
|
||||||
// GitHub token for writing attestations.
|
subjects?: Subject[]
|
||||||
|
// URI identifying the content type of the predicate being attested.
|
||||||
token: string
|
token: string
|
||||||
// Sigstore instance to use for signing. Must be one of "public-good" or
|
// Sigstore instance to use for signing. Must be one of "public-good" or
|
||||||
// "github".
|
// "github".
|
||||||
|
|
|
@ -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'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -17,7 +17,7 @@ describe('buildIntotoStatement', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('returns an intoto statement', () => {
|
it('returns an intoto statement', () => {
|
||||||
const statement = buildIntotoStatement(subject, predicate)
|
const statement = buildIntotoStatement([subject], predicate)
|
||||||
expect(statement).toMatchSnapshot()
|
expect(statement).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -115,8 +115,7 @@ describe('provenance functions', () => {
|
||||||
describe('when the sigstore instance is explicitly set', () => {
|
describe('when the sigstore instance is explicitly set', () => {
|
||||||
it('attests provenance', async () => {
|
it('attests provenance', async () => {
|
||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
subjectDigest,
|
|
||||||
token: 'token',
|
token: 'token',
|
||||||
sigstore: 'github'
|
sigstore: 'github'
|
||||||
})
|
})
|
||||||
|
@ -143,8 +142,7 @@ describe('provenance functions', () => {
|
||||||
|
|
||||||
it('attests provenance', async () => {
|
it('attests provenance', async () => {
|
||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
subjectDigest,
|
|
||||||
token: 'token'
|
token: 'token'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -178,8 +176,7 @@ describe('provenance functions', () => {
|
||||||
describe('when the sigstore instance is explicitly set', () => {
|
describe('when the sigstore instance is explicitly set', () => {
|
||||||
it('attests provenance', async () => {
|
it('attests provenance', async () => {
|
||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
subjectDigest,
|
|
||||||
token: 'token',
|
token: 'token',
|
||||||
sigstore: 'public-good'
|
sigstore: 'public-good'
|
||||||
})
|
})
|
||||||
|
@ -206,8 +203,7 @@ describe('provenance functions', () => {
|
||||||
|
|
||||||
it('attests provenance', async () => {
|
it('attests provenance', async () => {
|
||||||
const attestation = await attestProvenance({
|
const attestation = await attestProvenance({
|
||||||
subjectName,
|
subjects: [{name: subjectName, digest: subjectDigest}],
|
||||||
subjectDigest,
|
|
||||||
token: 'token'
|
token: 'token'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,16 @@ const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
|
||||||
* Options for attesting a subject / predicate.
|
* Options for attesting a subject / predicate.
|
||||||
*/
|
*/
|
||||||
export type AttestOptions = {
|
export type AttestOptions = {
|
||||||
// The name of the subject to be attested.
|
/**
|
||||||
subjectName: string
|
* @deprecated Use `subjects` instead.
|
||||||
// The digest of the subject to be attested. Should be a map of digest
|
**/
|
||||||
// algorithms to their hex-encoded values.
|
subjectName?: string
|
||||||
subjectDigest: Record<string, string>
|
/**
|
||||||
|
* @deprecated Use `subjects` instead.
|
||||||
|
**/
|
||||||
|
subjectDigest?: Record<string, string>
|
||||||
|
// Subjects to be attested.
|
||||||
|
subjects?: Subject[]
|
||||||
// Content type of the predicate being attested.
|
// Content type of the predicate being attested.
|
||||||
predicateType: string
|
predicateType: string
|
||||||
// Predicate to be attested.
|
// Predicate to be attested.
|
||||||
|
@ -42,15 +47,24 @@ export type AttestOptions = {
|
||||||
* @returns A promise that resolves to the attestation.
|
* @returns A promise that resolves to the attestation.
|
||||||
*/
|
*/
|
||||||
export async function attest(options: AttestOptions): Promise<Attestation> {
|
export async function attest(options: AttestOptions): Promise<Attestation> {
|
||||||
const subject: Subject = {
|
let subjects: Subject[]
|
||||||
name: options.subjectName,
|
|
||||||
digest: options.subjectDigest
|
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 = {
|
const predicate: Predicate = {
|
||||||
type: options.predicateType,
|
type: options.predicateType,
|
||||||
params: options.predicate
|
params: options.predicate
|
||||||
}
|
}
|
||||||
const statement = buildIntotoStatement(subject, predicate)
|
|
||||||
|
const statement = buildIntotoStatement(subjects, predicate)
|
||||||
|
|
||||||
// Sign the provenance statement
|
// Sign the provenance statement
|
||||||
const payload: Payload = {
|
const payload: Payload = {
|
||||||
|
|
|
@ -20,12 +20,12 @@ export type InTotoStatement = {
|
||||||
* @returns The constructed in-toto statement.
|
* @returns The constructed in-toto statement.
|
||||||
*/
|
*/
|
||||||
export const buildIntotoStatement = (
|
export const buildIntotoStatement = (
|
||||||
subject: Subject,
|
subjects: Subject[],
|
||||||
predicate: Predicate
|
predicate: Predicate
|
||||||
): InTotoStatement => {
|
): InTotoStatement => {
|
||||||
return {
|
return {
|
||||||
_type: INTOTO_STATEMENT_V1_TYPE,
|
_type: INTOTO_STATEMENT_V1_TYPE,
|
||||||
subject: [subject],
|
subject: subjects,
|
||||||
predicateType: predicate.type,
|
predicateType: predicate.type,
|
||||||
predicate: predicate.params
|
predicate: predicate.params
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue