Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fae568887 | ||
|
|
38bcf9b1c5 | ||
|
|
f03e04cc3f | ||
|
|
3fd15bdad4 | ||
|
|
f642f6cafe | ||
|
|
67164a6c7a | ||
|
|
e6c7a2a1f2 | ||
|
|
d8029d4690 | ||
|
|
bfa7e6911b | ||
|
|
d94b522220 | ||
|
|
63d2e98e26 | ||
|
|
94d0d43131 | ||
|
|
65e34a8aa7 |
@@ -1,4 +0,0 @@
|
||||
lib/
|
||||
dist/
|
||||
node_modules/
|
||||
coverage/
|
||||
84
.github/linters/.eslintrc.yml
vendored
84
.github/linters/.eslintrc.yml
vendored
@@ -1,84 +0,0 @@
|
||||
env:
|
||||
node: true
|
||||
es6: true
|
||||
jest: true
|
||||
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
|
||||
ignorePatterns:
|
||||
- '!.*'
|
||||
- '**/node_modules/.*'
|
||||
- '**/dist/.*'
|
||||
- '**/coverage/.*'
|
||||
- '*.json'
|
||||
|
||||
parser: '@typescript-eslint/parser'
|
||||
|
||||
parserOptions:
|
||||
ecmaVersion: 2023
|
||||
sourceType: module
|
||||
project:
|
||||
- './.github/linters/tsconfig.json'
|
||||
- './tsconfig.json'
|
||||
|
||||
plugins:
|
||||
- jest
|
||||
- '@typescript-eslint'
|
||||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:@typescript-eslint/eslint-recommended
|
||||
- plugin:@typescript-eslint/recommended
|
||||
- plugin:github/recommended
|
||||
- plugin:jest/recommended
|
||||
|
||||
rules:
|
||||
{
|
||||
'camelcase': 'off',
|
||||
'eslint-comments/no-use': 'off',
|
||||
'eslint-comments/no-unused-disable': 'off',
|
||||
'i18n-text/no-en': 'off',
|
||||
'import/no-namespace': 'off',
|
||||
'import/no-unresolved': ['error', { 'ignore': ['csv-parse/sync'] }],
|
||||
'no-console': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'prettier/prettier': 'error',
|
||||
'semi': 'off',
|
||||
'@typescript-eslint/array-type': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
'@typescript-eslint/explicit-member-accessibility':
|
||||
['error', { 'accessibility': 'no-public' }],
|
||||
'@typescript-eslint/explicit-function-return-type':
|
||||
['error', { 'allowExpressions': true }],
|
||||
'@typescript-eslint/func-call-spacing': ['error', 'never'],
|
||||
'@typescript-eslint/no-array-constructor': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-extraneous-class': 'error',
|
||||
'@typescript-eslint/no-for-in-array': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/no-misused-new': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/no-useless-constructor': 'error',
|
||||
'@typescript-eslint/no-var-requires': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'warn',
|
||||
'@typescript-eslint/prefer-function-type': 'warn',
|
||||
'@typescript-eslint/prefer-includes': 'error',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
|
||||
'@typescript-eslint/promise-function-async': 'error',
|
||||
'@typescript-eslint/require-array-sort-compare': 'error',
|
||||
'@typescript-eslint/restrict-plus-operands': 'error',
|
||||
'@typescript-eslint/semi': ['error', 'never'],
|
||||
'@typescript-eslint/space-before-function-paren': 'off',
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
'@typescript-eslint/unbound-method': 'error'
|
||||
}
|
||||
93
.github/linters/eslint.config.mjs
vendored
Normal file
93
.github/linters/eslint.config.mjs
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import eslint from '@eslint/js'
|
||||
import importplugin from 'eslint-plugin-import'
|
||||
import jestplugin from 'eslint-plugin-jest'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
// Ignore non-project files
|
||||
{
|
||||
name: 'ignore',
|
||||
ignores: ['.github', 'dist', 'coverage', '**/*.json', 'jest.setup.js']
|
||||
},
|
||||
// Use recommended rules from ESLint, TypeScript, and other plugins
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
jestplugin.configs['flat/recommended'],
|
||||
importplugin.flatConfigs.recommended,
|
||||
importplugin.flatConfigs.typescript,
|
||||
// Override some rules
|
||||
{
|
||||
name: 'project-settings',
|
||||
languageOptions: {
|
||||
ecmaVersion: 2023,
|
||||
parserOptions: {
|
||||
project: ['./.github/linters/tsconfig.json', './tsconfig.json']
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
// eslint rules
|
||||
eqeqeq: ['error', 'smart'],
|
||||
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
|
||||
'no-console': 'off',
|
||||
'no-implicit-globals': 'error',
|
||||
'no-inner-declarations': 'error',
|
||||
'no-invalid-this': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-shadow': 'error',
|
||||
'no-useless-concat': 'error',
|
||||
'object-shorthand': ['error', 'always', { avoidQuotes: true }],
|
||||
'one-var': ['error', 'never'],
|
||||
'prefer-template': 'error',
|
||||
|
||||
// typescript-eslint rules
|
||||
'@typescript-eslint/array-type': 'error',
|
||||
'@typescript-eslint/consistent-type-assertions': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': [
|
||||
'error',
|
||||
{ allowExpressions: true }
|
||||
],
|
||||
'@typescript-eslint/explicit-member-accessibility': [
|
||||
'error',
|
||||
{ accessibility: 'no-public' }
|
||||
],
|
||||
'@typescript-eslint/no-extraneous-class': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
'@typescript-eslint/no-unnecessary-qualifier': 'error',
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-useless-constructor': 'error',
|
||||
'@typescript-eslint/prefer-for-of': 'warn',
|
||||
'@typescript-eslint/prefer-function-type': 'warn',
|
||||
'@typescript-eslint/prefer-includes': 'error',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
|
||||
'@typescript-eslint/promise-function-async': 'error',
|
||||
'@typescript-eslint/require-array-sort-compare': 'error',
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
|
||||
// eslint-plugin-import rules
|
||||
'import/extensions': 'error',
|
||||
'import/first': 'error',
|
||||
'import/no-absolute-path': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-deprecated': 'warn',
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-namespace': 'off',
|
||||
'import/no-unresolved': ['error', { ignore: ['csv-parse/sync'] }],
|
||||
'import/no-anonymous-default-export': [
|
||||
'error',
|
||||
{
|
||||
allowAnonymousClass: false,
|
||||
allowAnonymousFunction: false,
|
||||
allowArray: true,
|
||||
allowArrowFunction: false,
|
||||
allowLiteral: true,
|
||||
allowObject: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
86
README.md
86
README.md
@@ -24,6 +24,16 @@ CLI][5].
|
||||
See [Using artifact attestations to establish provenance for builds][9] for more
|
||||
information on artifact attestations.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
> [!NOTE]
|
||||
> Artifact attestations are available in public repositories for all
|
||||
> current GitHub plans. They are not available on legacy plans, such as Bronze,
|
||||
> Silver, or Gold. If you are on a GitHub Free, GitHub Pro, or GitHub Team plan,
|
||||
> artifact attestations are only available for public repositories. To use
|
||||
> artifact attestations in private or internal repositories, you must be on a
|
||||
> GitHub Enterprise Cloud plan.
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Usage
|
||||
|
||||
Within the GitHub Actions workflow which builds some artifact you would like to
|
||||
@@ -44,7 +54,7 @@ attest:
|
||||
1. Add the following to your workflow after your artifact has been built:
|
||||
|
||||
```yaml
|
||||
- uses: actions/attest@v1
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
subject-path: '<PATH TO ARTIFACT>'
|
||||
predicate-type: '<PREDICATE URI>'
|
||||
@@ -61,23 +71,28 @@ attest:
|
||||
See [action.yml](action.yml)
|
||||
|
||||
```yaml
|
||||
- uses: actions/attest@v1
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
# Path to the artifact serving as the subject of the attestation. Must
|
||||
# specify exactly one of "subject-path" or "subject-digest". May contain
|
||||
# a glob pattern or list of paths (total subject count cannot exceed 1024).
|
||||
# specify exactly one of "subject-path", "subject-digest", or
|
||||
# "subject-checksums". May contain a glob pattern or list of paths
|
||||
# (total subject count cannot exceed 1024).
|
||||
subject-path:
|
||||
|
||||
# SHA256 digest of the subject for the attestation. Must be in the form
|
||||
# "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
|
||||
# of "subject-path" or "subject-digest".
|
||||
# of "subject-path", "subject-digest", or "subject-checksums".
|
||||
subject-digest:
|
||||
|
||||
# Subject name as it should appear in the attestation. Required unless
|
||||
# "subject-path" is specified, in which case it will be inferred from the
|
||||
# path.
|
||||
# Subject name as it should appear in the attestation. Required when
|
||||
# identifying the subject with the "subject-digest" input.
|
||||
subject-name:
|
||||
|
||||
# Path to checksums file containing digest and name of subjects for
|
||||
# attestation. Must specify exactly one of "subject-path", "subject-digest",
|
||||
# or "subject-checksums".
|
||||
subject-checksums:
|
||||
|
||||
# URI identifying the type of the predicate.
|
||||
predicate-type:
|
||||
|
||||
@@ -109,9 +124,11 @@ See [action.yml](action.yml)
|
||||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
|
||||
| Name | Description | Example |
|
||||
| ------------- | -------------------------------------------------------------- | ----------------------- |
|
||||
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
|
||||
| Name | Description | Example |
|
||||
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------ |
|
||||
| `attestation-id` | GitHub ID for the attestation | `123456` |
|
||||
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
|
||||
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
|
||||
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
@@ -157,7 +174,7 @@ jobs:
|
||||
- name: Build artifact
|
||||
run: make my-app
|
||||
- name: Attest
|
||||
uses: actions/attest@v1
|
||||
uses: actions/attest@v2
|
||||
with:
|
||||
subject-path: '${{ github.workspace }}/my-app'
|
||||
predicate-type: 'https://example.com/predicate/v1'
|
||||
@@ -170,7 +187,7 @@ If you are generating multiple artifacts, you can attest all of them at the same
|
||||
time by using a wildcard in the `subject-path` input.
|
||||
|
||||
```yaml
|
||||
- uses: actions/attest@v1
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
subject-path: 'dist/**/my-bin-*'
|
||||
predicate-type: 'https://example.com/predicate/v1'
|
||||
@@ -184,19 +201,56 @@ Alternatively, you can explicitly list multiple subjects with either a comma or
|
||||
newline delimited list:
|
||||
|
||||
```yaml
|
||||
- uses: actions/attest@v1
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
subject-path: 'dist/foo, dist/bar'
|
||||
```
|
||||
|
||||
```yaml
|
||||
- uses: actions/attest@v1
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
subject-path: |
|
||||
dist/foo
|
||||
dist/bar
|
||||
```
|
||||
|
||||
### Identify Subjects with Checksums File
|
||||
|
||||
If you are using tools like
|
||||
[goreleaser](https://goreleaser.com/customization/checksum/) or
|
||||
[jreleaser](https://jreleaser.org/guide/latest/reference/checksum.html) which
|
||||
generate a checksums file you can identify the attestation subjects by passing
|
||||
the path of the checksums file to the `subject-checksums` input. Each of the
|
||||
artifacts identified in the checksums file will be listed as a subject for the
|
||||
attestation.
|
||||
|
||||
```yaml
|
||||
- name: Calculate artifact digests
|
||||
run: |
|
||||
shasum -a 256 foo_0.0.1_* > subject.checksums.txt
|
||||
|
||||
- uses: actions/attest@v2
|
||||
with:
|
||||
subject-checksums: subject.checksums.txt
|
||||
predicate-type: 'https://example.com/predicate/v1'
|
||||
predicate: '{}'
|
||||
```
|
||||
|
||||
<!-- markdownlint-disable MD038 -->
|
||||
|
||||
The file referenced by the `subject-checksums` input must conform to the same
|
||||
format used by the shasum tools. Each subject should be listed on a separate
|
||||
line including the hex-encoded digest (either SHA256 or SHA512), a space, a
|
||||
single character flag indicating either binary (`*`) or text (` `) input mode,
|
||||
and the filename.
|
||||
|
||||
<!-- markdownlint-enable MD038 -->
|
||||
|
||||
```text
|
||||
b569bf992b287f55d78bf8ee476497e9b7e9d2bf1c338860bfb905016218c740 foo_0.0.1_darwin_amd64
|
||||
a54fc515e616cac7fcf11a49d5c5ec9ec315948a5935c1e11dd610b834b14dde foo_0.0.1_darwin_arm64
|
||||
```
|
||||
|
||||
### Container Image
|
||||
|
||||
When working with container images you can invoke the action with the
|
||||
@@ -247,7 +301,7 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- name: Attest
|
||||
uses: actions/attest@v1
|
||||
uses: actions/attest@v2
|
||||
id: attest
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('index', () => {
|
||||
beforeEach(() => {
|
||||
getBooleanInputMock.mockImplementation(() => false)
|
||||
})
|
||||
it('calls run when imported', async () => {
|
||||
it('calls run when imported', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('../src/index')
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const defaultInputs: main.RunInputs = {
|
||||
subjectName: '',
|
||||
subjectDigest: '',
|
||||
subjectPath: '',
|
||||
subjectChecksums: '',
|
||||
pushToRegistry: false,
|
||||
showSummary: true,
|
||||
githubToken: '',
|
||||
@@ -138,7 +139,9 @@ describe('action', () => {
|
||||
|
||||
expect(runMock).toHaveReturned()
|
||||
expect(setFailedMock).toHaveBeenCalledWith(
|
||||
new Error('One of subject-path or subject-digest must be provided')
|
||||
new Error(
|
||||
'One of subject-path, subject-digest, or subject-checksums must be provided'
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -199,6 +202,16 @@ describe('action', () => {
|
||||
'bundle-path',
|
||||
expect.stringMatching('attestation.json')
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'attestation-id',
|
||||
expect.stringMatching(attestationID)
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'attestation-url',
|
||||
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
|
||||
)
|
||||
expect(setFailedMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -285,6 +298,16 @@ describe('action', () => {
|
||||
'bundle-path',
|
||||
expect.stringMatching('attestation.json')
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'attestation-id',
|
||||
expect.stringMatching(attestationID)
|
||||
)
|
||||
expect(setOutputMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'attestation-url',
|
||||
expect.stringContaining(`foo/bar/attestations/${attestationID}`)
|
||||
)
|
||||
expect(setFailedMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,13 +12,14 @@ describe('subjectFromInputs', () => {
|
||||
const blankInputs: SubjectInputs = {
|
||||
subjectPath: '',
|
||||
subjectName: '',
|
||||
subjectDigest: ''
|
||||
subjectDigest: '',
|
||||
subjectChecksums: ''
|
||||
}
|
||||
|
||||
describe('when no inputs are provided', () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(subjectFromInputs(blankInputs)).rejects.toThrow(
|
||||
/one of subject-path or subject-digest must be provided/i
|
||||
/one of subject-path, subject-digest, or subject-checksums must be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -28,11 +29,42 @@ describe('subjectFromInputs', () => {
|
||||
const inputs: SubjectInputs = {
|
||||
subjectName: 'foo',
|
||||
subjectPath: 'path/to/subject',
|
||||
subjectDigest: 'digest'
|
||||
subjectDigest: 'digest',
|
||||
subjectChecksums: ''
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/only one of subject-path or subject-digest may be provided/i
|
||||
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when both subject path and subject checksums are provided', () => {
|
||||
it('throws an error', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
subjectName: '',
|
||||
subjectPath: 'path/to/subject',
|
||||
subjectDigest: '',
|
||||
subjectChecksums: 'path/to/checksums'
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when both subject digest and subject checksums are provided', () => {
|
||||
it('throws an error', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
subjectName: 'foo',
|
||||
subjectPath: '',
|
||||
subjectDigest: 'digest',
|
||||
subjectChecksums: 'path/to/checksums'
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/only one of subject-path, subject-digest, or subject-checksums may be provided/i
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -232,7 +264,6 @@ describe('subjectFromInputs', () => {
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(3)
|
||||
|
||||
/* eslint-disable-next-line github/array-foreach */
|
||||
subjects.forEach((subject, i) => {
|
||||
expect(subject.name).toEqual(`${filename}-${i}`)
|
||||
expect(subject.digest).toEqual({ sha256: expectedDigest })
|
||||
@@ -362,6 +393,143 @@ describe('subjectFromInputs', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when duplicate subjects are supplied', () => {
|
||||
let otherDir = ''
|
||||
|
||||
// Add duplicate subject in alternate directory
|
||||
beforeEach(async () => {
|
||||
// Set-up temp directory
|
||||
const tmpDir = await fs.realpath(os.tmpdir())
|
||||
otherDir = await fs.mkdtemp(tmpDir + path.sep)
|
||||
|
||||
// Write file to temp directory
|
||||
await fs.writeFile(path.join(otherDir, filename), content)
|
||||
})
|
||||
|
||||
it('returns de-duplicated subjects', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectPath: `${path.join(dir, 'subject')}, ${path.join(otherDir, 'subject')} `
|
||||
}
|
||||
const subjects = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specifying a subject checksums file', () => {
|
||||
const checksums = `
|
||||
187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d demo_0.0.1_linux_amd64
|
||||
badline
|
||||
5d8b4751ef31f9440d843fcfa4e53ca2e25b1cb1f13fd355fdc7c24b41fe645293291ea9297ba3989078abb77ebbaac66be073618a9e4974dbd0361881d4c718 demo_0.0.1_darwin_arm64`
|
||||
|
||||
let dir = ''
|
||||
const filename = 'checksums'
|
||||
|
||||
beforeEach(async () => {
|
||||
// Set-up temp directory
|
||||
const tmpDir = await fs.realpath(os.tmpdir())
|
||||
dir = await fs.mkdtemp(tmpDir + path.sep)
|
||||
|
||||
// Write file to temp directory
|
||||
await fs.writeFile(path.join(dir, filename), checksums)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean-up temp directory
|
||||
await fs.rm(dir, { recursive: true })
|
||||
})
|
||||
|
||||
describe('when the specified path is NOT a file', () => {
|
||||
it('throws an error', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectChecksums: dir
|
||||
}
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/subject checksums file not found/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the specific path is a file', () => {
|
||||
it('returns the multiple subjects', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectChecksums: path.join(dir, filename)
|
||||
}
|
||||
const subjects = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(2)
|
||||
|
||||
expect(subjects).toContainEqual({
|
||||
name: 'demo_0.0.1_linux_amd64',
|
||||
digest: {
|
||||
sha256:
|
||||
'187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specifying a subject checksums string', () => {
|
||||
const checksums = `
|
||||
f861e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e demo_0.0.1_linux_386
|
||||
187dcd1506a170337415589ff00c8743f19d41cc31fca246c2739dfd450d0b9d demo_0.0.1_linux_amd64
|
||||
9ecbf449e286a8a8748c161c52aa28b6b2fc64ab86f94161c5d1b3abc18156c5 demo_0.0.1_linux_arm64`
|
||||
|
||||
it('returns the multiple subjects', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectChecksums: checksums
|
||||
}
|
||||
const subjects = await subjectFromInputs(inputs)
|
||||
|
||||
expect(subjects).toBeDefined()
|
||||
expect(subjects).toHaveLength(3)
|
||||
|
||||
expect(subjects).toContainEqual({
|
||||
name: 'demo_0.0.1_linux_386',
|
||||
digest: {
|
||||
sha256:
|
||||
'f861e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e'
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specifying a subject checksums string with an unrecognized digest', () => {
|
||||
const checksums = `f861e demo_0.0.1_linux_386`
|
||||
|
||||
it('throws an error', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectChecksums: checksums
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(
|
||||
/unknown digest algorithm/i
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when specifying a subject checksums string with an invalid digest', () => {
|
||||
const checksums =
|
||||
'!!!!e68a080799ca83104630b56abb90d8dbcc5f8b5a8639cb691e269838f29e demo_0.0.1_linux_386'
|
||||
|
||||
it('throws an error', async () => {
|
||||
const inputs: SubjectInputs = {
|
||||
...blankInputs,
|
||||
subjectChecksums: checksums
|
||||
}
|
||||
|
||||
await expect(subjectFromInputs(inputs)).rejects.toThrow(/invalid digest/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
22
action.yml
22
action.yml
@@ -9,20 +9,26 @@ inputs:
|
||||
subject-path:
|
||||
description: >
|
||||
Path to the artifact serving as the subject of the attestation. Must
|
||||
specify exactly one of "subject-path" or "subject-digest". May contain a
|
||||
glob pattern or list of paths (total subject count cannot exceed 1024).
|
||||
specify exactly one of "subject-path", "subject-digest", or
|
||||
"subject-checksums". May contain a glob pattern or list of paths (total
|
||||
subject count cannot exceed 1024).
|
||||
required: false
|
||||
subject-digest:
|
||||
description: >
|
||||
Digest of the subject for the attestation. Must be in the form
|
||||
"algorithm:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
|
||||
of "subject-path" or "subject-digest".
|
||||
of "subject-path", "subject-digest", or "subject-checksums".
|
||||
required: false
|
||||
subject-name:
|
||||
description: >
|
||||
Subject name as it should appear in the attestation. Required unless
|
||||
"subject-path" is specified, in which case it will be inferred from the
|
||||
path.
|
||||
Subject name as it should appear in the attestation. Required when
|
||||
identifying the subject with the "subject-digest" input.
|
||||
required: false
|
||||
subject-checksums:
|
||||
description: >
|
||||
Path to checksums file containing digest and name of subjects for
|
||||
attestation. Must specify exactly one of "subject-path", "subject-digest",
|
||||
or "subject-checksums".
|
||||
required: false
|
||||
predicate-type:
|
||||
description: >
|
||||
@@ -61,6 +67,10 @@ inputs:
|
||||
outputs:
|
||||
bundle-path:
|
||||
description: 'The path to the file containing the attestation bundle.'
|
||||
attestation-id:
|
||||
description: 'The ID of the attestation.'
|
||||
attestation-url:
|
||||
description: 'The URL for the attestation summary.'
|
||||
|
||||
runs:
|
||||
using: node20
|
||||
|
||||
243
dist/index.js
generated
vendored
243
dist/index.js
generated
vendored
@@ -28485,7 +28485,7 @@ Object.defineProperty(exports, "cryptoRuntime", ({ enumerable: true, get: functi
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.compactDecrypt = void 0;
|
||||
exports.compactDecrypt = compactDecrypt;
|
||||
const decrypt_js_1 = __nccwpck_require__(59344);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const buffer_utils_js_1 = __nccwpck_require__(45734);
|
||||
@@ -28513,7 +28513,6 @@ async function compactDecrypt(jwe, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.compactDecrypt = compactDecrypt;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -28563,7 +28562,7 @@ exports.CompactEncrypt = CompactEncrypt;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.flattenedDecrypt = void 0;
|
||||
exports.flattenedDecrypt = flattenedDecrypt;
|
||||
const base64url_js_1 = __nccwpck_require__(12635);
|
||||
const decrypt_js_1 = __nccwpck_require__(52806);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -28725,7 +28724,6 @@ async function flattenedDecrypt(jwe, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.flattenedDecrypt = flattenedDecrypt;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -28909,7 +28907,7 @@ exports.FlattenedEncrypt = FlattenedEncrypt;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.generalDecrypt = void 0;
|
||||
exports.generalDecrypt = generalDecrypt;
|
||||
const decrypt_js_1 = __nccwpck_require__(59344);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
@@ -28941,7 +28939,6 @@ async function generalDecrypt(jwe, key, options) {
|
||||
}
|
||||
throw new errors_js_1.JWEDecryptionFailed();
|
||||
}
|
||||
exports.generalDecrypt = generalDecrypt;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29146,7 +29143,7 @@ exports.GeneralEncrypt = GeneralEncrypt;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.EmbeddedJWK = void 0;
|
||||
exports.EmbeddedJWK = EmbeddedJWK;
|
||||
const import_js_1 = __nccwpck_require__(45647);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -29164,7 +29161,6 @@ async function EmbeddedJWK(protectedHeader, token) {
|
||||
}
|
||||
return key;
|
||||
}
|
||||
exports.EmbeddedJWK = EmbeddedJWK;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29175,7 +29171,8 @@ exports.EmbeddedJWK = EmbeddedJWK;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.calculateJwkThumbprintUri = exports.calculateJwkThumbprint = void 0;
|
||||
exports.calculateJwkThumbprint = calculateJwkThumbprint;
|
||||
exports.calculateJwkThumbprintUri = calculateJwkThumbprintUri;
|
||||
const digest_js_1 = __nccwpck_require__(12931);
|
||||
const base64url_js_1 = __nccwpck_require__(12635);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -29224,13 +29221,11 @@ async function calculateJwkThumbprint(jwk, digestAlgorithm) {
|
||||
const data = buffer_utils_js_1.encoder.encode(JSON.stringify(components));
|
||||
return (0, base64url_js_1.encode)(await (0, digest_js_1.default)(digestAlgorithm, data));
|
||||
}
|
||||
exports.calculateJwkThumbprint = calculateJwkThumbprint;
|
||||
async function calculateJwkThumbprintUri(jwk, digestAlgorithm) {
|
||||
digestAlgorithm ??= 'sha256';
|
||||
const thumbprint = await calculateJwkThumbprint(jwk, digestAlgorithm);
|
||||
return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`;
|
||||
}
|
||||
exports.calculateJwkThumbprintUri = calculateJwkThumbprintUri;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29241,7 +29236,7 @@ exports.calculateJwkThumbprintUri = calculateJwkThumbprintUri;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.createLocalJWKSet = void 0;
|
||||
exports.createLocalJWKSet = createLocalJWKSet;
|
||||
const import_js_1 = __nccwpck_require__(45647);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
@@ -29364,7 +29359,6 @@ function createLocalJWKSet(jwks) {
|
||||
});
|
||||
return localJWKSet;
|
||||
}
|
||||
exports.createLocalJWKSet = createLocalJWKSet;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29375,7 +29369,8 @@ exports.createLocalJWKSet = createLocalJWKSet;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.experimental_jwksCache = exports.createRemoteJWKSet = exports.jwksCache = void 0;
|
||||
exports.experimental_jwksCache = exports.jwksCache = void 0;
|
||||
exports.createRemoteJWKSet = createRemoteJWKSet;
|
||||
const fetch_jwks_js_1 = __nccwpck_require__(20311);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const local_js_1 = __nccwpck_require__(25275);
|
||||
@@ -29388,7 +29383,7 @@ function isCloudflareWorkers() {
|
||||
let USER_AGENT;
|
||||
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
||||
const NAME = 'jose';
|
||||
const VERSION = 'v5.9.4';
|
||||
const VERSION = 'v5.9.6';
|
||||
USER_AGENT = `${NAME}/${VERSION}`;
|
||||
}
|
||||
exports.jwksCache = Symbol();
|
||||
@@ -29523,7 +29518,6 @@ function createRemoteJWKSet(url, options) {
|
||||
});
|
||||
return remoteJWKSet;
|
||||
}
|
||||
exports.createRemoteJWKSet = createRemoteJWKSet;
|
||||
exports.experimental_jwksCache = exports.jwksCache;
|
||||
|
||||
|
||||
@@ -29565,7 +29559,7 @@ exports.CompactSign = CompactSign;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.compactVerify = void 0;
|
||||
exports.compactVerify = compactVerify;
|
||||
const verify_js_1 = __nccwpck_require__(56358);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const buffer_utils_js_1 = __nccwpck_require__(45734);
|
||||
@@ -29587,7 +29581,6 @@ async function compactVerify(jws, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.compactVerify = compactVerify;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29694,7 +29687,7 @@ exports.FlattenedSign = FlattenedSign;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.flattenedVerify = void 0;
|
||||
exports.flattenedVerify = flattenedVerify;
|
||||
const base64url_js_1 = __nccwpck_require__(12635);
|
||||
const verify_js_1 = __nccwpck_require__(11242);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -29817,7 +29810,6 @@ async function flattenedVerify(jws, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.flattenedVerify = flattenedVerify;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29913,7 +29905,7 @@ exports.GeneralSign = GeneralSign;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.generalVerify = void 0;
|
||||
exports.generalVerify = generalVerify;
|
||||
const verify_js_1 = __nccwpck_require__(56358);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
@@ -29938,7 +29930,6 @@ async function generalVerify(jws, key, options) {
|
||||
}
|
||||
throw new errors_js_1.JWSSignatureVerificationFailed();
|
||||
}
|
||||
exports.generalVerify = generalVerify;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -29949,7 +29940,7 @@ exports.generalVerify = generalVerify;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.jwtDecrypt = void 0;
|
||||
exports.jwtDecrypt = jwtDecrypt;
|
||||
const decrypt_js_1 = __nccwpck_require__(14298);
|
||||
const jwt_claims_set_js_1 = __nccwpck_require__(13354);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -29973,7 +29964,6 @@ async function jwtDecrypt(jwt, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.jwtDecrypt = jwtDecrypt;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30236,7 +30226,7 @@ exports.UnsecuredJWT = UnsecuredJWT;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.jwtVerify = void 0;
|
||||
exports.jwtVerify = jwtVerify;
|
||||
const verify_js_1 = __nccwpck_require__(94212);
|
||||
const jwt_claims_set_js_1 = __nccwpck_require__(13354);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -30252,7 +30242,6 @@ async function jwtVerify(jwt, key, options) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.jwtVerify = jwtVerify;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30263,22 +30252,21 @@ exports.jwtVerify = jwtVerify;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.exportJWK = exports.exportPKCS8 = exports.exportSPKI = void 0;
|
||||
exports.exportSPKI = exportSPKI;
|
||||
exports.exportPKCS8 = exportPKCS8;
|
||||
exports.exportJWK = exportJWK;
|
||||
const asn1_js_1 = __nccwpck_require__(12774);
|
||||
const asn1_js_2 = __nccwpck_require__(12774);
|
||||
const key_to_jwk_js_1 = __nccwpck_require__(9041);
|
||||
async function exportSPKI(key) {
|
||||
return (0, asn1_js_1.toSPKI)(key);
|
||||
}
|
||||
exports.exportSPKI = exportSPKI;
|
||||
async function exportPKCS8(key) {
|
||||
return (0, asn1_js_2.toPKCS8)(key);
|
||||
}
|
||||
exports.exportPKCS8 = exportPKCS8;
|
||||
async function exportJWK(key) {
|
||||
return (0, key_to_jwk_js_1.default)(key);
|
||||
}
|
||||
exports.exportJWK = exportJWK;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30289,12 +30277,11 @@ exports.exportJWK = exportJWK;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.generateKeyPair = void 0;
|
||||
exports.generateKeyPair = generateKeyPair;
|
||||
const generate_js_1 = __nccwpck_require__(10088);
|
||||
async function generateKeyPair(alg, options) {
|
||||
return (0, generate_js_1.generateKeyPair)(alg, options);
|
||||
}
|
||||
exports.generateKeyPair = generateKeyPair;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30305,12 +30292,11 @@ exports.generateKeyPair = generateKeyPair;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.generateSecret = void 0;
|
||||
exports.generateSecret = generateSecret;
|
||||
const generate_js_1 = __nccwpck_require__(10088);
|
||||
async function generateSecret(alg, options) {
|
||||
return (0, generate_js_1.generateSecret)(alg, options);
|
||||
}
|
||||
exports.generateSecret = generateSecret;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30321,7 +30307,10 @@ exports.generateSecret = generateSecret;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.importJWK = exports.importPKCS8 = exports.importX509 = exports.importSPKI = void 0;
|
||||
exports.importSPKI = importSPKI;
|
||||
exports.importX509 = importX509;
|
||||
exports.importPKCS8 = importPKCS8;
|
||||
exports.importJWK = importJWK;
|
||||
const base64url_js_1 = __nccwpck_require__(12635);
|
||||
const asn1_js_1 = __nccwpck_require__(12774);
|
||||
const jwk_to_key_js_1 = __nccwpck_require__(80939);
|
||||
@@ -30333,21 +30322,18 @@ async function importSPKI(spki, alg, options) {
|
||||
}
|
||||
return (0, asn1_js_1.fromSPKI)(spki, alg, options);
|
||||
}
|
||||
exports.importSPKI = importSPKI;
|
||||
async function importX509(x509, alg, options) {
|
||||
if (typeof x509 !== 'string' || x509.indexOf('-----BEGIN CERTIFICATE-----') !== 0) {
|
||||
throw new TypeError('"x509" must be X.509 formatted string');
|
||||
}
|
||||
return (0, asn1_js_1.fromX509)(x509, alg, options);
|
||||
}
|
||||
exports.importX509 = importX509;
|
||||
async function importPKCS8(pkcs8, alg, options) {
|
||||
if (typeof pkcs8 !== 'string' || pkcs8.indexOf('-----BEGIN PRIVATE KEY-----') !== 0) {
|
||||
throw new TypeError('"pkcs8" must be PKCS#8 formatted string');
|
||||
}
|
||||
return (0, asn1_js_1.fromPKCS8)(pkcs8, alg, options);
|
||||
}
|
||||
exports.importPKCS8 = importPKCS8;
|
||||
async function importJWK(jwk, alg) {
|
||||
if (!(0, is_object_js_1.default)(jwk)) {
|
||||
throw new TypeError('JWK must be an object');
|
||||
@@ -30370,7 +30356,6 @@ async function importJWK(jwk, alg) {
|
||||
throw new errors_js_1.JOSENotSupported('Unsupported "kty" (Key Type) Parameter value');
|
||||
}
|
||||
}
|
||||
exports.importJWK = importJWK;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30381,7 +30366,8 @@ exports.importJWK = importJWK;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.unwrap = exports.wrap = void 0;
|
||||
exports.wrap = wrap;
|
||||
exports.unwrap = unwrap;
|
||||
const encrypt_js_1 = __nccwpck_require__(36286);
|
||||
const decrypt_js_1 = __nccwpck_require__(52806);
|
||||
const base64url_js_1 = __nccwpck_require__(12635);
|
||||
@@ -30394,12 +30380,10 @@ async function wrap(alg, key, cek, iv) {
|
||||
tag: (0, base64url_js_1.encode)(wrapped.tag),
|
||||
};
|
||||
}
|
||||
exports.wrap = wrap;
|
||||
async function unwrap(alg, key, encryptedKey, iv, tag) {
|
||||
const jweAlgorithm = alg.slice(0, 7);
|
||||
return (0, decrypt_js_1.default)(jweAlgorithm, key, encryptedKey, iv, tag, new Uint8Array(0));
|
||||
}
|
||||
exports.unwrap = unwrap;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30410,7 +30394,13 @@ exports.unwrap = unwrap;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.concatKdf = exports.lengthAndInput = exports.uint32be = exports.uint64be = exports.p2s = exports.concat = exports.decoder = exports.encoder = void 0;
|
||||
exports.decoder = exports.encoder = void 0;
|
||||
exports.concat = concat;
|
||||
exports.p2s = p2s;
|
||||
exports.uint64be = uint64be;
|
||||
exports.uint32be = uint32be;
|
||||
exports.lengthAndInput = lengthAndInput;
|
||||
exports.concatKdf = concatKdf;
|
||||
const digest_js_1 = __nccwpck_require__(12931);
|
||||
exports.encoder = new TextEncoder();
|
||||
exports.decoder = new TextDecoder();
|
||||
@@ -30425,11 +30415,9 @@ function concat(...buffers) {
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
exports.concat = concat;
|
||||
function p2s(alg, p2sInput) {
|
||||
return concat(exports.encoder.encode(alg), new Uint8Array([0]), p2sInput);
|
||||
}
|
||||
exports.p2s = p2s;
|
||||
function writeUInt32BE(buf, value, offset) {
|
||||
if (value < 0 || value >= MAX_INT32) {
|
||||
throw new RangeError(`value must be >= 0 and <= ${MAX_INT32 - 1}. Received ${value}`);
|
||||
@@ -30444,17 +30432,14 @@ function uint64be(value) {
|
||||
writeUInt32BE(buf, low, 4);
|
||||
return buf;
|
||||
}
|
||||
exports.uint64be = uint64be;
|
||||
function uint32be(value) {
|
||||
const buf = new Uint8Array(4);
|
||||
writeUInt32BE(buf, value);
|
||||
return buf;
|
||||
}
|
||||
exports.uint32be = uint32be;
|
||||
function lengthAndInput(input) {
|
||||
return concat(uint32be(input.length), input);
|
||||
}
|
||||
exports.lengthAndInput = lengthAndInput;
|
||||
async function concatKdf(secret, bits, value) {
|
||||
const iterations = Math.ceil((bits >> 3) / 32);
|
||||
const res = new Uint8Array(iterations * 32);
|
||||
@@ -30467,7 +30452,6 @@ async function concatKdf(secret, bits, value) {
|
||||
}
|
||||
return res.slice(0, bits >> 3);
|
||||
}
|
||||
exports.concatKdf = concatKdf;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30478,7 +30462,7 @@ exports.concatKdf = concatKdf;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.bitLength = void 0;
|
||||
exports.bitLength = bitLength;
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const random_js_1 = __nccwpck_require__(23376);
|
||||
function bitLength(alg) {
|
||||
@@ -30498,7 +30482,6 @@ function bitLength(alg) {
|
||||
throw new errors_js_1.JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
|
||||
}
|
||||
}
|
||||
exports.bitLength = bitLength;
|
||||
exports["default"] = (alg) => (0, random_js_1.default)(new Uint8Array(bitLength(alg) >> 3));
|
||||
|
||||
|
||||
@@ -30616,13 +30599,13 @@ exports.checkKeyTypeWithJwk = checkKeyType.bind(undefined, true);
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = checkP2s;
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
function checkP2s(p2s) {
|
||||
if (!(p2s instanceof Uint8Array) || p2s.length < 8) {
|
||||
throw new errors_js_1.JWEInvalid('PBES2 Salt Input must be 8 or more octets');
|
||||
}
|
||||
}
|
||||
exports["default"] = checkP2s;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -30633,7 +30616,8 @@ exports["default"] = checkP2s;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.checkEncCryptoKey = exports.checkSigCryptoKey = void 0;
|
||||
exports.checkSigCryptoKey = checkSigCryptoKey;
|
||||
exports.checkEncCryptoKey = checkEncCryptoKey;
|
||||
function unusable(name, prop = 'algorithm.name') {
|
||||
return new TypeError(`CryptoKey does not support this operation, its ${prop} must be ${name}`);
|
||||
}
|
||||
@@ -30728,7 +30712,6 @@ function checkSigCryptoKey(key, alg, ...usages) {
|
||||
}
|
||||
checkUsage(key, usages);
|
||||
}
|
||||
exports.checkSigCryptoKey = checkSigCryptoKey;
|
||||
function checkEncCryptoKey(key, alg, ...usages) {
|
||||
switch (alg) {
|
||||
case 'A128GCM':
|
||||
@@ -30787,7 +30770,6 @@ function checkEncCryptoKey(key, alg, ...usages) {
|
||||
}
|
||||
checkUsage(key, usages);
|
||||
}
|
||||
exports.checkEncCryptoKey = checkEncCryptoKey;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31047,7 +31029,7 @@ exports["default"] = (date) => Math.floor(date.getTime() / 1000);
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.withAlg = void 0;
|
||||
exports.withAlg = withAlg;
|
||||
function message(msg, actual, ...types) {
|
||||
types = types.filter(Boolean);
|
||||
if (types.length > 2) {
|
||||
@@ -31079,7 +31061,6 @@ exports["default"] = (actual, ...types) => {
|
||||
function withAlg(alg, actual, ...types) {
|
||||
return message(`Key for the ${alg} algorithm must be `, actual, ...types);
|
||||
}
|
||||
exports.withAlg = withAlg;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31122,24 +31103,23 @@ exports["default"] = isDisjoint;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.isSecretJWK = exports.isPublicJWK = exports.isPrivateJWK = exports.isJWK = void 0;
|
||||
exports.isJWK = isJWK;
|
||||
exports.isPrivateJWK = isPrivateJWK;
|
||||
exports.isPublicJWK = isPublicJWK;
|
||||
exports.isSecretJWK = isSecretJWK;
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
function isJWK(key) {
|
||||
return (0, is_object_js_1.default)(key) && typeof key.kty === 'string';
|
||||
}
|
||||
exports.isJWK = isJWK;
|
||||
function isPrivateJWK(key) {
|
||||
return key.kty !== 'oct' && typeof key.d === 'string';
|
||||
}
|
||||
exports.isPrivateJWK = isPrivateJWK;
|
||||
function isPublicJWK(key) {
|
||||
return key.kty !== 'oct' && typeof key.d === 'undefined';
|
||||
}
|
||||
exports.isPublicJWK = isPublicJWK;
|
||||
function isSecretJWK(key) {
|
||||
return isJWK(key) && key.kty === 'oct' && typeof key.k === 'string';
|
||||
}
|
||||
exports.isSecretJWK = isSecretJWK;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31150,6 +31130,7 @@ exports.isSecretJWK = isSecretJWK;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = isObject;
|
||||
function isObjectLike(value) {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
@@ -31166,7 +31147,6 @@ function isObject(input) {
|
||||
}
|
||||
return Object.getPrototypeOf(input) === proto;
|
||||
}
|
||||
exports["default"] = isObject;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31177,7 +31157,7 @@ exports["default"] = isObject;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.bitLength = void 0;
|
||||
exports.bitLength = bitLength;
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
const random_js_1 = __nccwpck_require__(23376);
|
||||
function bitLength(alg) {
|
||||
@@ -31197,7 +31177,6 @@ function bitLength(alg) {
|
||||
throw new errors_js_1.JOSENotSupported(`Unsupported JWE Algorithm: ${alg}`);
|
||||
}
|
||||
}
|
||||
exports.bitLength = bitLength;
|
||||
exports["default"] = (alg) => (0, random_js_1.default)(new Uint8Array(bitLength(alg) >> 3));
|
||||
|
||||
|
||||
@@ -31618,6 +31597,7 @@ exports.decode = decode;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = cbcTag;
|
||||
const node_crypto_1 = __nccwpck_require__(77598);
|
||||
const buffer_utils_js_1 = __nccwpck_require__(45734);
|
||||
function cbcTag(aad, iv, ciphertext, macSize, macKey, keySize) {
|
||||
@@ -31626,7 +31606,6 @@ function cbcTag(aad, iv, ciphertext, macSize, macKey, keySize) {
|
||||
hmac.update(macData);
|
||||
return hmac.digest().slice(0, keySize >> 3);
|
||||
}
|
||||
exports["default"] = cbcTag;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31848,6 +31827,7 @@ exports["default"] = digest;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = dsaDigest;
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
function dsaDigest(alg) {
|
||||
switch (alg) {
|
||||
@@ -31870,7 +31850,6 @@ function dsaDigest(alg) {
|
||||
throw new errors_js_1.JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
||||
}
|
||||
}
|
||||
exports["default"] = dsaDigest;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -31881,7 +31860,9 @@ exports["default"] = dsaDigest;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ecdhAllowed = exports.generateEpk = exports.deriveKey = void 0;
|
||||
exports.ecdhAllowed = void 0;
|
||||
exports.deriveKey = deriveKey;
|
||||
exports.generateEpk = generateEpk;
|
||||
const node_crypto_1 = __nccwpck_require__(77598);
|
||||
const node_util_1 = __nccwpck_require__(57975);
|
||||
const get_named_curve_js_1 = __nccwpck_require__(65661);
|
||||
@@ -31920,7 +31901,6 @@ async function deriveKey(publicKee, privateKee, algorithm, keyLength, apu = new
|
||||
const sharedSecret = (0, node_crypto_1.diffieHellman)({ privateKey, publicKey });
|
||||
return (0, buffer_utils_js_1.concatKdf)(sharedSecret, keyLength, value);
|
||||
}
|
||||
exports.deriveKey = deriveKey;
|
||||
async function generateEpk(kee) {
|
||||
let key;
|
||||
if ((0, webcrypto_js_1.isCryptoKey)(kee)) {
|
||||
@@ -31946,7 +31926,6 @@ async function generateEpk(kee) {
|
||||
throw new errors_js_1.JOSENotSupported('Invalid or unsupported EPK');
|
||||
}
|
||||
}
|
||||
exports.generateEpk = generateEpk;
|
||||
const ecdhAllowed = (key) => ['P-256', 'P-384', 'P-521', 'X25519', 'X448'].includes((0, get_named_curve_js_1.default)(key));
|
||||
exports.ecdhAllowed = ecdhAllowed;
|
||||
|
||||
@@ -32100,7 +32079,8 @@ exports["default"] = fetchJwks;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.generateKeyPair = exports.generateSecret = void 0;
|
||||
exports.generateSecret = generateSecret;
|
||||
exports.generateKeyPair = generateKeyPair;
|
||||
const node_crypto_1 = __nccwpck_require__(77598);
|
||||
const node_util_1 = __nccwpck_require__(57975);
|
||||
const random_js_1 = __nccwpck_require__(23376);
|
||||
@@ -32133,7 +32113,6 @@ async function generateSecret(alg, options) {
|
||||
}
|
||||
return (0, node_crypto_1.createSecretKey)((0, random_js_1.default)(new Uint8Array(length >> 3)));
|
||||
}
|
||||
exports.generateSecret = generateSecret;
|
||||
async function generateKeyPair(alg, options) {
|
||||
switch (alg) {
|
||||
case 'RS256':
|
||||
@@ -32199,7 +32178,6 @@ async function generateKeyPair(alg, options) {
|
||||
throw new errors_js_1.JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
||||
}
|
||||
}
|
||||
exports.generateKeyPair = generateKeyPair;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -32279,6 +32257,7 @@ exports["default"] = getNamedCurve;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = getSignVerifyKey;
|
||||
const node_crypto_1 = __nccwpck_require__(77598);
|
||||
const webcrypto_js_1 = __nccwpck_require__(59044);
|
||||
const crypto_key_js_1 = __nccwpck_require__(26319);
|
||||
@@ -32307,7 +32286,6 @@ function getSignVerifyKey(alg, key, usage) {
|
||||
}
|
||||
throw new TypeError((0, invalid_key_input_js_1.default)(key, ...is_key_like_js_1.types, 'Uint8Array', 'JSON Web Key'));
|
||||
}
|
||||
exports["default"] = getSignVerifyKey;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -32318,6 +32296,7 @@ exports["default"] = getSignVerifyKey;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = hmacDigest;
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
function hmacDigest(alg) {
|
||||
switch (alg) {
|
||||
@@ -32331,7 +32310,6 @@ function hmacDigest(alg) {
|
||||
throw new errors_js_1.JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
||||
}
|
||||
}
|
||||
exports["default"] = hmacDigest;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -32435,6 +32413,7 @@ exports["default"] = keyToJWK;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports["default"] = keyForCrypto;
|
||||
const node_crypto_1 = __nccwpck_require__(77598);
|
||||
const get_named_curve_js_1 = __nccwpck_require__(65661);
|
||||
const errors_js_1 = __nccwpck_require__(15974);
|
||||
@@ -32538,7 +32517,6 @@ function keyForCrypto(alg, key) {
|
||||
}
|
||||
return options ? { ...options, key } : key;
|
||||
}
|
||||
exports["default"] = keyForCrypto;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -32829,7 +32807,7 @@ exports.decode = base64url.decode;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.decodeJwt = void 0;
|
||||
exports.decodeJwt = decodeJwt;
|
||||
const base64url_js_1 = __nccwpck_require__(78305);
|
||||
const buffer_utils_js_1 = __nccwpck_require__(45734);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
@@ -32862,7 +32840,6 @@ function decodeJwt(jwt) {
|
||||
throw new errors_js_1.JWTInvalid('Invalid JWT Claims Set');
|
||||
return result;
|
||||
}
|
||||
exports.decodeJwt = decodeJwt;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -32873,7 +32850,7 @@ exports.decodeJwt = decodeJwt;
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.decodeProtectedHeader = void 0;
|
||||
exports.decodeProtectedHeader = decodeProtectedHeader;
|
||||
const base64url_js_1 = __nccwpck_require__(78305);
|
||||
const buffer_utils_js_1 = __nccwpck_require__(45734);
|
||||
const is_object_js_1 = __nccwpck_require__(92242);
|
||||
@@ -32908,7 +32885,6 @@ function decodeProtectedHeader(token) {
|
||||
throw new TypeError('Invalid Token or Protected Header formatting');
|
||||
}
|
||||
}
|
||||
exports.decodeProtectedHeader = decodeProtectedHeader;
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -70859,6 +70835,7 @@ const inputs = {
|
||||
subjectPath: core.getInput('subject-path'),
|
||||
subjectName: core.getInput('subject-name'),
|
||||
subjectDigest: core.getInput('subject-digest'),
|
||||
subjectChecksums: core.getInput('subject-checksums'),
|
||||
predicateType: core.getInput('predicate-type'),
|
||||
predicate: core.getInput('predicate'),
|
||||
predicatePath: core.getInput('predicate-path'),
|
||||
@@ -70868,7 +70845,7 @@ const inputs = {
|
||||
// undocumented -- not part of public interface
|
||||
privateSigning: ['true', 'True', 'TRUE', '1'].includes(core.getInput('private-signing'))
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
|
||||
(0, main_1.run)(inputs);
|
||||
|
||||
|
||||
@@ -70970,8 +70947,12 @@ async function run(inputs) {
|
||||
encoding: 'utf-8',
|
||||
flag: 'a'
|
||||
});
|
||||
if (att.attestationID) {
|
||||
core.setOutput('attestation-id', att.attestationID);
|
||||
core.setOutput('attestation-url', attestationURL(att.attestationID));
|
||||
}
|
||||
if (inputs.showSummary) {
|
||||
logSummary(att);
|
||||
await logSummary(att);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
@@ -71014,13 +70995,13 @@ const logAttestation = (subjects, attestation, sigstoreInstance) => {
|
||||
}
|
||||
};
|
||||
// Attach summary information to the GitHub Actions run
|
||||
const logSummary = (attestation) => {
|
||||
const logSummary = async (attestation) => {
|
||||
const { attestationID } = attestation;
|
||||
if (attestationID) {
|
||||
const url = attestationURL(attestationID);
|
||||
core.summary.addHeading('Attestation Created', 3);
|
||||
core.summary.addList([`<a href="${url}">${url}</a>`]);
|
||||
core.summary.write();
|
||||
await core.summary.write();
|
||||
}
|
||||
};
|
||||
const tempDir = () => {
|
||||
@@ -71149,23 +71130,28 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.formatSubjectDigest = exports.subjectFromInputs = void 0;
|
||||
const glob = __importStar(__nccwpck_require__(47206));
|
||||
const assert_1 = __importDefault(__nccwpck_require__(42613));
|
||||
const crypto_1 = __importDefault(__nccwpck_require__(76982));
|
||||
const sync_1 = __nccwpck_require__(61110);
|
||||
const fs_1 = __importDefault(__nccwpck_require__(79896));
|
||||
const os_1 = __importDefault(__nccwpck_require__(70857));
|
||||
const path_1 = __importDefault(__nccwpck_require__(16928));
|
||||
const MAX_SUBJECT_COUNT = 1024;
|
||||
const MAX_SUBJECT_CHECKSUM_SIZE_BYTES = 512 * MAX_SUBJECT_COUNT;
|
||||
const DIGEST_ALGORITHM = 'sha256';
|
||||
const HEX_STRING_RE = /^[0-9a-fA-F]+$/;
|
||||
// Returns the subject specified by the action's inputs. The subject may be
|
||||
// specified as a path to a file or as a digest. If a path is provided, the
|
||||
// file's digest is calculated and returned along with the subject's name. If a
|
||||
// digest is provided, the name must also be provided.
|
||||
const subjectFromInputs = async (inputs) => {
|
||||
const { subjectPath, subjectDigest, subjectName, downcaseName } = inputs;
|
||||
if (!subjectPath && !subjectDigest) {
|
||||
throw new Error('One of subject-path or subject-digest must be provided');
|
||||
const { subjectPath, subjectDigest, subjectName, subjectChecksums, downcaseName } = inputs;
|
||||
const enabledInputs = [subjectPath, subjectDigest, subjectChecksums].filter(Boolean);
|
||||
if (enabledInputs.length === 0) {
|
||||
throw new Error('One of subject-path, subject-digest, or subject-checksums must be provided');
|
||||
}
|
||||
if (subjectPath && subjectDigest) {
|
||||
throw new Error('Only one of subject-path or subject-digest may be provided');
|
||||
if (enabledInputs.length > 1) {
|
||||
throw new Error('Only one of subject-path, subject-digest, or subject-checksums may be provided');
|
||||
}
|
||||
if (subjectDigest && !subjectName) {
|
||||
throw new Error('subject-name must be provided when using subject-digest');
|
||||
@@ -71173,11 +71159,17 @@ const subjectFromInputs = async (inputs) => {
|
||||
// If push-to-registry is enabled, ensure the subject name is lowercase
|
||||
// to conform to OCI image naming conventions
|
||||
const name = downcaseName ? subjectName.toLowerCase() : subjectName;
|
||||
if (subjectPath) {
|
||||
return await getSubjectFromPath(subjectPath, name);
|
||||
}
|
||||
else {
|
||||
return [getSubjectFromDigest(subjectDigest, name)];
|
||||
switch (true) {
|
||||
case !!subjectPath:
|
||||
return getSubjectFromPath(subjectPath, name);
|
||||
case !!subjectDigest:
|
||||
return [getSubjectFromDigest(subjectDigest, name)];
|
||||
case !!subjectChecksums:
|
||||
return getSubjectFromChecksums(subjectChecksums);
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
// This should be unreachable, but TS requires a default case
|
||||
assert_1.default.fail('unreachable');
|
||||
}
|
||||
};
|
||||
exports.subjectFromInputs = subjectFromInputs;
|
||||
@@ -71193,9 +71185,8 @@ exports.formatSubjectDigest = formatSubjectDigest;
|
||||
const getSubjectFromPath = async (subjectPath, subjectName) => {
|
||||
const digestedSubjects = [];
|
||||
// Parse the list of subject paths
|
||||
const subjectPaths = parseList(subjectPath).join('\n');
|
||||
const subjectPaths = parseSubjectPathList(subjectPath).join('\n');
|
||||
// Expand the globbed paths to a list of actual paths
|
||||
/* eslint-disable-next-line github/no-then */
|
||||
const paths = await glob.create(subjectPaths).then(async (g) => g.glob());
|
||||
// Filter path list to just the files (not directories)
|
||||
const files = paths.filter(p => fs_1.default.statSync(p).isFile());
|
||||
@@ -71205,7 +71196,10 @@ const getSubjectFromPath = async (subjectPath, subjectName) => {
|
||||
for (const file of files) {
|
||||
const name = subjectName || path_1.default.parse(file).base;
|
||||
const digest = await digestFile(DIGEST_ALGORITHM, file);
|
||||
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } });
|
||||
// Only add the subject if it is not already in the list
|
||||
if (!digestedSubjects.some(s => s.name === name && s.digest[DIGEST_ALGORITHM] === digest)) {
|
||||
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } });
|
||||
}
|
||||
}
|
||||
if (digestedSubjects.length === 0) {
|
||||
throw new Error(`Could not find subject at path ${subjectPath}`);
|
||||
@@ -71224,6 +71218,49 @@ const getSubjectFromDigest = (subjectDigest, subjectName) => {
|
||||
digest: { [alg]: digest }
|
||||
};
|
||||
};
|
||||
const getSubjectFromChecksums = (subjectChecksums) => {
|
||||
if (fs_1.default.existsSync(subjectChecksums)) {
|
||||
return getSubjectFromChecksumsFile(subjectChecksums);
|
||||
}
|
||||
else {
|
||||
return getSubjectFromChecksumsString(subjectChecksums);
|
||||
}
|
||||
};
|
||||
const getSubjectFromChecksumsFile = (checksumsPath) => {
|
||||
const stats = fs_1.default.statSync(checksumsPath);
|
||||
if (!stats.isFile()) {
|
||||
throw new Error(`subject checksums file not found: ${checksumsPath}`);
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
if (stats.size > MAX_SUBJECT_CHECKSUM_SIZE_BYTES) {
|
||||
throw new Error(`subject checksums file exceeds maximum allowed size: ${MAX_SUBJECT_CHECKSUM_SIZE_BYTES} bytes`);
|
||||
}
|
||||
const checksums = fs_1.default.readFileSync(checksumsPath, 'utf-8');
|
||||
return getSubjectFromChecksumsString(checksums);
|
||||
};
|
||||
const getSubjectFromChecksumsString = (checksums) => {
|
||||
const subjects = [];
|
||||
const records = checksums.split(os_1.default.EOL).filter(Boolean);
|
||||
for (const record of records) {
|
||||
// Find the space delimiter following the digest
|
||||
const delimIndex = record.indexOf(' ');
|
||||
// Skip any line that doesn't have a delimiter
|
||||
if (delimIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
// Swallow the type identifier character at the beginning of the name
|
||||
const name = record.slice(delimIndex + 2);
|
||||
const digest = record.slice(0, delimIndex);
|
||||
if (!HEX_STRING_RE.test(digest)) {
|
||||
throw new Error(`Invalid digest: ${digest}`);
|
||||
}
|
||||
subjects.push({
|
||||
name,
|
||||
digest: { [digestAlgorithm(digest)]: digest }
|
||||
});
|
||||
}
|
||||
return subjects;
|
||||
};
|
||||
// Calculates the digest of a file using the specified algorithm. The file is
|
||||
// streamed into the digest function to avoid loading the entire file into
|
||||
// memory. The returned digest is a hex string.
|
||||
@@ -71236,7 +71273,7 @@ const digestFile = async (algorithm, filePath) => {
|
||||
.once('finish', () => resolve(hash.read()));
|
||||
});
|
||||
};
|
||||
const parseList = (input) => {
|
||||
const parseSubjectPathList = (input) => {
|
||||
const res = [];
|
||||
const records = (0, sync_1.parse)(input, {
|
||||
columns: false,
|
||||
@@ -71249,6 +71286,16 @@ const parseList = (input) => {
|
||||
}
|
||||
return res.filter(item => item).map(pat => pat.trim());
|
||||
};
|
||||
const digestAlgorithm = (digest) => {
|
||||
switch (digest.length) {
|
||||
case 64:
|
||||
return 'sha256';
|
||||
case 128:
|
||||
return 'sha512';
|
||||
default:
|
||||
throw new Error(`Unknown digest algorithm: ${digest}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
4018
package-lock.json
generated
4018
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "actions/attest",
|
||||
"description": "Generate signed attestations for workflow artifacts",
|
||||
"version": "2.0.0",
|
||||
"version": "2.2.0",
|
||||
"author": "",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/actions/attest",
|
||||
@@ -27,7 +27,7 @@
|
||||
"ci-test": "jest",
|
||||
"format:write": "prettier --write **/*.ts",
|
||||
"format:check": "prettier --check **/*.ts",
|
||||
"lint:eslint": "npx eslint . -c ./.github/linters/.eslintrc.yml",
|
||||
"lint:eslint": "npx eslint . -c ./.github/linters/eslint.config.mjs",
|
||||
"lint:markdown": "npx markdownlint --config .github/linters/.markdown-lint.yml \"*.md\"",
|
||||
"lint": "npm run lint:eslint && npm run lint:markdown",
|
||||
"package": "ncc build src/index.ts --license licenses.txt",
|
||||
@@ -71,31 +71,29 @@
|
||||
"dependencies": {
|
||||
"@actions/attest": "^1.5.0",
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@sigstore/oci": "^0.4.0",
|
||||
"csv-parse": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sigstore/mock": "^0.8.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@sigstore/mock": "^0.9.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/make-fetch-happen": "^10.0.4",
|
||||
"@types/node": "^22.9.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-plugin-github": "^5.1.2",
|
||||
"eslint-plugin-jest": "^28.9.0",
|
||||
"eslint-plugin-jsonc": "^2.18.2",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jest": "^28.11.0",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"markdownlint-cli": "^0.43.0",
|
||||
"nock": "^13.5.6",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-eslint": "^16.3.0",
|
||||
"prettier": "^3.4.2",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.21.0",
|
||||
"undici": "^5.28.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const inputs: RunInputs = {
|
||||
subjectPath: core.getInput('subject-path'),
|
||||
subjectName: core.getInput('subject-name'),
|
||||
subjectDigest: core.getInput('subject-digest'),
|
||||
subjectChecksums: core.getInput('subject-checksums'),
|
||||
predicateType: core.getInput('predicate-type'),
|
||||
predicate: core.getInput('predicate'),
|
||||
predicatePath: core.getInput('predicate-path'),
|
||||
@@ -20,5 +21,5 @@ const inputs: RunInputs = {
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
|
||||
run(inputs)
|
||||
|
||||
11
src/main.ts
11
src/main.ts
@@ -79,8 +79,13 @@ export async function run(inputs: RunInputs): Promise<void> {
|
||||
flag: 'a'
|
||||
})
|
||||
|
||||
if (att.attestationID) {
|
||||
core.setOutput('attestation-id', att.attestationID)
|
||||
core.setOutput('attestation-url', attestationURL(att.attestationID))
|
||||
}
|
||||
|
||||
if (inputs.showSummary) {
|
||||
logSummary(att)
|
||||
await logSummary(att)
|
||||
}
|
||||
} catch (err) {
|
||||
// Fail the workflow run if an error occurs
|
||||
@@ -148,14 +153,14 @@ const logAttestation = (
|
||||
}
|
||||
|
||||
// Attach summary information to the GitHub Actions run
|
||||
const logSummary = (attestation: AttestResult): void => {
|
||||
const logSummary = async (attestation: AttestResult): Promise<void> => {
|
||||
const { attestationID } = attestation
|
||||
|
||||
if (attestationID) {
|
||||
const url = attestationURL(attestationID)
|
||||
core.summary.addHeading('Attestation Created', 3)
|
||||
core.summary.addList([`<a href="${url}">${url}</a>`])
|
||||
core.summary.write()
|
||||
await core.summary.write()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
122
src/subject.ts
122
src/subject.ts
@@ -1,18 +1,23 @@
|
||||
import * as glob from '@actions/glob'
|
||||
import assert from 'assert'
|
||||
import crypto from 'crypto'
|
||||
import { parse } from 'csv-parse/sync'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
|
||||
import type { Subject } from '@actions/attest'
|
||||
|
||||
const MAX_SUBJECT_COUNT = 1024
|
||||
const MAX_SUBJECT_CHECKSUM_SIZE_BYTES = 512 * MAX_SUBJECT_COUNT
|
||||
const DIGEST_ALGORITHM = 'sha256'
|
||||
const HEX_STRING_RE = /^[0-9a-fA-F]+$/
|
||||
|
||||
export type SubjectInputs = {
|
||||
subjectPath: string
|
||||
subjectName: string
|
||||
subjectDigest: string
|
||||
subjectChecksums: string
|
||||
downcaseName?: boolean
|
||||
}
|
||||
// Returns the subject specified by the action's inputs. The subject may be
|
||||
@@ -22,15 +27,26 @@ export type SubjectInputs = {
|
||||
export const subjectFromInputs = async (
|
||||
inputs: SubjectInputs
|
||||
): Promise<Subject[]> => {
|
||||
const { subjectPath, subjectDigest, subjectName, downcaseName } = inputs
|
||||
const {
|
||||
subjectPath,
|
||||
subjectDigest,
|
||||
subjectName,
|
||||
subjectChecksums,
|
||||
downcaseName
|
||||
} = inputs
|
||||
|
||||
if (!subjectPath && !subjectDigest) {
|
||||
throw new Error('One of subject-path or subject-digest must be provided')
|
||||
const enabledInputs = [subjectPath, subjectDigest, subjectChecksums].filter(
|
||||
Boolean
|
||||
)
|
||||
if (enabledInputs.length === 0) {
|
||||
throw new Error(
|
||||
'One of subject-path, subject-digest, or subject-checksums must be provided'
|
||||
)
|
||||
}
|
||||
|
||||
if (subjectPath && subjectDigest) {
|
||||
if (enabledInputs.length > 1) {
|
||||
throw new Error(
|
||||
'Only one of subject-path or subject-digest may be provided'
|
||||
'Only one of subject-path, subject-digest, or subject-checksums may be provided'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,10 +58,17 @@ export const subjectFromInputs = async (
|
||||
// to conform to OCI image naming conventions
|
||||
const name = downcaseName ? subjectName.toLowerCase() : subjectName
|
||||
|
||||
if (subjectPath) {
|
||||
return await getSubjectFromPath(subjectPath, name)
|
||||
} else {
|
||||
return [getSubjectFromDigest(subjectDigest, name)]
|
||||
switch (true) {
|
||||
case !!subjectPath:
|
||||
return getSubjectFromPath(subjectPath, name)
|
||||
case !!subjectDigest:
|
||||
return [getSubjectFromDigest(subjectDigest, name)]
|
||||
case !!subjectChecksums:
|
||||
return getSubjectFromChecksums(subjectChecksums)
|
||||
/* istanbul ignore next */
|
||||
default:
|
||||
// This should be unreachable, but TS requires a default case
|
||||
assert.fail('unreachable')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +88,9 @@ const getSubjectFromPath = async (
|
||||
const digestedSubjects: Subject[] = []
|
||||
|
||||
// Parse the list of subject paths
|
||||
const subjectPaths = parseList(subjectPath).join('\n')
|
||||
const subjectPaths = parseSubjectPathList(subjectPath).join('\n')
|
||||
|
||||
// Expand the globbed paths to a list of actual paths
|
||||
/* eslint-disable-next-line github/no-then */
|
||||
const paths = await glob.create(subjectPaths).then(async g => g.glob())
|
||||
|
||||
// Filter path list to just the files (not directories)
|
||||
@@ -84,7 +106,14 @@ const getSubjectFromPath = async (
|
||||
const name = subjectName || path.parse(file).base
|
||||
const digest = await digestFile(DIGEST_ALGORITHM, file)
|
||||
|
||||
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
|
||||
// Only add the subject if it is not already in the list
|
||||
if (
|
||||
!digestedSubjects.some(
|
||||
s => s.name === name && s.digest[DIGEST_ALGORITHM] === digest
|
||||
)
|
||||
) {
|
||||
digestedSubjects.push({ name, digest: { [DIGEST_ALGORITHM]: digest } })
|
||||
}
|
||||
}
|
||||
|
||||
if (digestedSubjects.length === 0) {
|
||||
@@ -113,6 +142,62 @@ const getSubjectFromDigest = (
|
||||
}
|
||||
}
|
||||
|
||||
const getSubjectFromChecksums = (subjectChecksums: string): Subject[] => {
|
||||
if (fs.existsSync(subjectChecksums)) {
|
||||
return getSubjectFromChecksumsFile(subjectChecksums)
|
||||
} else {
|
||||
return getSubjectFromChecksumsString(subjectChecksums)
|
||||
}
|
||||
}
|
||||
|
||||
const getSubjectFromChecksumsFile = (checksumsPath: string): Subject[] => {
|
||||
const stats = fs.statSync(checksumsPath)
|
||||
if (!stats.isFile()) {
|
||||
throw new Error(`subject checksums file not found: ${checksumsPath}`)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (stats.size > MAX_SUBJECT_CHECKSUM_SIZE_BYTES) {
|
||||
throw new Error(
|
||||
`subject checksums file exceeds maximum allowed size: ${MAX_SUBJECT_CHECKSUM_SIZE_BYTES} bytes`
|
||||
)
|
||||
}
|
||||
|
||||
const checksums = fs.readFileSync(checksumsPath, 'utf-8')
|
||||
return getSubjectFromChecksumsString(checksums)
|
||||
}
|
||||
|
||||
const getSubjectFromChecksumsString = (checksums: string): Subject[] => {
|
||||
const subjects: Subject[] = []
|
||||
|
||||
const records: string[] = checksums.split(os.EOL).filter(Boolean)
|
||||
|
||||
for (const record of records) {
|
||||
// Find the space delimiter following the digest
|
||||
const delimIndex = record.indexOf(' ')
|
||||
|
||||
// Skip any line that doesn't have a delimiter
|
||||
if (delimIndex === -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Swallow the type identifier character at the beginning of the name
|
||||
const name = record.slice(delimIndex + 2)
|
||||
const digest = record.slice(0, delimIndex)
|
||||
|
||||
if (!HEX_STRING_RE.test(digest)) {
|
||||
throw new Error(`Invalid digest: ${digest}`)
|
||||
}
|
||||
|
||||
subjects.push({
|
||||
name,
|
||||
digest: { [digestAlgorithm(digest)]: digest }
|
||||
})
|
||||
}
|
||||
|
||||
return subjects
|
||||
}
|
||||
|
||||
// Calculates the digest of a file using the specified algorithm. The file is
|
||||
// streamed into the digest function to avoid loading the entire file into
|
||||
// memory. The returned digest is a hex string.
|
||||
@@ -129,7 +214,7 @@ const digestFile = async (
|
||||
})
|
||||
}
|
||||
|
||||
const parseList = (input: string): string[] => {
|
||||
const parseSubjectPathList = (input: string): string[] => {
|
||||
const res: string[] = []
|
||||
|
||||
const records: string[][] = parse(input, {
|
||||
@@ -145,3 +230,14 @@ const parseList = (input: string): string[] => {
|
||||
|
||||
return res.filter(item => item).map(pat => pat.trim())
|
||||
}
|
||||
|
||||
const digestAlgorithm = (digest: string): string => {
|
||||
switch (digest.length) {
|
||||
case 64:
|
||||
return 'sha256'
|
||||
case 128:
|
||||
return 'sha512'
|
||||
default:
|
||||
throw new Error(`Unknown digest algorithm: ${digest}`)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user