mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
ci: Refine Trivy scans, centralizes security config (#26384)
This commit is contained in:
parent
0b84e1079d
commit
241ad231a4
6
.github/WORKFLOWS.md
vendored
6
.github/WORKFLOWS.md
vendored
|
|
@ -599,10 +599,10 @@ npm audit signatures n8n@VERSION
|
||||||
|
|
||||||
VEX documents which CVEs actually affect n8n vs false positives from scanners.
|
VEX documents which CVEs actually affect n8n vs false positives from scanners.
|
||||||
|
|
||||||
- **File:** `vex.openvex.json` (repo root)
|
- **File:** `security/vex.openvex.json`
|
||||||
- **Format:** OpenVEX (broad scanner compatibility - Trivy, Docker Scout, etc.)
|
- **Format:** OpenVEX (broad scanner compatibility - Trivy, Docker Scout, etc.)
|
||||||
- **Attached to:** GitHub Release, Docker image attestations
|
- **Attached to:** GitHub Release, Docker image attestations
|
||||||
- **Used by:** Trivy scans (via `.github/trivy.yaml`)
|
- **Used by:** Trivy scans (via `security/trivy.yaml`)
|
||||||
|
|
||||||
**VEX Status Types:**
|
**VEX Status Types:**
|
||||||
| Status | Meaning |
|
| Status | Meaning |
|
||||||
|
|
@ -620,7 +620,7 @@ cosign verify-attestation --type openvex \
|
||||||
ghcr.io/n8n-io/n8n:VERSION
|
ghcr.io/n8n-io/n8n:VERSION
|
||||||
```
|
```
|
||||||
|
|
||||||
**Adding a CVE statement to vex.openvex.json:**
|
**Adding a CVE statement to security/vex.openvex.json:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"statements": [
|
"statements": [
|
||||||
|
|
|
||||||
10
.github/workflows/docker-build-push.yml
vendored
10
.github/workflows/docker-build-push.yml
vendored
|
|
@ -330,7 +330,7 @@ jobs:
|
||||||
secrets:
|
secrets:
|
||||||
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
registry-password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
# VEX Attestation - Documents which CVEs affect us (vex.openvex.json)
|
# VEX Attestation - Documents which CVEs affect us (security/vex.openvex.json)
|
||||||
vex-attestation:
|
vex-attestation:
|
||||||
name: VEX Attestation
|
name: VEX Attestation
|
||||||
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest, provenance-n8n, provenance-runners, provenance-runners-distroless]
|
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest, provenance-n8n, provenance-runners, provenance-runners-distroless]
|
||||||
|
|
@ -349,7 +349,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||||
|
|
@ -363,7 +363,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cosign attest --yes \
|
cosign attest --yes \
|
||||||
--type openvex \
|
--type openvex \
|
||||||
--predicate vex.openvex.json \
|
--predicate security/vex.openvex.json \
|
||||||
${{ needs.create_multi_arch_manifest.outputs.n8n_image }}@${{ needs.create_multi_arch_manifest.outputs.n8n_digest }}
|
${{ needs.create_multi_arch_manifest.outputs.n8n_image }}@${{ needs.create_multi_arch_manifest.outputs.n8n_digest }}
|
||||||
|
|
||||||
- name: Attest VEX to runners image
|
- name: Attest VEX to runners image
|
||||||
|
|
@ -371,7 +371,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cosign attest --yes \
|
cosign attest --yes \
|
||||||
--type openvex \
|
--type openvex \
|
||||||
--predicate vex.openvex.json \
|
--predicate security/vex.openvex.json \
|
||||||
${{ needs.create_multi_arch_manifest.outputs.runners_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_digest }}
|
${{ needs.create_multi_arch_manifest.outputs.runners_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_digest }}
|
||||||
|
|
||||||
- name: Attest VEX to runners-distroless image
|
- name: Attest VEX to runners-distroless image
|
||||||
|
|
@ -379,7 +379,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cosign attest --yes \
|
cosign attest --yes \
|
||||||
--type openvex \
|
--type openvex \
|
||||||
--predicate vex.openvex.json \
|
--predicate security/vex.openvex.json \
|
||||||
${{ needs.create_multi_arch_manifest.outputs.runners_distroless_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_distroless_digest }}
|
${{ needs.create_multi_arch_manifest.outputs.runners_distroless_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_distroless_digest }}
|
||||||
|
|
||||||
security-scan:
|
security-scan:
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,11 @@ jobs:
|
||||||
# Upload SBOM and VEX files to the existing release
|
# Upload SBOM and VEX files to the existing release
|
||||||
gh release upload "${{ inputs.release_tag_ref }}" \
|
gh release upload "${{ inputs.release_tag_ref }}" \
|
||||||
sbom-source.cdx.json \
|
sbom-source.cdx.json \
|
||||||
vex.openvex.json \
|
security/vex.openvex.json \
|
||||||
--clobber
|
--clobber
|
||||||
|
|
||||||
COMPONENT_COUNT=$(jq '.components | length' sbom-source.cdx.json 2>/dev/null || echo "unknown")
|
COMPONENT_COUNT=$(jq '.components | length' sbom-source.cdx.json 2>/dev/null || echo "unknown")
|
||||||
VEX_STATEMENTS=$(jq '.statements | length' vex.openvex.json 2>/dev/null || echo "0")
|
VEX_STATEMENTS=$(jq '.statements | length' security/vex.openvex.json 2>/dev/null || echo "0")
|
||||||
echo "SBOM and VEX attached to release"
|
echo "SBOM and VEX attached to release"
|
||||||
echo " - SBOM: $COMPONENT_COUNT components"
|
echo " - SBOM: $COMPONENT_COUNT components"
|
||||||
echo " - VEX: $VEX_STATEMENTS CVE statements"
|
echo " - VEX: $VEX_STATEMENTS CVE statements"
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
vex.openvex.json
|
security/vex.openvex.json
|
||||||
.github/trivy.yaml
|
security/trivy.yaml
|
||||||
|
security/trivy-ignore-policy.rego
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
- name: Pull Docker image with retry
|
- name: Pull Docker image with retry
|
||||||
|
|
@ -46,7 +47,7 @@ jobs:
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Run Trivy vulnerability scanner
|
- name: Run Trivy vulnerability scanner
|
||||||
uses: aquasecurity/trivy-action@dc5a429b52fcf669ce959baa2c2dd26090d2a6c4 # v0.32.0
|
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1
|
||||||
id: trivy_scan
|
id: trivy_scan
|
||||||
with:
|
with:
|
||||||
image-ref: ${{ inputs.image_ref }}
|
image-ref: ${{ inputs.image_ref }}
|
||||||
|
|
@ -55,7 +56,7 @@ jobs:
|
||||||
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
|
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
|
||||||
ignore-unfixed: false
|
ignore-unfixed: false
|
||||||
exit-code: '0'
|
exit-code: '0'
|
||||||
trivy-config: '.github/trivy.yaml'
|
trivy-config: 'security/trivy.yaml'
|
||||||
|
|
||||||
- name: Calculate vulnerability counts
|
- name: Calculate vulnerability counts
|
||||||
id: process_results
|
id: process_results
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,15 @@ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
||||||
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
|
||||||
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
|
||||||
|
|
||||||
|
const assertPathWithinRoot = (envVar, defaultRelPath) => {
|
||||||
|
const resolved = path.resolve(process.env[envVar] || path.join(rootDir, defaultRelPath));
|
||||||
|
if (!resolved.startsWith(rootDir + path.sep) && resolved !== rootDir) {
|
||||||
|
echo(chalk.red(`Error: ${envVar} must resolve within the repository root`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
};
|
||||||
|
|
||||||
// #region ===== Configuration =====
|
// #region ===== Configuration =====
|
||||||
const config = {
|
const config = {
|
||||||
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8nio/n8n',
|
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8nio/n8n',
|
||||||
|
|
@ -27,7 +36,8 @@ const config = {
|
||||||
scanners: process.env.TRIVY_SCANNERS || 'vuln',
|
scanners: process.env.TRIVY_SCANNERS || 'vuln',
|
||||||
quiet: process.env.TRIVY_QUIET === 'true',
|
quiet: process.env.TRIVY_QUIET === 'true',
|
||||||
rootDir: rootDir,
|
rootDir: rootDir,
|
||||||
vexFile: process.env.TRIVY_VEX || path.join(rootDir, 'vex.openvex.json'),
|
vexFile: assertPathWithinRoot('TRIVY_VEX', 'security/vex.openvex.json'),
|
||||||
|
ignorePolicyFile: assertPathWithinRoot('TRIVY_IGNORE_POLICY', 'security/trivy-ignore-policy.rego'),
|
||||||
};
|
};
|
||||||
|
|
||||||
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
|
||||||
|
|
@ -54,6 +64,7 @@ const printSummary = (status, time, message) => {
|
||||||
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
|
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
|
||||||
echo(chalk.gray(` • Scanners: ${config.scanners}`));
|
echo(chalk.gray(` • Scanners: ${config.scanners}`));
|
||||||
echo(chalk.gray(` • VEX file: ${config.vexFile}`));
|
echo(chalk.gray(` • VEX file: ${config.vexFile}`));
|
||||||
|
echo(chalk.gray(` • Ignore policy: ${config.ignorePolicyFile}`));
|
||||||
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
|
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
|
||||||
echo(chalk.blue.bold('========================'));
|
echo(chalk.blue.bold('========================'));
|
||||||
};
|
};
|
||||||
|
|
@ -94,6 +105,8 @@ const printSummary = (status, time, message) => {
|
||||||
'/var/run/docker.sock:/var/run/docker.sock',
|
'/var/run/docker.sock:/var/run/docker.sock',
|
||||||
'-v',
|
'-v',
|
||||||
`${config.vexFile}:/vex.openvex.json:ro`,
|
`${config.vexFile}:/vex.openvex.json:ro`,
|
||||||
|
'-v',
|
||||||
|
`${config.ignorePolicyFile}:/trivy-ignore-policy.rego:ro`,
|
||||||
config.trivyImage,
|
config.trivyImage,
|
||||||
'image',
|
'image',
|
||||||
'--severity',
|
'--severity',
|
||||||
|
|
@ -107,6 +120,8 @@ const printSummary = (status, time, message) => {
|
||||||
'--no-progress',
|
'--no-progress',
|
||||||
'--vex',
|
'--vex',
|
||||||
'/vex.openvex.json',
|
'/vex.openvex.json',
|
||||||
|
'--ignore-policy',
|
||||||
|
'/trivy-ignore-policy.rego',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');
|
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');
|
||||||
|
|
|
||||||
14
security/trivy-ignore-policy.rego
Normal file
14
security/trivy-ignore-policy.rego
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Trivy ignore policy for n8n security scans.
|
||||||
|
# n8n's own published CVEs/GHSAs are intentionally excluded from internal
|
||||||
|
# scan results. Vulnerabilities in the n8n package should be visible to
|
||||||
|
# anyone running an older version — they indicate an upgrade is required.
|
||||||
|
# VEX (vex.openvex.json) covers third-party dependency false positives only.
|
||||||
|
package trivy
|
||||||
|
|
||||||
|
import future.keywords.if
|
||||||
|
|
||||||
|
default ignore := false
|
||||||
|
|
||||||
|
ignore if {
|
||||||
|
input.PkgName == "n8n"
|
||||||
|
}
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
# See: https://trivy.dev/latest/docs/references/configuration/config-file/
|
# See: https://trivy.dev/latest/docs/references/configuration/config-file/
|
||||||
vulnerability:
|
vulnerability:
|
||||||
vex:
|
vex:
|
||||||
- vex.openvex.json
|
- security/vex.openvex.json
|
||||||
|
ignore-policy: security/trivy-ignore-policy.rego
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
"@context": "https://openvex.dev/ns/v0.2.0",
|
"@context": "https://openvex.dev/ns/v0.2.0",
|
||||||
"@id": "https://github.com/n8n-io/n8n/vex",
|
"@id": "https://github.com/n8n-io/n8n/vex",
|
||||||
"author": "n8n Security Team <security@n8n.io>",
|
"author": "n8n Security Team <security@n8n.io>",
|
||||||
"timestamp": "2026-02-13T00:00:00Z",
|
"timestamp": "2026-03-01T00:00:00Z",
|
||||||
"version": 3,
|
"version": 5,
|
||||||
"statements": [
|
"statements": [
|
||||||
{
|
{
|
||||||
"vulnerability": {
|
"vulnerability": {
|
||||||
Loading…
Reference in New Issue
Block a user