@docupace/pipeline 1.0.128 → 1.0.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitleaksignore +9 -0
- package/CHANGELOG.md +8 -0
- package/bin/tenable-scan.sh +12 -3
- package/bitbucket-pipelines.yml +91 -0
- package/package.json +5 -2
- package/src/auth0/metadata.js +5 -2
- package/src/auth0/metadata.test.js +5 -2
- package/src/changeset/cli.js +26 -0
- package/src/changeset/validate.js +18 -0
- package/src/next/config.js +3 -1
- package/src/next/connect.js +3 -2
- package/src/next/deploy.js +2 -1
- package/src/next/deploy.test.js +1 -0
- package/src/next/util.js +45 -3
- package/src/tenable/args.js +157 -0
- package/src/tenable/args.test.js +226 -0
- package/src/tenable/cli.js +24 -0
- package/src/tenable/docker.js +45 -0
- package/src/tenable/fsops.js +15 -0
- package/src/tenable/scan.js +284 -0
- package/src/tenable/scan.test.js +355 -0
- package/src/veracode/args.js +67 -0
- package/src/veracode/args.test.js +137 -0
- package/src/veracode/cli.js +16 -0
- package/src/veracode/fsops.js +7 -0
- package/src/veracode/scan.js +145 -0
- package/src/veracode/scan.test.js +293 -0
- package/src/veracode/veracode.js +45 -0
- package/src/xray/cli.js +37 -0
- package/src/xray/notify.js +107 -0
- package/src/xray/notify.test.js +161 -0
- package/src/xray/report.js +194 -0
- package/src/xray/report.test.js +278 -0
- package/src/xray/slack.js +14 -0
- package/testCanny.sh +0 -4
package/.gitleaksignore
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Historical audit trail only. Because the Secrets Scan step clones at
|
|
2
|
+
# depth: 1, gitleaks only ever sees the latest commit — these fingerprints
|
|
3
|
+
# (tied to specific historical commit SHAs) will never actually be re-scanned
|
|
4
|
+
# and do not suppress future findings. The real fix for these two was
|
|
5
|
+
# removing the credential from testCanny.sh; see that commit for details.
|
|
6
|
+
#
|
|
7
|
+
# Canny API key in testCanny.sh, revoked (curl confirmed: "invalid api key").
|
|
8
|
+
2337b1f58f2e5fec0d71ca22305573505c84daee:testCanny.sh:generic-api-key:4
|
|
9
|
+
dfa078fd93eeb339d32ec4067a247195caef24cd:testCanny.sh:generic-api-key:4
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @docupace/pipeline
|
|
2
2
|
|
|
3
|
+
## 1.0.130
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cfa3fe9: Add a shared secrets-scan pipeline definition (importable as pipeline-shared-scripts:master:secrets-scan, also runnable manually via custom.secrets-scan in this repo) that runs Atlassian's git-secrets-scan pipe (gitleaks) with clone depth 1, so only the latest commit is scanned rather than full history
|
|
8
|
+
- cfa3fe9: Add dp-tenable-cli, dp-veracode-cli, and dp-xray-cli Node CLIs porting the Tenable scan, Veracode upload-and-scan, and JFrog Xray Slack-notify bash logic
|
|
9
|
+
- cfa3fe9: Fix setGitInfo to fall back to BITBUCKET_APP_PASSWORD (an API token, paired with the static x-bitbucket-api-token-auth username, matching getRepUrl in next/config.js) when BITBUCKET_STEP_OAUTH_TOKEN/BITBUCKET_TOKEN isn't set, fixing changeset validation failing to authenticate against Bitbucket on runners that rewrite the SSH origin to a credential-less HTTPS URL
|
|
10
|
+
|
|
3
11
|
## 1.0.123
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/bin/tenable-scan.sh
CHANGED
|
@@ -119,8 +119,8 @@ scan_one() {
|
|
|
119
119
|
"--code-commit-hash" "${COMMIT}"
|
|
120
120
|
"--code-commit-user" "${CODE_USER}"
|
|
121
121
|
"--code-repository-url" "${CODE_REPO_URL}"
|
|
122
|
-
"--min-severity" "${MIN_SEVERITY}"
|
|
123
|
-
"--output-file-formats" "
|
|
122
|
+
"--min-severity" "$(printf '%s' "${MIN_SEVERITY}" | tr 'A-Z' 'a-z')"
|
|
123
|
+
"--output-file-formats" "json,md,junit"
|
|
124
124
|
"--output-path" "/tmp/tenable/out"
|
|
125
125
|
"--output-file-name" "${slug}-tenable"
|
|
126
126
|
"--no-color"
|
|
@@ -134,15 +134,24 @@ scan_one() {
|
|
|
134
134
|
)
|
|
135
135
|
fi
|
|
136
136
|
if [[ -n "${TENABLE_FAIL_ON_MIN_SEVERITY:-}" ]]; then
|
|
137
|
-
scan_args+=("--fail-on-min-severity" "${TENABLE_FAIL_ON_MIN_SEVERITY}")
|
|
137
|
+
scan_args+=("--fail-on-min-severity" "$(printf '%s' "${TENABLE_FAIL_ON_MIN_SEVERITY}" | tr 'A-Z' 'a-z')")
|
|
138
138
|
fi
|
|
139
139
|
|
|
140
140
|
echo "Running Tenable scan for '${slug}' (min-severity=${MIN_SEVERITY}${TENABLE_FAIL_ON_MIN_SEVERITY:+, fail-on=${TENABLE_FAIL_ON_MIN_SEVERITY}})..."
|
|
141
|
+
# --entrypoint is not optional: Tenable's own GitHub action builds FROM this image
|
|
142
|
+
# solely to replace the entrypoint with a script that runs `tenable container-image
|
|
143
|
+
# scan "$@"`, so the image's own entrypoint is not that binary. Handing the
|
|
144
|
+
# subcommand to whatever it does start fails silently — exit 0, no output, no
|
|
145
|
+
# reports. The binary also takes its output destination from the environment (the
|
|
146
|
+
# action passes no --output-path flag at all), so it is set both ways.
|
|
141
147
|
docker run --rm \
|
|
142
148
|
--workdir /tmp/tenable \
|
|
143
149
|
--volume "${work}:/tmp/tenable" \
|
|
144
150
|
--env TENABLE_API_TOKEN \
|
|
145
151
|
--env TENABLE_API_URL \
|
|
152
|
+
--env "TENABLE_OUTPUT_PATH=/tmp/tenable/out" \
|
|
153
|
+
--env "TENABLE_OUTPUT_FILE_NAME=${slug}-tenable" \
|
|
154
|
+
--entrypoint "${TENABLE_SCANNER_ENTRYPOINT:-tenable}" \
|
|
146
155
|
--pull=always \
|
|
147
156
|
"${SCANNER_IMAGE}" "${scan_args[@]}"
|
|
148
157
|
local scan_exit=$?
|
package/bitbucket-pipelines.yml
CHANGED
|
@@ -453,6 +453,69 @@ definitions:
|
|
|
453
453
|
[ "${failed}" -ne 0 ] && exit 1
|
|
454
454
|
exit 0
|
|
455
455
|
|
|
456
|
+
# Scans only the latest commit(s) for hardcoded secrets using Atlassian's
|
|
457
|
+
# git-secrets-scan pipe (gitleaks under the hood):
|
|
458
|
+
# import: pipeline-shared-scripts:master:secrets-scan
|
|
459
|
+
#
|
|
460
|
+
# No `runs-on` here deliberately: Bitbucket's Pipes launcher requires a
|
|
461
|
+
# runner in Docker execution mode, which this workspace's on-prem
|
|
462
|
+
# self-hosted runners don't provide (they're shell-mode only) — omitting
|
|
463
|
+
# `runs-on` falls back to Bitbucket's own cloud runners, which do support
|
|
464
|
+
# Pipes.
|
|
465
|
+
#
|
|
466
|
+
# Per https://support.atlassian.com/bitbucket-cloud/kb/how-to-scan-only-the-latest-commits-for-secrets-using-the-git-secrets-scan-pipe/
|
|
467
|
+
# the pipe itself has no "latest commit only" flag — gitleaks' default `git`
|
|
468
|
+
# mode scans the whole history it can see. The scope is controlled entirely
|
|
469
|
+
# by the step's own `clone: depth`, which is why this step overrides the
|
|
470
|
+
# repo-wide default (depth: 2) down to depth: 1: with a depth-1 clone there
|
|
471
|
+
# is only one commit for gitleaks to see, so it scans just that one. This
|
|
472
|
+
# also means the step never manages BITBUCKET_PREVIOUS_COMMIT/HEAD^ ranges
|
|
473
|
+
# itself — a deeper clone here would silently widen the scan.
|
|
474
|
+
#
|
|
475
|
+
# Optional: SECRETS_SCAN_EXTRA_ARGS (extra gitleaks flags, e.g. a custom
|
|
476
|
+
# --gitleaks-ignore-path or --config — appended after the default
|
|
477
|
+
# --redact below, so a repo can add flags without losing redaction; do
|
|
478
|
+
# NOT pass --exit-code 0, that would make the pipe always exit 0 and
|
|
479
|
+
# silently defeat the registry-push gate that depends on this step
|
|
480
|
+
# failing on a real find), SECRETS_SCAN_CREATE_REPORT (default "true" —
|
|
481
|
+
# set "false" to skip the Code Insights report), SECRETS_SCAN_DEBUG
|
|
482
|
+
# (default "false").
|
|
483
|
+
secrets-scan: &def-secrets-scan
|
|
484
|
+
- step:
|
|
485
|
+
name: Secrets Scan (latest commit)
|
|
486
|
+
clone:
|
|
487
|
+
depth: 1
|
|
488
|
+
script:
|
|
489
|
+
# bitbucket-pipelines.yml variable substitution only does a plain
|
|
490
|
+
# ${VAR} lookup (empty string if unset) — it doesn't support shell
|
|
491
|
+
# ${VAR:-default} fallback syntax, so defaults are applied here
|
|
492
|
+
# instead, before the pipe reads them.
|
|
493
|
+
#
|
|
494
|
+
# --redact is on by default: gitleaks-report.json below is kept
|
|
495
|
+
# as a build artifact for triage, and without --redact its
|
|
496
|
+
# `Secret` field would hold the actual matched credential in
|
|
497
|
+
# plain text — turning a transient leak into a persistent,
|
|
498
|
+
# downloadable copy of it for anyone with repo read access.
|
|
499
|
+
- |
|
|
500
|
+
export SECRETS_SCAN_CREATE_REPORT="${SECRETS_SCAN_CREATE_REPORT:-true}"
|
|
501
|
+
export SECRETS_SCAN_DEBUG="${SECRETS_SCAN_DEBUG:-false}"
|
|
502
|
+
export SECRETS_SCAN_EXTRA_ARGS="--redact ${SECRETS_SCAN_EXTRA_ARGS}"
|
|
503
|
+
- pipe: atlassian/git-secrets-scan:3.2.0
|
|
504
|
+
variables:
|
|
505
|
+
GITLEAKS_EXTRA_ARGS: ${SECRETS_SCAN_EXTRA_ARGS}
|
|
506
|
+
CREATE_REPORT: ${SECRETS_SCAN_CREATE_REPORT}
|
|
507
|
+
DEBUG: ${SECRETS_SCAN_DEBUG}
|
|
508
|
+
# Gitleaks writes this report before the pipe attempts its Bitbucket
|
|
509
|
+
# Code Insights upload, so it's captured here even if that upload
|
|
510
|
+
# fails (a known bug in the pipe: it can crash on a 400 from
|
|
511
|
+
# Bitbucket's annotations API for certain file paths) or the step
|
|
512
|
+
# otherwise fails on a real finding. `artifacts:` is collected
|
|
513
|
+
# regardless of step exit code — this is how to see which secrets
|
|
514
|
+
# were found (file, line, rule, fingerprint) with the matched
|
|
515
|
+
# secret value itself redacted by --redact above.
|
|
516
|
+
artifacts:
|
|
517
|
+
- gitleaks-report.json
|
|
518
|
+
|
|
456
519
|
# Runs a SonarQube analysis against the current branch, fire-and-forget
|
|
457
520
|
# (no quality-gate enforcement): import: pipeline-shared-scripts:master:sonar-scan
|
|
458
521
|
#
|
|
@@ -725,11 +788,39 @@ pipelines:
|
|
|
725
788
|
xray-scan: *def-xray-scan
|
|
726
789
|
tenable-scan: *def-tenable-scan
|
|
727
790
|
veracode-scan: *def-veracode-scan
|
|
791
|
+
secrets-scan: *def-secrets-scan
|
|
728
792
|
sonar-scan: *def-sonar-scan
|
|
729
793
|
sonar-pr-gate: *def-sonar-pr-gate
|
|
730
794
|
|
|
731
795
|
pull-requests:
|
|
732
796
|
'**':
|
|
797
|
+
# Inlined rather than aliased to *def-secrets-scan: that anchor is bound
|
|
798
|
+
# to a whole pipeline (a list with one step), and YAML's merge key only
|
|
799
|
+
# splices mappings, not list items, so it can't be spliced into this
|
|
800
|
+
# list. Keep this step's content in sync with the secrets-scan
|
|
801
|
+
# definition above if it changes.
|
|
802
|
+
- step:
|
|
803
|
+
name: Secrets Scan (latest commit)
|
|
804
|
+
# No `runs-on` deliberately — see the secrets-scan definition above
|
|
805
|
+
# for why (Pipes need a Docker-mode runner, which the on-prem
|
|
806
|
+
# runners here don't provide; omitting runs-on falls back to
|
|
807
|
+
# Bitbucket's cloud runners).
|
|
808
|
+
clone:
|
|
809
|
+
depth: 1
|
|
810
|
+
script:
|
|
811
|
+
- |
|
|
812
|
+
export SECRETS_SCAN_CREATE_REPORT="${SECRETS_SCAN_CREATE_REPORT:-true}"
|
|
813
|
+
export SECRETS_SCAN_DEBUG="${SECRETS_SCAN_DEBUG:-false}"
|
|
814
|
+
export SECRETS_SCAN_EXTRA_ARGS="--redact ${SECRETS_SCAN_EXTRA_ARGS}"
|
|
815
|
+
- pipe: atlassian/git-secrets-scan:3.2.0
|
|
816
|
+
variables:
|
|
817
|
+
GITLEAKS_EXTRA_ARGS: ${SECRETS_SCAN_EXTRA_ARGS}
|
|
818
|
+
CREATE_REPORT: ${SECRETS_SCAN_CREATE_REPORT}
|
|
819
|
+
DEBUG: ${SECRETS_SCAN_DEBUG}
|
|
820
|
+
# See the secrets-scan definition above for why this is captured
|
|
821
|
+
# as an artifact regardless of the step's exit code.
|
|
822
|
+
artifacts:
|
|
823
|
+
- gitleaks-report.json
|
|
733
824
|
- step:
|
|
734
825
|
runs-on:
|
|
735
826
|
- self.hosted
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docupace/pipeline",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.131",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"dp-next-cli": "src/next/cli.js",
|
|
13
13
|
"dp-auth0-cli": "src/auth0/cli.js",
|
|
14
14
|
"dp-merge-cli": "src/merge/cli.js",
|
|
15
|
-
"dp-micro-cli": "src/micro/cli.js"
|
|
15
|
+
"dp-micro-cli": "src/micro/cli.js",
|
|
16
|
+
"dp-xray-cli": "src/xray/cli.js",
|
|
17
|
+
"dp-tenable-cli": "src/tenable/cli.js",
|
|
18
|
+
"dp-veracode-cli": "src/veracode/cli.js"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"bash"
|
package/src/auth0/metadata.js
CHANGED
|
@@ -4,11 +4,14 @@ import * as path from 'node:path'; // ESM-compatible import
|
|
|
4
4
|
import { cloneRepository, pushDeployConfig } from '../next/deploy.js';
|
|
5
5
|
import { getAuth0AuthToken } from './configure.js';
|
|
6
6
|
import { getVaultSecrets } from './vault.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
connectDeployRepoName,
|
|
9
|
+
getVaultSecretsConfigs,
|
|
10
|
+
} from '../next/config.js';
|
|
8
11
|
|
|
9
12
|
export async function getAuth0Metadata(site, env, auth0Tenant, jiraTicket) {
|
|
10
13
|
try {
|
|
11
|
-
const connectDeployRepoPath = await cloneRepository(
|
|
14
|
+
const connectDeployRepoPath = await cloneRepository(connectDeployRepoName);
|
|
12
15
|
|
|
13
16
|
const envMap = {
|
|
14
17
|
dev: 'dev',
|
|
@@ -34,6 +34,7 @@ jest.unstable_mockModule('./vault.js', () => ({
|
|
|
34
34
|
|
|
35
35
|
jest.unstable_mockModule('../next/config.js', () => ({
|
|
36
36
|
getVaultSecretsConfigs: jest.fn(),
|
|
37
|
+
connectDeployRepoName: 'dp-eks-connect-ui-gitops',
|
|
37
38
|
}));
|
|
38
39
|
|
|
39
40
|
// ----- Imports AFTER mocks -----
|
|
@@ -43,7 +44,9 @@ const axios = (await import('axios')).default;
|
|
|
43
44
|
const { cloneRepository, pushDeployConfig } = await import('../next/deploy.js');
|
|
44
45
|
const { getAuth0AuthToken } = await import('./configure.js');
|
|
45
46
|
const { getVaultSecrets } = await import('./vault.js');
|
|
46
|
-
const { getVaultSecretsConfigs } = await import(
|
|
47
|
+
const { getVaultSecretsConfigs, connectDeployRepoName } = await import(
|
|
48
|
+
'../next/config.js'
|
|
49
|
+
);
|
|
47
50
|
|
|
48
51
|
// ----- Shared Setup -----
|
|
49
52
|
beforeAll(() => {
|
|
@@ -113,7 +116,7 @@ describe('getAuth0Metadata', () => {
|
|
|
113
116
|
|
|
114
117
|
await getAuth0Metadata(site, env, auth0Tenant);
|
|
115
118
|
|
|
116
|
-
expect(cloneRepository).toHaveBeenCalledWith(
|
|
119
|
+
expect(cloneRepository).toHaveBeenCalledWith(connectDeployRepoName);
|
|
117
120
|
expect(fs.readFile).toHaveBeenCalledWith('conf/application.conf', 'utf-8');
|
|
118
121
|
expect(fs.ensureDir).toHaveBeenCalledWith('conf/saml');
|
|
119
122
|
expect(fs.writeFile).toHaveBeenCalledWith(
|
package/src/changeset/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { $ } from 'zx';
|
|
4
|
+
import path from 'path';
|
|
4
5
|
import { validateChangesets } from './validate.js';
|
|
5
6
|
import dotenv from 'dotenv';
|
|
6
7
|
import { isMonoRepo, setGitInfo } from '../next/util.js';
|
|
@@ -19,6 +20,26 @@ const program = new Command();
|
|
|
19
20
|
|
|
20
21
|
const args = process.argv;
|
|
21
22
|
|
|
23
|
+
async function runPostVersionScript(scriptPath, releases) {
|
|
24
|
+
if (!scriptPath || !releases?.length) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const resolvedScriptPath = path.resolve(process.cwd(), scriptPath);
|
|
29
|
+
console.log(`📌 Running post-version script: ${resolvedScriptPath}`);
|
|
30
|
+
|
|
31
|
+
if (isMonoRepo()) {
|
|
32
|
+
for (const release of getAppReleases(releases)) {
|
|
33
|
+
await $`node ${resolvedScriptPath} ${release.newVersion}`;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
const newVersion = releases[0]?.newVersion;
|
|
37
|
+
if (newVersion) {
|
|
38
|
+
await $`node ${resolvedScriptPath} ${newVersion}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
async function commitTagCommand(releases) {
|
|
23
44
|
console.log('📌 Checking for updates to commit...');
|
|
24
45
|
|
|
@@ -157,12 +178,17 @@ program
|
|
|
157
178
|
'--install-latest',
|
|
158
179
|
'Install the latest version of @docupace/pipeline instead of the current version',
|
|
159
180
|
)
|
|
181
|
+
.option(
|
|
182
|
+
'--post-version-script <path>',
|
|
183
|
+
'Path to a JS file to run after the version is bumped but before it is committed, tagged, and pushed. Called with the new version as its first argument.',
|
|
184
|
+
)
|
|
160
185
|
.action(async (options) => {
|
|
161
186
|
console.log('📦 Running all steps...');
|
|
162
187
|
try {
|
|
163
188
|
const releases = await versionCommand(false, options.installLatest);
|
|
164
189
|
await updateJiraWithVersion(releases);
|
|
165
190
|
await updateWikiWithVersion(releases);
|
|
191
|
+
await runPostVersionScript(options.postVersionScript, releases);
|
|
166
192
|
await commitTagCommand(releases);
|
|
167
193
|
} catch (error) {
|
|
168
194
|
console.error('❌ Version-commit-tag failed:', error);
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import { $ } from 'zx';
|
|
4
5
|
|
|
5
6
|
import { setGitInfo } from '../next/util.js';
|
|
6
7
|
import { versionStatus } from './version.js';
|
|
7
8
|
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(
|
|
10
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
11
|
+
'..',
|
|
12
|
+
'..',
|
|
13
|
+
);
|
|
14
|
+
const PACKAGE_VERSION = JSON.parse(
|
|
15
|
+
fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'),
|
|
16
|
+
).version;
|
|
17
|
+
|
|
8
18
|
export async function validateChangesets() {
|
|
19
|
+
console.log(
|
|
20
|
+
`validateChangesets: @docupace/pipeline version = ${PACKAGE_VERSION}`,
|
|
21
|
+
);
|
|
9
22
|
await setGitInfo();
|
|
10
23
|
const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
24
|
+
console.log(`validateChangesets: destBranch = ${destBranch}`);
|
|
25
|
+
console.log(
|
|
26
|
+
`validateChangesets: fetching origin ${destBranch}:${destBranch}`,
|
|
27
|
+
);
|
|
11
28
|
await $`git fetch origin ${destBranch}:${destBranch}`;
|
|
29
|
+
console.log('validateChangesets: fetch succeeded');
|
|
12
30
|
let releases;
|
|
13
31
|
|
|
14
32
|
try {
|
package/src/next/config.js
CHANGED
|
@@ -144,7 +144,7 @@ export const getBitbucketInfo = () => {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
export const getRepUrl = (repoName) => {
|
|
147
|
-
const {
|
|
147
|
+
const { password, workspace } = getBitbucketInfo();
|
|
148
148
|
return `https://x-bitbucket-api-token-auth:${password}@bitbucket.org/${workspace}/${repoName}.git`;
|
|
149
149
|
};
|
|
150
150
|
|
|
@@ -172,3 +172,5 @@ export const coreSiteEnvMap = {
|
|
|
172
172
|
};
|
|
173
173
|
|
|
174
174
|
export const autoDeployConfigName = 'auto-deploy.yaml';
|
|
175
|
+
|
|
176
|
+
export const connectDeployRepoName = 'dp-eks-connect-ui-gitops';
|
package/src/next/connect.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { $ } from 'zx';
|
|
5
5
|
import { getVaultSecrets } from '../auth0/vault.js';
|
|
6
|
+
import { connectDeployRepoName } from './config.js';
|
|
6
7
|
|
|
7
8
|
process.env.COMMON_VAULT_PATH =
|
|
8
9
|
process.env.COMMON_VAULT_PATH || 'secret/data/common/config';
|
|
@@ -109,9 +110,9 @@ export async function connectClone() {
|
|
|
109
110
|
process.env.AUTH0_DOMAIN = `docupace-${idpEnvMap[process.env.BUILD_ENVIRONMENT]}-${process.env.BUILD_SITE}.${process.env.AUTH0_REGION_DOMAIN}`;
|
|
110
111
|
|
|
111
112
|
const projectRoot = process.cwd();
|
|
112
|
-
// 4) Clone
|
|
113
|
+
// 4) Clone connect deploy repo into CONNECT_DIR
|
|
113
114
|
|
|
114
|
-
await gitCheckout(
|
|
115
|
+
await gitCheckout(connectDeployRepoName, process.env.CONNECT_DIR);
|
|
115
116
|
|
|
116
117
|
await gitCheckout(
|
|
117
118
|
`${siteSettingsEnvMap[connectToEnv]}-site-settings`,
|
package/src/next/deploy.js
CHANGED
|
@@ -7,6 +7,7 @@ import semver from 'semver';
|
|
|
7
7
|
import { $ } from 'zx';
|
|
8
8
|
import {
|
|
9
9
|
appInfo,
|
|
10
|
+
connectDeployRepoName,
|
|
10
11
|
environmentConfig,
|
|
11
12
|
getDefaultAppInfo,
|
|
12
13
|
getRepUrl,
|
|
@@ -1188,7 +1189,7 @@ export async function deployFromSite(
|
|
|
1188
1189
|
);
|
|
1189
1190
|
if (getComparedVersion) {
|
|
1190
1191
|
console.log('Version to deploy is: ', getComparedVersion);
|
|
1191
|
-
const connectDeployPath = await cloneRepository(
|
|
1192
|
+
const connectDeployPath = await cloneRepository(connectDeployRepoName);
|
|
1192
1193
|
// update target version set to git-commit-${getCommparedVersion}}
|
|
1193
1194
|
await updateAndCommitConnectVersion(
|
|
1194
1195
|
toEnv,
|
package/src/next/deploy.test.js
CHANGED
package/src/next/util.js
CHANGED
|
@@ -84,12 +84,54 @@ export async function getChangedAppPackages() {
|
|
|
84
84
|
return appPackages;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function maskCreds(url) {
|
|
88
|
+
return url.replace(/:[^:@/]+@/, ':***@');
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
export async function setGitInfo() {
|
|
92
|
+
console.log(
|
|
93
|
+
`setGitInfo: SET_GITOPS_USER=${process.env.SET_GITOPS_USER || '(unset)'}`,
|
|
94
|
+
);
|
|
88
95
|
if (process.env.SET_GITOPS_USER) {
|
|
96
|
+
const before = (await $`git remote get-url origin`).stdout.trim();
|
|
97
|
+
console.log(`setGitInfo: origin before = ${maskCreds(before)}`);
|
|
98
|
+
|
|
89
99
|
await $`git config --global user.email "gitops@docupace.com"`;
|
|
90
100
|
await $`git config --global user.name "Gitops"`;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
101
|
+
|
|
102
|
+
// Some self-hosted runners rewrite `git@bitbucket.org:` (SSH) to a bare,
|
|
103
|
+
// credential-less `https://bitbucket.org/` via a git `insteadOf` rule, so an
|
|
104
|
+
// SSH-form origin silently loses its identity at fetch/push time. Use an
|
|
105
|
+
// HTTPS URL with the token embedded instead — insteadOf doesn't touch it.
|
|
106
|
+
// Not every step has BITBUCKET_STEP_OAUTH_TOKEN (it depends on step/repo
|
|
107
|
+
// OAuth config), so fall back to BITBUCKET_APP_PASSWORD (an API token,
|
|
108
|
+
// despite the name — app passwords are deprecated) paired with the
|
|
109
|
+
// static x-bitbucket-api-token-auth username, matching getRepUrl() in
|
|
110
|
+
// ../next/config.js.
|
|
111
|
+
const token =
|
|
112
|
+
process.env.BITBUCKET_STEP_OAUTH_TOKEN || process.env.BITBUCKET_TOKEN;
|
|
113
|
+
const apiToken = process.env.BITBUCKET_APP_PASSWORD;
|
|
114
|
+
let repoUrl;
|
|
115
|
+
if (token) {
|
|
116
|
+
repoUrl = `https://x-token-auth:${token}@bitbucket.org/dp-cloud/${process.env.BITBUCKET_REPO_SLUG}.git`;
|
|
117
|
+
} else if (apiToken) {
|
|
118
|
+
repoUrl = `https://x-bitbucket-api-token-auth:${apiToken}@bitbucket.org/dp-cloud/${process.env.BITBUCKET_REPO_SLUG}.git`;
|
|
119
|
+
}
|
|
120
|
+
if (!repoUrl) {
|
|
121
|
+
console.log(
|
|
122
|
+
'setGitInfo: no BITBUCKET_STEP_OAUTH_TOKEN/BITBUCKET_TOKEN or BITBUCKET_APP_PASSWORD available, leaving origin as-is',
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(`Setting new origin: ${maskCreds(repoUrl)}`);
|
|
126
|
+
await $`git remote set-url origin ${repoUrl}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const after = (await $`git remote get-url origin`).stdout.trim();
|
|
130
|
+
console.log(`setGitInfo: origin after = ${maskCreds(after)}`);
|
|
131
|
+
} else {
|
|
132
|
+
const current = (await $`git remote get-url origin`).stdout.trim();
|
|
133
|
+
console.log(
|
|
134
|
+
`setGitInfo: SET_GITOPS_USER not set, leaving origin as-is = ${maskCreds(current)}`,
|
|
135
|
+
);
|
|
94
136
|
}
|
|
95
137
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Pure parsing/formatting logic for the Tenable scan step, ported from
|
|
2
|
+
// bin/tenable-scan.sh. No fs/docker calls here so it's unit-testable.
|
|
3
|
+
|
|
4
|
+
// Mirrors `tr -c 'A-Za-z0-9._-' '-'`: every character outside that set is
|
|
5
|
+
// replaced with '-' (no squeezing of consecutive replacements).
|
|
6
|
+
export function slugify(label) {
|
|
7
|
+
return label.replace(/[^A-Za-z0-9._-]/g, '-');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Splits `str` on `sep` into exactly `n` fields, folding any extra fields
|
|
11
|
+
// into the last one — matching `read a b c d <<< "$str"` semantics, where a
|
|
12
|
+
// trailing variable absorbs everything left over.
|
|
13
|
+
function splitFixed(str, sep, n) {
|
|
14
|
+
const parts = str.split(sep);
|
|
15
|
+
if (parts.length <= n) {
|
|
16
|
+
return [...parts, ...Array(n - parts.length).fill('')];
|
|
17
|
+
}
|
|
18
|
+
return [...parts.slice(0, n - 1), parts.slice(n - 1).join(sep)];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// One "label:auditDir:buildContext:dockerfile" entry from SCAN_TARGETS.
|
|
22
|
+
// auditDir belongs to the Xray step and is ignored here. A target with no
|
|
23
|
+
// build context is returned with skipped: true rather than dropped, so the
|
|
24
|
+
// caller can log why it was skipped (matching the bash script's behavior).
|
|
25
|
+
export function parseScanTargets(scanTargetsStr) {
|
|
26
|
+
return (scanTargetsStr || '')
|
|
27
|
+
.split(';')
|
|
28
|
+
.map((t) => t.trim())
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.map((raw) => {
|
|
31
|
+
const [rawLabel, , rawContext, rawDockerfile] = splitFixed(raw, ':', 4);
|
|
32
|
+
const label = rawLabel || 'app';
|
|
33
|
+
const slug = slugify(label);
|
|
34
|
+
if (!rawContext) {
|
|
35
|
+
return { label, slug, buildContext: '', dockerfile: '', skipped: true };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
label,
|
|
39
|
+
slug,
|
|
40
|
+
buildContext: rawContext,
|
|
41
|
+
dockerfile: rawDockerfile || `${rawContext}/Dockerfile`,
|
|
42
|
+
skipped: false,
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function defaultScanTargets(buildContext, dockerfilePath) {
|
|
48
|
+
const ctx = buildContext || '.';
|
|
49
|
+
return `app::${ctx}:${dockerfilePath || `${ctx}/Dockerfile`}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildCodeMetadata({
|
|
53
|
+
commit = 'local',
|
|
54
|
+
branch = '',
|
|
55
|
+
prDestinationBranch = '',
|
|
56
|
+
workspace = 'unknown',
|
|
57
|
+
repoSlug = 'unknown',
|
|
58
|
+
gitAuthorName = '',
|
|
59
|
+
} = {}) {
|
|
60
|
+
return {
|
|
61
|
+
shortSha: commit.slice(0, 7),
|
|
62
|
+
codeBranch: branch || prDestinationBranch || 'unknown',
|
|
63
|
+
codeRepoUrl: `https://bitbucket.org/${workspace}/${repoSlug}`,
|
|
64
|
+
codeUser: gitAuthorName || 'bitbucket-pipelines',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The scanner requires --pipeline-run-url as soon as any pipeline flag is
|
|
69
|
+
// supplied, so the trio (id/trigger/url) travels together or not at all —
|
|
70
|
+
// includeInScanArgs tells the caller which case applies.
|
|
71
|
+
export function buildPipelineRunInfo({
|
|
72
|
+
runId = '',
|
|
73
|
+
runTrigger = '',
|
|
74
|
+
runUrl = '',
|
|
75
|
+
prId = '',
|
|
76
|
+
buildNumber = '',
|
|
77
|
+
workspace = 'unknown',
|
|
78
|
+
repoSlug = 'unknown',
|
|
79
|
+
} = {}) {
|
|
80
|
+
const resolvedRunId = runId || buildNumber || '';
|
|
81
|
+
const resolvedTrigger = runTrigger || (prId ? 'pull_request' : '') || 'push';
|
|
82
|
+
const resolvedUrl =
|
|
83
|
+
runUrl ||
|
|
84
|
+
(buildNumber
|
|
85
|
+
? `https://bitbucket.org/${workspace}/${repoSlug}/pipelines/results/${buildNumber}`
|
|
86
|
+
: '');
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
runId: resolvedRunId,
|
|
90
|
+
runTrigger: resolvedTrigger,
|
|
91
|
+
runUrl: resolvedUrl,
|
|
92
|
+
includeInScanArgs: Boolean(resolvedRunId) && Boolean(resolvedUrl),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The scanner's documented vocabularies are lowercase — severities
|
|
97
|
+
// information|low|medium|high|critical, formats cyclonedx|json|junit|md — and it
|
|
98
|
+
// does not correct a case mismatch or complain about one: it just exports nothing.
|
|
99
|
+
// Callers set these from env vars that read naturally capitalised ("Medium"), so
|
|
100
|
+
// normalise here rather than making every caller remember.
|
|
101
|
+
export function buildScanArgs({
|
|
102
|
+
slug,
|
|
103
|
+
scanTarget,
|
|
104
|
+
codeBranch,
|
|
105
|
+
commit,
|
|
106
|
+
codeUser,
|
|
107
|
+
codeRepoUrl,
|
|
108
|
+
minSeverity,
|
|
109
|
+
runInfo,
|
|
110
|
+
failOnMinSeverity = '',
|
|
111
|
+
}) {
|
|
112
|
+
const args = [
|
|
113
|
+
'container-image',
|
|
114
|
+
'scan',
|
|
115
|
+
'--archive-path',
|
|
116
|
+
'/tmp/tenable/image.tar',
|
|
117
|
+
'--name',
|
|
118
|
+
scanTarget,
|
|
119
|
+
'--code-branch',
|
|
120
|
+
codeBranch,
|
|
121
|
+
'--code-commit-hash',
|
|
122
|
+
commit,
|
|
123
|
+
'--code-commit-user',
|
|
124
|
+
codeUser,
|
|
125
|
+
'--code-repository-url',
|
|
126
|
+
codeRepoUrl,
|
|
127
|
+
'--min-severity',
|
|
128
|
+
String(minSeverity).toLowerCase(),
|
|
129
|
+
'--output-file-formats',
|
|
130
|
+
'json,md,junit',
|
|
131
|
+
'--output-path',
|
|
132
|
+
'/tmp/tenable/out',
|
|
133
|
+
'--output-file-name',
|
|
134
|
+
`${slug}-tenable`,
|
|
135
|
+
'--no-color',
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
if (runInfo?.includeInScanArgs) {
|
|
139
|
+
args.push(
|
|
140
|
+
'--pipeline-run-id',
|
|
141
|
+
runInfo.runId,
|
|
142
|
+
'--pipeline-run-trigger',
|
|
143
|
+
runInfo.runTrigger,
|
|
144
|
+
'--pipeline-run-url',
|
|
145
|
+
runInfo.runUrl,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (failOnMinSeverity) {
|
|
150
|
+
args.push('--fail-on-min-severity', failOnMinSeverity.toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return args;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const SEPARATOR =
|
|
157
|
+
'==============================================================';
|