Add a GitHub Action for installing slsa-verifier. (#246)

This commit is contained in:
kpk47
2022-09-22 18:58:40 +00:00
committed by GitHub
parent aa75f1b7bb
commit b9c3c9d87e
17 changed files with 19558 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
name: Actions pre submits
on: [pull_request, workflow_dispatch]
permissions: read-all
jobs:
action-presubmit:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2
- name: Setup Node.js 16
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3.4.1
with:
node-version: 16
- name: Install dependencies
working-directory: actions/installer
run: npm ci
- name: Run build and tests
working-directory: actions/installer
run: npm run all
- name: Compare the expected and actual dist/ directories
working-directory: actions/installer
id: diff
run: |
set -euo pipefail
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
# If there are uncommited changes in dist/, upload the directory
- name: Upload dist/
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3.1.0
with:
name: dist
path: dist/
+3
View File
@@ -0,0 +1,3 @@
dist/
lib/
node_modules/
+53
View File
@@ -0,0 +1,53 @@
{
"plugins": ["@typescript-eslint"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"i18n-text/no-en": "off",
"eslint-comments/no-use": "off",
"import/no-namespace": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-comment": "error",
"camelcase": "off",
"@typescript-eslint/consistent-type-assertions": "error",
"@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-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "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",
"semi": "off",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unbound-method": "error"
},
"env": {
"node": true,
"es6": true
}
}
+2
View File
@@ -0,0 +1,2 @@
node_modules
lib
+3
View File
@@ -0,0 +1,3 @@
dist/
lib/
node_modules/
+21
View File
@@ -0,0 +1,21 @@
# slsa-verifier setup GitHub Action
This action installs the SLSA verifier and adds it to your PATH.
For more information about `slsa-verifier`, refer to [its documentation](https://github.com/slsa-framework/slsa-verifier#verification-of-provenance).
For more information about SLSA in general, see [https://slsa.dev](https://slsa.dev).
## Usage
To install a specific version of `slsa-verifier`, use:
```yaml
uses: slsa-framework/slsa-verifier-installer@v1.3.0
```
See https://github.com/slsa-framework/slsa-verifier/releases for the list of available `slsa-verifier` releases.
For a full example workflow, see [../../.github/workflows/pre-submit.actions.yml](https://github.com/slsa-framework/slsa-verifier/.github/workflows/pre-submit.actions.yml).
This action requires using GitHub-provided Linux runners.
+48
View File
@@ -0,0 +1,48 @@
# Copyright 2022 SLSA Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: 'Install slsa-verifier'
description: 'Installs SLSA verifier and adds it to your PATH'
branding:
icon: 'package'
color: 'blue'
runs:
using: 'composite'
steps:
- name: Checkout
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2
- name: Setup Node.js 16
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3.4.1
with:
node-version: 16
- name: Install dependencies
working-directory: actions/installer
shell: bash
run: npm ci
- name: Run build
working-directory: actions/installer
shell: bash
run: npm run build
- name: Run installer
env:
ACTION_REF: "${{ github.action_ref }}"
TOKEN: "${{ github.token }}"
REPOSITORY: "${{ github.repository }}"
working-directory: actions/installer/dist
shell: bash
run: nodejs index.js
+13756
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5297
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
{
"name": "setup-slsa-verifier",
"version": "0.0.1",
"description": "",
"main": "lib/index.js",
"scripts": {
"compile": "tsc",
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"test": "jasmine",
"package": "ncc build --source-map",
"lint": "eslint src/**/*.ts",
"build": "npm run compile && npm run package",
"all": "npm run compile && npm run format && npm run lint && npm run test && npm run package"
},
"dependencies": {
"@actions/core": "^1.9.1",
"@actions/exec": "^1.1.1",
"@actions/github": "^5.0.3",
"@actions/io": "^1.1.2",
"@actions/tool-cache": "^2.0.1"
},
"devDependencies": {
"@types/jasmine": "^4.3.0",
"@types/node": "^18.7.13",
"@vercel/ncc": "^0.34.0",
"eslint": "^8.23.0",
"eslint-plugin-github": "^4.3.7",
"jasmine": "^4.3.0",
"prettier": "^2.7.1",
"typescript": "^4.8.2"
}
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const exec = require("@actions/exec");
const index = require("../lib/index");
const fs = require("fs");
describe("Test validVersion", function () {
it("Accepts v0.0.1", function () {
expect(index.validVersion("v1.0.0")).toBe(true);
});
it("Accepts v1.2.0", function () {
expect(index.validVersion("v1.0.0")).toBe(true);
});
it("Rejects foobar", function () {
expect(index.validVersion("foobar")).toBe(false);
});
it("Rejects commit hashes", function () {
expect(
index.validVersion(
"1326430d044e8a9522c51e5f721e237b5f75acb6b4e518d129f669403cf7a79a"
)
).toBe(false);
});
});
describe("Test fileHasExpectedSha256Hash", function () {
it("throws an error when the file does not exist", function () {
expect(function () {
index.fileHasExpectedSha256Hash("/path/to/nowhere", "12345");
}).toThrow();
});
describe("Tests accessing real files", function () {
beforeEach(function () {
this.tmpDir = fs.mkdtempSync("jasmine");
this.testFile = `${this.tmpDir}/testfile`;
fs.writeFileSync(this.testFile, "test data");
});
afterEach(function () {
fs.rmSync(this.tmpDir, { recursive: true });
});
it("Returns false when the computed and expected hashes don't match", function () {
expect(index.fileHasExpectedSha256Hash(this.testFile, "foobar")).toBe(
false
);
});
it("Returns true when the computed and expected hashes don't match", function () {
expect(
index.fileHasExpectedSha256Hash(
this.testFile,
"916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"
)
).toBe(true);
});
});
});
@@ -0,0 +1,13 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*.test.?(m)ts"
],
"helpers": [
"helpers/**/*.?(m)js"
],
"env": {
"stopSpecOnExpectationFailure": false,
"random": true
}
}
+202
View File
@@ -0,0 +1,202 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import * as github from "@actions/github";
import * as io from "@actions/io";
import * as tc from "@actions/tool-cache";
import * as crypto from "crypto";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
const BOOTSTRAP_VERSION = "v1.3.0";
const BOOTSTRAP_VERIFIER_SHA256 =
"1326430d044e8a9522c51e5f721e237b5f75acb6b4e518d129f669403cf7a79a";
const BINARY_NAME = "slsa-verifier";
const PROVENANCE_NAME = "slsa-verifier-linux-amd64.intoto.jsonl";
// If true, the input string conforms to slsa-verifier's versioning system.
export function validVersion(version: string): boolean {
const re = /(v[0-9]+\.[0-9]+\.[0-9]+)/;
return re.test(version);
}
// Resolve command line argument to a version number
export async function getVerifierVersion(actionRef: string): Promise<string> {
if (validVersion(actionRef)) {
return actionRef;
}
// If actionRef is a commit SHA, then find the associated version number.
const shaRe = /^[a-f\d]{40}$/;
if (shaRe.test(actionRef)) {
const octokit = github.getOctokit(process.env.TOKEN || "");
const { data: releases } = await octokit.request(
"GET /repos/{repository}/releases",
{
repository: process.env.REPOSITORY,
}
);
for (const release of releases) {
const { data: commit } = await octokit.request(
"GET /reps/{repository}/git/ref/tags/{tagName}",
{
repository: process.env.REPOSITORY,
tagName: release.tag_name,
}
);
const commitSha = commit.object.sha;
if (commitSha === actionRef) {
return release.tag_name;
}
}
}
throw new Error(
`Invalid version provided: ${actionRef}. For the set of valid versions, see https://github.com/slsa-framework/slsa-verifier/releases.`
);
}
// If true, then the file in `path` has the same SHA256 hash as `expectedSha256Hash``.
export function fileHasExpectedSha256Hash(
filePath: string,
expectedSha256Hash: string
): boolean {
if (!fs.existsSync(filePath)) {
throw new Error(`File not found: ${filePath}`);
}
const untrustedFile = fs.readFileSync(filePath);
const computedSha256Hash = crypto
.createHash("sha256")
.update(untrustedFile)
.digest("hex");
return computedSha256Hash === expectedSha256Hash;
}
let tmpDir: string;
// Delete bootstrap version and maybe installed version
async function cleanup(): Promise<void> {
await io.rmRF(`${tmpDir}`);
}
async function run(): Promise<void> {
// Get requested verifier version and validate
const actionRef = process.env.ACTION_REF || "";
let version: string;
try {
version = await getVerifierVersion(actionRef);
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
core.setFailed(
`Invalid version provided. For the set of valid versions, see https://github.com/slsa-framework/slsa-verifier/releases. ${errMsg}`
);
cleanup();
return;
}
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "slsa-verifier_"));
const bootstrapDir = `${tmpDir}/bootstrap`;
const installDir = `${tmpDir}/${version}`;
let bootstrapVerifierPath;
try {
// Download bootstrap version and validate SHA256 checksum
bootstrapVerifierPath = await tc.downloadTool(
`https://github.com/slsa-framework/slsa-verifier/releases/download/${BOOTSTRAP_VERSION}/slsa-verifier-linux-amd64`,
`${bootstrapDir}/${BINARY_NAME}`
);
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
core.setFailed(`Error downloading bootstrap slsa-verifier: ${errMsg}`);
cleanup();
return;
}
if (
!fileHasExpectedSha256Hash(bootstrapVerifierPath, BOOTSTRAP_VERIFIER_SHA256)
) {
core.setFailed(
`Unable to verify slsa-verifier checksum. Aborting installation.`
);
cleanup();
return;
}
fs.chmodSync(bootstrapVerifierPath, 0o100);
let downloadedBinaryPath;
try {
// Download requested version binary and provenance
downloadedBinaryPath = await tc.downloadTool(
`https://github.com/slsa-framework/slsa-verifier/releases/download/${version}/slsa-verifier-linux-amd64`,
`${installDir}/${BINARY_NAME}`
);
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
core.setFailed(`Error downloading slsa-verifier: ${errMsg}`);
cleanup();
return;
}
let downloadedProvenancePath;
try {
downloadedProvenancePath = await tc.downloadTool(
`https://github.com/slsa-framework/slsa-verifier/releases/download/${version}/slsa-verifier-linux-amd64.intoto.jsonl`,
`${installDir}/${PROVENANCE_NAME}`
);
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
core.setFailed(`Error downloading binary provenance: ${errMsg}`);
cleanup();
return;
}
// Validate binary provenance
try {
const { exitCode, stdout, stderr } = await exec.getExecOutput(
`${bootstrapVerifierPath}`,
[
`-artifact-path=${downloadedBinaryPath}`,
`-provenance=${downloadedProvenancePath}`,
`-source=github.com/slsa-framework/slsa-verifier`,
`-tag=${version}`,
]
);
if (exitCode !== 0) {
throw new Error(
`Unable to verify binary provenance. Aborting installation. stdout: ${stdout}; stderr: ${stderr}`
);
}
} catch (error: unknown) {
const errMsg = error instanceof Error ? error.message : String(error);
core.setFailed(`Error executing slsa-verifier: ${errMsg}`);
cleanup();
return;
}
// Copy requested version to HOME directory.
const finalDir = `${os.homedir()}/.slsa/bin/${version}`;
const finalPath = `${finalDir}/${BINARY_NAME}`;
fs.mkdirSync(finalDir, { recursive: true });
fs.copyFileSync(downloadedBinaryPath, finalPath);
fs.chmodSync(finalPath, 0o100);
core.addPath(finalDir);
core.setOutput("verifier-path", finalDir);
cleanup();
}
run();
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./lib" , /* Redirect output structure to the directory. */
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
},
"exclude": ["node_modules", "**/*.test.ts"]
}