Skip to content

Commit c182aab

Browse files
RTDEV-58178 - Collect GitHub attestation into JFrog Artifactory at the end of the workflow
1 parent 0775fb3 commit c182aab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1040
-1375
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
.vscode
44
*.iml
55

6-
node_modules/.package-lock.json
6+
node_modules
77
__tests__/runner/*
88

99
# Vim
1010
*~
1111
*.swp
1212

1313
# IOS
14-
*.DS_Store
14+
*.DS_Store

.husky/pre-commit

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
#!/bin/sh
22
. "$(dirname "$0")/_/husky.sh"
33

4-
npm i
5-
npm prune --production
6-
git add node_modules

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ inputs:
2323
description: "By default, if the workflow completes with collected build-info that has not been published using the jf rt build-publish command, the build-info will be automatically published to Artifactory. Set this to true to disable the automatic publication of build-info at the end of the workflow."
2424
default: "false"
2525
required: false
26+
disable-auto-evidence-collection:
27+
description: "Set to true to disable the automatic collection of evidence at the end of the workflow."
28+
default: "false"
29+
required: false
2630
custom-server-id:
2731
description: "Custom JFrog CLI configuration server ID to use instead of the default one generated by the action."
2832
required: false

lib/cleanup.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
3232
});
3333
};
3434
Object.defineProperty(exports, "__esModule", { value: true });
35+
exports.checkConnectionToArtifactory = checkConnectionToArtifactory;
3536
const core = __importStar(require("@actions/core"));
3637
const utils_1 = require("./utils");
3738
const job_summary_1 = require("./job-summary");
39+
const evidence_collection_1 = require("./evidence-collection");
3840
function cleanup() {
3941
return __awaiter(this, void 0, void 0, function* () {
4042
if (yield shouldSkipCleanup()) {
4143
return;
4244
}
43-
// Run post tasks related to Build Info (auto build publish, job summary)
45+
// Run post tasks related to Build Info (auto build publish, evidence collection, job summary)
4446
yield buildInfoPostTasks();
4547
// Cleanup JFrog CLI servers configuration
4648
try {
@@ -59,17 +61,19 @@ function cleanup() {
5961
* Executes post tasks related to build information.
6062
*
6163
* This function performs several tasks after the main build process:
62-
* 1. Checks if auto build publish and job summary are disabled.
64+
* 1. Checks if auto build publish, evidence collection, and job summary are disabled.
6365
* 2. Verifies connection to JFrog Artifactory.
6466
* 3. Collects and publishes build information if needed.
65-
* 4. Generates a job summary if required.
67+
* 4. Collects evidences if enabled.
68+
* 5. Generates a job summary if required.
6669
*/
6770
function buildInfoPostTasks() {
6871
return __awaiter(this, void 0, void 0, function* () {
6972
const disableAutoBuildPublish = core.getBooleanInput(utils_1.Utils.AUTO_BUILD_PUBLISH_DISABLE);
73+
const disableAutoEvidenceCollection = core.getBooleanInput(utils_1.Utils.AUTO_EVIDENCE_COLLECTION_DISABLE);
7074
const disableJobSummary = core.getBooleanInput(utils_1.Utils.JOB_SUMMARY_DISABLE) || !job_summary_1.JobSummary.isJobSummarySupported();
71-
if (disableAutoBuildPublish && disableJobSummary) {
72-
core.info(`Both auto-build-publish and job-summary are disabled. Skipping Build Info post tasks.`);
75+
if (disableAutoBuildPublish && disableAutoEvidenceCollection && disableJobSummary) {
76+
core.info(`All post tasks (auto-build-publish, evidence collection, and job-summary) are disabled. Skipping Build Info post tasks.`);
7377
return;
7478
}
7579
// Check connection to Artifactory before proceeding with build info post tasks
@@ -83,6 +87,13 @@ function buildInfoPostTasks() {
8387
else {
8488
core.info('Auto build info publish is disabled. Skipping auto build info collection and publishing');
8589
}
90+
// Collect evidences if not disabled
91+
if (!disableAutoEvidenceCollection) {
92+
yield (0, evidence_collection_1.collectEvidences)();
93+
}
94+
else {
95+
core.info('Auto evidence collection is disabled. Skipping evidence collection');
96+
}
8697
// Generate job summary if not disabled and the JFrog CLI version supports it
8798
if (!disableJobSummary) {
8899
yield generateJobSummary();

lib/evidence-collection.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"use strict";
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
var desc = Object.getOwnPropertyDescriptor(m, k);
5+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+
desc = { enumerable: true, get: function() { return m[k]; } };
7+
}
8+
Object.defineProperty(o, k2, desc);
9+
}) : (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
o[k2] = m[k];
12+
}));
13+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+
Object.defineProperty(o, "default", { enumerable: true, value: v });
15+
}) : function(o, v) {
16+
o["default"] = v;
17+
});
18+
var __importStar = (this && this.__importStar) || function (mod) {
19+
if (mod && mod.__esModule) return mod;
20+
var result = {};
21+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22+
__setModuleDefault(result, mod);
23+
return result;
24+
};
25+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27+
return new (P || (P = Promise))(function (resolve, reject) {
28+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31+
step((generator = generator.apply(thisArg, _arguments || [])).next());
32+
});
33+
};
34+
Object.defineProperty(exports, "__esModule", { value: true });
35+
exports.collectEvidences = collectEvidences;
36+
const core = __importStar(require("@actions/core"));
37+
const utils_1 = require("./utils");
38+
const http_client_1 = require("@actions/http-client");
39+
const fs_1 = require("fs");
40+
const path = __importStar(require("path"));
41+
/**
42+
* Collects evidences from the current workflow.
43+
* This function first checks if evidence collection is supported by the Artifactory server.
44+
*/
45+
function collectEvidences() {
46+
return __awaiter(this, void 0, void 0, function* () {
47+
try {
48+
core.startGroup('Collecting evidences');
49+
// Check if evidence collection is supported by the server
50+
const evidenceConfig = yield getEvidenceConfiguration();
51+
if (!evidenceConfig.external_evidence_collection_supported) {
52+
core.info('Evidence collection is not supported by this Artifactory server. Skipping evidence collection.');
53+
return;
54+
}
55+
core.info(`Evidence collection is supported. Maximum file size: ${evidenceConfig.max_evidence_file_size_mb} MB`);
56+
// Read sigstore bundle file paths and create evidence for each
57+
yield createEvidenceFromSigstoreBundles(evidenceConfig.max_evidence_file_size_mb);
58+
}
59+
catch (error) {
60+
core.warning('Failed while attempting to collect evidences: ' + error);
61+
}
62+
finally {
63+
core.endGroup();
64+
}
65+
});
66+
}
67+
/**
68+
* Checks if evidence collection is supported by the Artifactory server.
69+
* @returns EvidenceConfigResponse with support status and max file size
70+
*/
71+
function getEvidenceConfiguration() {
72+
return __awaiter(this, void 0, void 0, function* () {
73+
const credentials = utils_1.Utils.collectJfrogCredentialsFromEnvVars();
74+
if (!credentials.jfrogUrl) {
75+
throw new Error('JF_URL is required to check evidence support');
76+
}
77+
// Get access token for authentication
78+
let accessToken = credentials.accessToken;
79+
if (!accessToken && credentials.oidcProviderName) {
80+
// Import OidcUtils dynamically to avoid circular dependency
81+
const { OidcUtils } = yield Promise.resolve().then(() => __importStar(require('./oidc-utils')));
82+
accessToken = yield OidcUtils.exchangeOidcToken(credentials);
83+
}
84+
if (!accessToken) {
85+
throw new Error('No access token available for authentication');
86+
}
87+
// Remove trailing slash from jfrogUrl to avoid double slashes when appending the API path
88+
const url = `${credentials.jfrogUrl.replace(/\/$/, '')}/evidence/api/v1/config/`;
89+
const httpClient = new http_client_1.HttpClient();
90+
const headers = {
91+
'Authorization': `Bearer ${accessToken}`,
92+
};
93+
core.debug(`Getting evidence configuration at: ${url}`);
94+
let response;
95+
let body;
96+
try {
97+
response = yield httpClient.get(url, headers);
98+
body = yield response.readBody();
99+
}
100+
catch (error) {
101+
core.warning(`Failed to get evidence configuration (network error or server unavailable): ${error}`);
102+
return { external_evidence_collection_supported: false, max_evidence_file_size_mb: 0 };
103+
}
104+
if (response.message.statusCode !== 200) {
105+
core.warning(`Failed to get evidence configuration. Status: ${response.message.statusCode}, Response: ${body}`);
106+
return { external_evidence_collection_supported: false, max_evidence_file_size_mb: 0 };
107+
}
108+
try {
109+
const config = JSON.parse(body);
110+
return config;
111+
}
112+
catch (error) {
113+
core.warning(`Failed to parse evidence config response: ${error}`);
114+
return { external_evidence_collection_supported: false, max_evidence_file_size_mb: 0 };
115+
}
116+
});
117+
}
118+
/**
119+
* Reads sigstore bundle file paths and creates evidence for each file.
120+
* Reads from ${RUNNER_TEMP}/created_attestation_paths.txt
121+
* @param maxFileSizeMB Maximum allowed file size in MB
122+
*/
123+
function createEvidenceFromSigstoreBundles(maxFileSizeMB) {
124+
return __awaiter(this, void 0, void 0, function* () {
125+
const runnerTemp = process.env.RUNNER_TEMP;
126+
if (!runnerTemp) {
127+
core.warning('RUNNER_TEMP environment variable is not set. Skipping evidence creation.');
128+
return;
129+
}
130+
const attestationPathsFile = path.join(runnerTemp, 'created_attestation_paths.txt');
131+
try {
132+
// Check if the file exists
133+
yield fs_1.promises.access(attestationPathsFile);
134+
}
135+
catch (error) {
136+
core.info(`No attestation paths file found. Skipping evidence creation. Searched for: ${attestationPathsFile}. Error: ${error}`);
137+
return;
138+
}
139+
// Read the file content
140+
core.info(`Reading attestation paths file: ${attestationPathsFile}`);
141+
const fileContent = yield fs_1.promises.readFile(attestationPathsFile, 'utf8');
142+
const filePaths = fileContent.split('\n').filter(line => line.trim() !== '');
143+
if (filePaths.length === 0) {
144+
core.info('No sigstore bundle files found in attestation paths file.');
145+
return;
146+
}
147+
core.info(`Found ${filePaths.length} sigstore bundle file(s) to process.`);
148+
for (const filePath of filePaths) {
149+
try {
150+
const fileStats = yield fs_1.promises.stat(filePath);
151+
const fileSizeMB = fileStats.size / (1024 * 1024); // Convert bytes to MB
152+
if (fileSizeMB > maxFileSizeMB) {
153+
core.warning(`Skipping ${filePath}: File size (${fileSizeMB.toFixed(2)} MB) exceeds maximum allowed size (${maxFileSizeMB} MB)`);
154+
continue;
155+
}
156+
core.info(`Creating evidence for: ${filePath}`);
157+
const output = yield utils_1.Utils.runCliAndGetOutput(['evd', 'create', '--sigstore-bundle', filePath]);
158+
core.info(`Evidence created successfully for ${filePath}: ${output}`);
159+
}
160+
catch (error) {
161+
core.warning(`Failed to create evidence for ${filePath}: ${error}`);
162+
}
163+
}
164+
});
165+
}

lib/utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,5 +471,7 @@ Utils.OIDC_INTEGRATION_PROVIDER_NAME = 'oidc-provider-name';
471471
Utils.JOB_SUMMARY_DISABLE = 'disable-job-summary';
472472
// Disable auto build info publish feature flag
473473
Utils.AUTO_BUILD_PUBLISH_DISABLE = 'disable-auto-build-publish';
474+
// Disable auto evidence collection feature flag
475+
Utils.AUTO_EVIDENCE_COLLECTION_DISABLE = 'disable-auto-evidence-collection';
474476
// Custom server ID input
475477
Utils.CUSTOM_SERVER_ID = 'custom-server-id';

0 commit comments

Comments
 (0)