Compare commits
16 Commits
7f366204a9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f2b5c1b47 | |||
| 43d6ad6d6c | |||
| 2d246bd95c | |||
| 0d80215207 | |||
| e0e7018a55 | |||
| 3676ccf990 | |||
| 1e849976aa | |||
| 35ad38dda7 | |||
| 78f30b9608 | |||
| 749afaebf7 | |||
| 0099dc1e4a | |||
| 193230edac | |||
| 4cf7bf2d57 | |||
| fefe72d177 | |||
| 6f0252776f | |||
| 5bdf3fe114 |
Executable
+5
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
"${repo_root}/scripts/check-chart.sh"
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
pnpm-debug.log
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Reports and temporary files
|
||||||
|
reports/
|
||||||
|
*.sarif
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
.amp-usage.db
|
||||||
@@ -1,68 +1,130 @@
|
|||||||
# agentguard-ci
|
# agentguard-ci
|
||||||
|
|
||||||
A DevSecOps Argo Workflows pipeline specifically designed to protect against AI coding agent hallucinations, supply chain attacks, and security misconfigurations in a homelab or solo-developer environment.
|
A DevSecOps Argo Workflows pipeline designed to protect against AI coding agent hallucinations, supply chain attacks, and security misconfigurations in a homelab or solo-developer environment.
|
||||||
|
|
||||||
## 📖 The Problem
|
## Problem
|
||||||
|
|
||||||
AI coding agents are highly productive "junior developers," but they lack intrinsic context. They frequently hallucinate dummy credentials, introduce insecure application logic, or pull in new, potentially typosquatted dependencies.
|
AI coding agents are highly productive junior developers, but they lack intrinsic context. They can hallucinate credentials, introduce insecure logic, or pull in risky dependencies.
|
||||||
|
|
||||||
This pipeline acts as a strict, automated gatekeeper that prioritizes zero-noise alerting, allowing you to maintain high development velocity without compromising the security of your exposed homelab.
|
This project adds a reusable security gate in front of deployment by cloning a repository into an Argo workflow, running multiple scanners in parallel, uploading supported results to DefectDojo and object storage, and enforcing a CVSS-based policy threshold.
|
||||||
|
|
||||||
## 🏗️ Architecture & Features
|
## What the pipeline does
|
||||||
|
|
||||||
This project deploys an **Argo ClusterWorkflowTemplate** that orchestrates a parallel security scanning matrix whenever code is pushed:
|
- Runs TruffleHog for secret scanning.
|
||||||
* **TruffleHog**: Verifies leaked API keys dynamically to prevent false-positives from AI hallucinations.
|
- Runs Semgrep for first-party code scanning.
|
||||||
* **Semgrep**: Scans first-party application logic for vulnerabilities (e.g., SQLi, XSS).
|
- Runs KICS for infrastructure misconfiguration scanning.
|
||||||
* **Socket.dev**: Analyzes dependencies for supply chain attacks, malware, and typosquatting.
|
- Runs Socket.dev for dependency risk scanning.
|
||||||
* **Pulumi CrossGuard**: Validates Infrastructure as Code against policy packs.
|
- Runs Syft and Grype for SBOM generation and vulnerability scanning.
|
||||||
* **Syft + Grype**: Generates SBOMs and scans for container vulnerabilities scored via EPSS.
|
- Runs Pulumi CrossGuard for policy-pack validation.
|
||||||
* **KICS**: Scans infrastructure misconfigurations.
|
- Uploads supported reports to DefectDojo when enabled.
|
||||||
* **DefectDojo & MinIO**: Uploads findings to a centralized ASPM dashboard and raw SARIF/JSON reports to S3-compatible storage.
|
- Uploads raw reports to S3-compatible storage when enabled.
|
||||||
* **Policy Enforcement**: Custom TypeScript logic automatically fails the build if any findings exceed your defined CVSS severity threshold.
|
- Fails the workflow when findings meet or exceed the configured CVSS threshold.
|
||||||
|
|
||||||
For deep-dive architecture decisions, see the [Pipeline Overview ADR](docs/pipeline-overview.md) and [Secret Strategy ADR](docs/secret-strategy.md).
|
## Prerequisites
|
||||||
|
|
||||||
## 🚀 Prerequisites
|
Install these separately in your cluster before using this chart:
|
||||||
|
|
||||||
Before installing the pipeline, ensure your Kubernetes cluster has the following installed:
|
- Argo Workflows
|
||||||
* **Argo Workflows**
|
- Infisical Kubernetes Operator, if you want this chart to sync secrets automatically
|
||||||
* **Infisical Kubernetes Operator** (for secret injection)
|
- DefectDojo, if you want report ingestion enabled
|
||||||
* **DefectDojo** (for vulnerability dashboards)
|
- MinIO or another S3-compatible store, if you want raw report uploads enabled
|
||||||
* **MinIO / S3** (for raw report storage)
|
|
||||||
|
|
||||||
You will also need API keys or tokens for: Socket.dev, Pulumi, AWS/MinIO, and DefectDojo.
|
You will also need the corresponding credentials for Socket.dev, Pulumi, S3-compatible object storage, and DefectDojo.
|
||||||
|
|
||||||
## 🛠️ Installation
|
## Reading the chart
|
||||||
|
|
||||||
|
If the Helm templates start to feel too abstract, use these two files together:
|
||||||
|
|
||||||
|
- [`helm/values.schema.json`](helm/values.schema.json) documents the expected shape and meaning of the values file.
|
||||||
|
- [`docs/rendered/default-clusterworkflowtemplate.yaml`](docs/rendered/default-clusterworkflowtemplate.yaml) shows the default rendered `ClusterWorkflowTemplate` without Helm directives in the way.
|
||||||
|
|
||||||
|
The rendered reference reflects the default values in `helm/values.yaml`, so optional storage, DefectDojo, and Infisical resources are intentionally omitted there.
|
||||||
|
|
||||||
|
## Validation workflow
|
||||||
|
|
||||||
|
For fast validation while wiring up infrastructure, use these tools together:
|
||||||
|
|
||||||
|
- `./scripts/check-chart.sh`
|
||||||
|
- `RUN_KUBECTL_CLIENT_CHECK=1 ./scripts/check-chart.sh`
|
||||||
|
- `RUN_KUBECTL_SERVER_CHECK=1 ./scripts/check-chart.sh`
|
||||||
|
|
||||||
|
What each mode does:
|
||||||
|
|
||||||
|
- `./scripts/check-chart.sh` runs the fast offline checks used by the repo-managed pre-commit hook: `helm lint`, `helm template`, and `argo lint --offline`.
|
||||||
|
- `RUN_KUBECTL_CLIENT_CHECK=1 ./scripts/check-chart.sh` adds a client-side `kubectl` dry-run. This is optional because CRD-heavy manifests can still be environment-sensitive here.
|
||||||
|
- `RUN_KUBECTL_SERVER_CHECK=1 ./scripts/check-chart.sh` adds a server-side dry-run against your current cluster context, which is the strongest validation once the Argo and Infisical CRDs are installed.
|
||||||
|
|
||||||
|
Install the shared git hook once per clone:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `helm lint` catches Helm chart problems.
|
||||||
|
- `helm template` proves the chart renders successfully with the current values.
|
||||||
|
- `argo lint --offline` is the most useful Argo-specific local check because it validates the rendered `ClusterWorkflowTemplate` without needing cluster access.
|
||||||
|
- `kubectl --dry-run=client` is weaker for CRDs than Argo lint, so it is included as an optional extra check instead of the default hook behavior.
|
||||||
|
- `kubectl --dry-run=server` is best once the cluster already has the Argo and Infisical CRDs installed.
|
||||||
|
- CI should still rerun the same baseline checks even if pre-commit already passed, because hooks are local and bypassable. The usual CI extra is the server-side `kubectl` dry-run once a cluster with the needed CRDs is available.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Build the tools image
|
||||||
|
|
||||||
|
The workflow uses custom TypeScript utilities for policy enforcement and DefectDojo uploads.
|
||||||
|
|
||||||
### 1. Build the Pipeline Tools Image
|
|
||||||
The pipeline relies on custom TypeScript logic (e.g., CVSS enforcement and API uploads). Build and push this image to your registry:
|
|
||||||
```bash
|
```bash
|
||||||
cd tools
|
cd tools
|
||||||
docker build -t your-registry/agentguard-tools:latest .
|
docker build -t your-registry/agentguard-tools:latest .
|
||||||
docker push your-registry/agentguard-tools:latest
|
docker push your-registry/agentguard-tools:latest
|
||||||
```
|
```
|
||||||
*(Make sure to update `clusterworkflowtemplate.yaml` with your custom image if you do not use `agentguard-tools:latest`)*
|
|
||||||
|
|
||||||
### 2. Configure Helm Values
|
### 2. Configure values
|
||||||
Update `helm/values.yaml` (if applicable) and configure your Infisical integration:
|
|
||||||
|
Start from [`helm/values.yaml`](helm/values.yaml) and set at least:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
pipeline:
|
pipeline:
|
||||||
enabled: true
|
toolsImage:
|
||||||
|
repository: your-registry/agentguard-tools
|
||||||
|
tag: latest
|
||||||
|
|
||||||
infisical:
|
infisical:
|
||||||
workspaceSlug: "your-workspace-id"
|
enabled: true
|
||||||
projectSlug: "your-project-id"
|
workspaceSlug: your-workspace-id
|
||||||
|
projectSlug: your-project-id
|
||||||
|
|
||||||
|
storage:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
defectdojo:
|
||||||
|
enabled: false
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Deploy via Helm
|
Keep `storage.enabled` and `defectdojo.enabled` disabled until those services are actually installed and reachable. Keep `infisical.enabled` disabled until the operator is installed and your project identifiers are ready.
|
||||||
Install the pipeline and its associated resources to your cluster:
|
|
||||||
|
If you do not use Infisical, create the `amp-security-pipeline-secrets` secret yourself before running the workflow. For storage uploads, the secret should contain `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`.
|
||||||
|
|
||||||
|
### 3. Deploy the chart
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install agentguard-ci ./helm -n argo
|
helm upgrade --install agentguard-ci ./helm -n argo
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔐 Secret Management Integration
|
## Scope and boundaries
|
||||||
|
|
||||||
To prevent hardcoded secrets in the pipeline, this project uses the **Infisical Kubernetes Operator**.
|
This repository is intentionally focused on **source, IaC, and dependency scanning** before deployment.
|
||||||
|
|
||||||
When you deploy the Helm chart, it creates an `InfisicalSecret` Custom Resource (`helm/templates/infisical-secret.yaml`). The Infisical Operator securely fetches your vault secrets (like `SOCKET_DEV_API_KEY` and `DEFECTDOJO_API_TOKEN`) and synchronizes them into a standard Kubernetes `Secret` named `amp-security-pipeline-secrets`.
|
It does **not** try to be the full build-signing, deploy-admission, or runtime-security stack. For the explicit boundary, missing controls, and recommended sibling pipeline responsibilities, read [`docs/security-scope.md`](docs/security-scope.md).
|
||||||
|
|
||||||
The Argo Workflow then mounts this standard secret as environment variables inside the scanning containers, ensuring zero secret leakage in the Git repository.
|
## DefectDojo integration
|
||||||
|
|
||||||
|
DefectDojo is not installed by this repository.
|
||||||
|
|
||||||
|
You install DefectDojo separately, then enable this chart's upload step. When enabled, the workflow uploads supported reports into DefectDojo through the API using the custom uploader in [`tools/src/upload-defectdojo.ts`](tools/src/upload-defectdojo.ts).
|
||||||
|
|
||||||
|
## Secret management
|
||||||
|
|
||||||
|
When `infisical.enabled` is `true`, this chart creates an `InfisicalSecret` that syncs the runtime credentials needed by the workflow into the `amp-security-pipeline-secrets` Kubernetes secret.
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Nuclei - https://github.com/projectdiscovery/nuclei
|
||||||
|
|
||||||
|
cli tester with embedded chromium
|
||||||
|
sandyaa - https://github.com/securelayer7/sandyaa
|
||||||
|
|
||||||
|
pipeline vuln tester, very comprehensive
|
||||||
|
darwis-taka - https://hub.docker.com/r/cysecurity/darwis-taka
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
# Rendered reference for the default chart values in helm/values.yaml.
|
||||||
|
# This is intended for reading and review, so you can inspect the final Argo object
|
||||||
|
# without also mentally evaluating Helm templates.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Optional storage, DefectDojo, and Infisical resources are omitted because they are
|
||||||
|
# disabled by default.
|
||||||
|
# - Argo placeholders such as {{workflow.parameters.repo-url}} are expected and remain
|
||||||
|
# in the rendered object because Argo resolves them at workflow runtime.
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: ClusterWorkflowTemplate
|
||||||
|
metadata:
|
||||||
|
name: amp-security-pipeline-v1.0.0
|
||||||
|
spec:
|
||||||
|
serviceAccountName: default
|
||||||
|
entrypoint: security-pipeline
|
||||||
|
onExit: pipeline-exit-hook
|
||||||
|
volumeClaimTemplates:
|
||||||
|
- metadata:
|
||||||
|
name: workspace
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "."
|
||||||
|
- name: fail-on-cvss
|
||||||
|
value: "7.0"
|
||||||
|
- name: repo-url
|
||||||
|
- name: git-revision
|
||||||
|
value: "main"
|
||||||
|
templates:
|
||||||
|
- name: security-pipeline
|
||||||
|
dag:
|
||||||
|
tasks:
|
||||||
|
- name: clone
|
||||||
|
template: clone-repo
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: repo-url
|
||||||
|
value: "{{workflow.parameters.repo-url}}"
|
||||||
|
- name: git-revision
|
||||||
|
value: "{{workflow.parameters.git-revision}}"
|
||||||
|
- name: scanners
|
||||||
|
dependencies:
|
||||||
|
- clone
|
||||||
|
template: parallel-scanners
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{workflow.parameters.working-dir}}"
|
||||||
|
- name: enforce-policy
|
||||||
|
dependencies:
|
||||||
|
- scanners
|
||||||
|
template: enforce-policy
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: fail-on-cvss
|
||||||
|
value: "{{workflow.parameters.fail-on-cvss}}"
|
||||||
|
- name: clone-repo
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: repo-url
|
||||||
|
- name: git-revision
|
||||||
|
container:
|
||||||
|
image: alpine/git:2.45.2
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- git clone --branch "{{inputs.parameters.git-revision}}" --single-branch "{{inputs.parameters.repo-url}}" /workspace
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: parallel-scanners
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
dag:
|
||||||
|
tasks:
|
||||||
|
- name: trufflehog
|
||||||
|
template: scan-trufflehog
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: semgrep
|
||||||
|
template: scan-semgrep
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: kics
|
||||||
|
template: scan-kics
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: socketdev
|
||||||
|
template: scan-socketdev
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: syft-grype
|
||||||
|
template: scan-syft-grype
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: pulumi-crossguard
|
||||||
|
template: scan-pulumi-crossguard
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: "{{inputs.parameters.working-dir}}"
|
||||||
|
- name: pipeline-exit-hook
|
||||||
|
container:
|
||||||
|
image: curlimages/curl:8.8.0
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
echo "Pipeline completed with status: {{workflow.status}}"
|
||||||
|
- name: scan-trufflehog
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: trufflesecurity/trufflehog:latest
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
trufflehog filesystem "/workspace/{{inputs.parameters.working-dir}}" --json > /workspace/reports/trufflehog.json || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: scan-semgrep
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: returntocorp/semgrep:1.85.0
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
semgrep scan --config auto --sarif --output /workspace/reports/semgrep.sarif "/workspace/{{inputs.parameters.working-dir}}" || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: scan-kics
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: checkmarx/kics:1.7.14
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
kics scan -p "/workspace/{{inputs.parameters.working-dir}}" -o /workspace/reports --report-formats sarif,json --output-name kics || true
|
||||||
|
if [ -f /workspace/reports/kics.sarif ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if [ -f /workspace/reports/kics.json ]; then
|
||||||
|
cp /workspace/reports/kics.json /workspace/reports/kics.sarif
|
||||||
|
fi
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: scan-socketdev
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: socketdev/socketcli:latest
|
||||||
|
env:
|
||||||
|
- name: SOCKET_DEV_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: amp-security-pipeline-secrets
|
||||||
|
key: SOCKET_DEV_API_KEY
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
socketdev scan "/workspace/{{inputs.parameters.working-dir}}" --format json --output /workspace/reports/socketdev.json || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: scan-syft-grype
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: anchore/syft:latest
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
syft scan dir:/workspace/{{inputs.parameters.working-dir}} -o cyclonedx-json=/workspace/reports/sbom.json || true
|
||||||
|
grype sbom:/workspace/reports/sbom.json -o sarif=/workspace/reports/grype.sarif || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: scan-pulumi-crossguard
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: pulumi/pulumi:3.154.0
|
||||||
|
env:
|
||||||
|
- name: PULUMI_ACCESS_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: amp-security-pipeline-secrets
|
||||||
|
key: PULUMI_ACCESS_TOKEN
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
cd "/workspace/{{inputs.parameters.working-dir}}"
|
||||||
|
pulumi preview --policy-pack "policy-pack" > /workspace/reports/pulumi-crossguard.json 2>&1 || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
- name: enforce-policy
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: fail-on-cvss
|
||||||
|
container:
|
||||||
|
image: agentguard-tools:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- node
|
||||||
|
- /app/dist/enforce-policy.js
|
||||||
|
env:
|
||||||
|
- name: FAIL_ON_CVSS
|
||||||
|
value: "{{inputs.parameters.fail-on-cvss}}"
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# Security Scope and Boundaries
|
||||||
|
|
||||||
|
This repository is intentionally a **source, IaC, and dependency scanning pipeline**.
|
||||||
|
It is not a complete software supply chain or runtime security platform.
|
||||||
|
|
||||||
|
The goal of this document is to make the scope explicit so users understand:
|
||||||
|
|
||||||
|
- what this repository is designed to secure
|
||||||
|
- what it does not secure
|
||||||
|
- what additional controls are still required for a production or homelab deployment pipeline
|
||||||
|
- what requirements a sibling pipeline should meet if you want to fill the gaps
|
||||||
|
|
||||||
|
## What this repository covers
|
||||||
|
|
||||||
|
This repository provides a reusable pre-deploy security gate that scans a repository before deployment proceeds.
|
||||||
|
|
||||||
|
Today it covers:
|
||||||
|
|
||||||
|
- **Secret scanning** with TruffleHog in the pipeline and Gitleaks in local development guidance.
|
||||||
|
- **First-party code scanning** with Semgrep.
|
||||||
|
- **Infrastructure-as-code scanning** with KICS.
|
||||||
|
- **Dependency risk scanning** with Socket.dev.
|
||||||
|
- **SBOM generation and vulnerability scanning** with Syft and Grype.
|
||||||
|
- **Policy-pack validation** with Pulumi CrossGuard.
|
||||||
|
- **Finding export and triage plumbing** through optional DefectDojo and object storage uploads.
|
||||||
|
- **Dependency update automation support** through the Renovate preset and Renovate bot chart in this repository.
|
||||||
|
|
||||||
|
In practical terms, this repository is strongest at:
|
||||||
|
|
||||||
|
- stopping known-bad or suspicious changes before deploy
|
||||||
|
- reducing the chance that AI-generated insecure code or dependencies reach production
|
||||||
|
- creating visibility into code, IaC, dependency, and package vulnerability risk
|
||||||
|
|
||||||
|
## What this repository does not cover
|
||||||
|
|
||||||
|
This repository does **not** attempt to be responsible for every security control in a modern delivery system.
|
||||||
|
|
||||||
|
It does not provide:
|
||||||
|
|
||||||
|
- **Artifact signing** for container images, packages, or release bundles.
|
||||||
|
- **Build provenance / attestations** proving which trusted builder produced an artifact from which source revision.
|
||||||
|
- **Admission control verification** that only signed or attested artifacts may deploy.
|
||||||
|
- **Runtime threat detection** inside the cluster or host.
|
||||||
|
- **Network segmentation or runtime isolation** for the deployed applications.
|
||||||
|
- **DAST** or other black-box testing against a live running application.
|
||||||
|
- **Repository hosting configuration enforcement** such as branch protection, required reviews, CODEOWNERS, or token restrictions.
|
||||||
|
- **Application deployment hardening enforcement** such as checking whether deployed workloads run as non-root, drop capabilities, use read-only root filesystems, or have network policies.
|
||||||
|
- **Secure secret delivery for the scanned application** beyond the pipeline's own runtime secret needs.
|
||||||
|
- **Incident response, rollback, backup, disaster recovery, or patch SLAs**.
|
||||||
|
|
||||||
|
Those areas still matter. They are simply outside the intended scope of this repository.
|
||||||
|
|
||||||
|
## Why the scope stops here
|
||||||
|
|
||||||
|
The security boundary for this repository is:
|
||||||
|
|
||||||
|
1. clone a source repository
|
||||||
|
2. scan source, IaC, dependencies, and generated SBOM data
|
||||||
|
3. publish findings
|
||||||
|
4. fail or pass based on policy
|
||||||
|
|
||||||
|
That boundary is useful because it keeps this repository focused, fast, and reusable.
|
||||||
|
If artifact build security, deploy admission, and runtime controls are mixed directly into this pipeline, the repository stops being a clear pre-deploy scanner and turns into a broader platform.
|
||||||
|
|
||||||
|
This repository should remain the **shift-left scanning gate**, not the entire delivery architecture.
|
||||||
|
|
||||||
|
## What a complete deployment pipeline still needs
|
||||||
|
|
||||||
|
If you are deploying software after this pipeline passes, you still need additional controls elsewhere.
|
||||||
|
|
||||||
|
At a minimum, a production-grade or public homelab deployment flow should also have:
|
||||||
|
|
||||||
|
### 1. Repository and change-management controls
|
||||||
|
|
||||||
|
These controls protect the trustworthiness of the source before scanning even begins.
|
||||||
|
|
||||||
|
Recommended controls:
|
||||||
|
|
||||||
|
- branch protection on default and release branches
|
||||||
|
- required pull request reviews for sensitive paths
|
||||||
|
- `CODEOWNERS` for pipeline, deployment, and policy files
|
||||||
|
- restricted direct pushes to protected branches
|
||||||
|
- minimal repository admin access
|
||||||
|
- mandatory CI status checks before merge
|
||||||
|
- protected tags or release branches if releases are tag-driven
|
||||||
|
|
||||||
|
This repository can document those requirements, but enforcement belongs in the source control platform.
|
||||||
|
|
||||||
|
### 2. Build pipeline controls
|
||||||
|
|
||||||
|
These controls establish that build outputs came from a trusted process.
|
||||||
|
|
||||||
|
Recommended controls:
|
||||||
|
|
||||||
|
- isolated build environment
|
||||||
|
- pinned builder images and actions
|
||||||
|
- least-privilege credentials for the build system
|
||||||
|
- artifact signing for produced container images or release assets
|
||||||
|
- provenance / attestation generation for builds
|
||||||
|
- immutable artifact storage or registry retention controls
|
||||||
|
|
||||||
|
If you want image signing or provenance, that should usually live in the **build pipeline**, because that is where artifacts are actually created.
|
||||||
|
|
||||||
|
### 3. Deploy pipeline controls
|
||||||
|
|
||||||
|
These controls ensure only approved artifacts and manifests reach the cluster.
|
||||||
|
|
||||||
|
Recommended controls:
|
||||||
|
|
||||||
|
- verification of image signatures or attestations before deploy
|
||||||
|
- enforcement that deployment pulls only from approved registries
|
||||||
|
- promotion between environments using immutable digests rather than floating tags
|
||||||
|
- deployment-time policy checks for manifests and workload security settings
|
||||||
|
- manual approval or staged rollout where risk justifies it
|
||||||
|
|
||||||
|
### 4. Runtime and cluster controls
|
||||||
|
|
||||||
|
These controls reduce blast radius after deployment.
|
||||||
|
|
||||||
|
Recommended controls:
|
||||||
|
|
||||||
|
- namespace isolation and least-privilege RBAC
|
||||||
|
- workload security contexts
|
||||||
|
- non-root containers where possible
|
||||||
|
- dropped Linux capabilities
|
||||||
|
- read-only root filesystems where possible
|
||||||
|
- Kubernetes network policies
|
||||||
|
- admission control for workload standards
|
||||||
|
- runtime threat detection / alerting
|
||||||
|
- centralized logs, metrics, and alerts
|
||||||
|
- secrets delivery from a vault or equivalent system
|
||||||
|
|
||||||
|
### 5. Recovery and resilience controls
|
||||||
|
|
||||||
|
These controls matter because modern guidance assumes prevention will sometimes fail.
|
||||||
|
|
||||||
|
Recommended controls:
|
||||||
|
|
||||||
|
- tested rollback procedures
|
||||||
|
- backups and restore testing
|
||||||
|
- credential rotation procedures
|
||||||
|
- vulnerability remediation SLAs
|
||||||
|
- incident response playbooks
|
||||||
|
- inventory of critical services and owners
|
||||||
|
|
||||||
|
## Recommended sibling pipeline responsibilities
|
||||||
|
|
||||||
|
If you build a second pipeline to fill the gaps, keep the separation of concerns explicit.
|
||||||
|
|
||||||
|
A sibling **build and deploy security pipeline** should be responsible for:
|
||||||
|
|
||||||
|
- building artifacts in a trusted environment
|
||||||
|
- generating SBOMs tied to the built artifact
|
||||||
|
- signing artifacts
|
||||||
|
- generating provenance / attestations
|
||||||
|
- verifying signatures or attestations before deploy
|
||||||
|
- enforcing deploy-time policy on manifests and workloads
|
||||||
|
- optionally running DAST or post-deploy validation outside the fast PR loop
|
||||||
|
|
||||||
|
That keeps this repository focused on source scanning while the sibling pipeline owns artifact trust and deployment trust.
|
||||||
|
|
||||||
|
## Mapping this repository to modern security guidance
|
||||||
|
|
||||||
|
A concise way to think about the split is:
|
||||||
|
|
||||||
|
- **Assume compromise:** this repo helps by enforcing checks before deploy, but recovery and runtime containment live elsewhere.
|
||||||
|
- **Defense in depth:** this repo is one layer, not the whole system.
|
||||||
|
- **Blast-radius reduction:** this repo can reduce risky changes reaching production, but runtime RBAC, isolation, and network controls are outside its scope.
|
||||||
|
- **Zero trust / verified supply chain:** this repo contributes source and dependency scrutiny, but full artifact trust also needs build signing and deploy verification in another pipeline.
|
||||||
|
- **Prevention plus detection and recovery:** this repo is primarily a prevention and early-detection layer.
|
||||||
|
|
||||||
|
## Minimum expectations for users of this repository
|
||||||
|
|
||||||
|
If you adopt this repository, you should assume it is only one part of the security architecture.
|
||||||
|
|
||||||
|
At minimum, you should also have:
|
||||||
|
|
||||||
|
- repository branch protection and required reviews
|
||||||
|
- a secure build pipeline
|
||||||
|
- a deployment path that uses immutable artifacts
|
||||||
|
- workload hardening standards for deployed applications
|
||||||
|
- secret management outside Git
|
||||||
|
- logging/monitoring and a plan for response when a scanner finds something important
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
To avoid scope creep, the following are non-goals for this repository:
|
||||||
|
|
||||||
|
- becoming the authoritative runtime security platform
|
||||||
|
- becoming the artifact signing system
|
||||||
|
- becoming the deployment admission controller
|
||||||
|
- replacing source control platform protections
|
||||||
|
- replacing cluster hardening or incident response processes
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This repository is the **source-side security gate**.
|
||||||
|
It helps answer: "Is this code, IaC, dependency set, and generated SBOM safe enough to continue?"
|
||||||
|
|
||||||
|
It does **not** answer the full downstream questions:
|
||||||
|
|
||||||
|
- "Was the artifact built by a trusted builder?"
|
||||||
|
- "Should the cluster admit this artifact?"
|
||||||
|
- "Is the running workload isolated and monitored correctly?"
|
||||||
|
- "Can we detect, contain, and recover if prevention fails?"
|
||||||
|
|
||||||
|
Those controls should exist, but they should live in adjacent repository, build, deploy, and runtime systems rather than being forced into this repository.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: agentguard-ci
|
||||||
|
description: Argo Workflows security pipeline for AI-assisted repositories
|
||||||
|
type: application
|
||||||
|
version: 0.1.0
|
||||||
|
appVersion: "1.0.0"
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
{{- define "template.enforce-policy" }}
|
{{- define "template.enforce-policy" -}}
|
||||||
- name: enforce-policy
|
- name: enforce-policy
|
||||||
inputs:
|
inputs:
|
||||||
parameters:
|
parameters:
|
||||||
- name: fail-on-cvss
|
- name: fail-on-cvss
|
||||||
container:
|
container:
|
||||||
image: agentguard-tools:latest
|
image: {{ include "template.tools-image" . | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.pipeline.toolsImage.pullPolicy }}
|
||||||
command:
|
command:
|
||||||
- node
|
- node
|
||||||
- /app/dist/enforce-policy.js
|
- /app/dist/enforce-policy.js
|
||||||
env:
|
env:
|
||||||
- name: FAIL_ON_CVSS
|
- name: FAIL_ON_CVSS
|
||||||
value: "{{inputs.parameters.fail-on-cvss}}"
|
value: {{ `{{inputs.parameters.fail-on-cvss}}` | quote }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{{- define "template.tools-image" -}}
|
||||||
|
{{- printf "%s:%s" .Values.pipeline.toolsImage.repository .Values.pipeline.toolsImage.tag -}}
|
||||||
|
{{- end }}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{{- define "template.scan-defectdojo" }}
|
|
||||||
- name: scan-defectdojo
|
|
||||||
container:
|
|
||||||
image: pulumi/pulumi:3.154.0
|
|
||||||
env:
|
|
||||||
- name: PULUMI_ACCESS_TOKEN
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: PULUMI_ACCESS_TOKEN
|
|
||||||
- name: AWS_ACCESS_KEY_ID
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: AWS_ACCESS_KEY_ID
|
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: AWS_SECRET_ACCESS_KEY
|
|
||||||
command:
|
|
||||||
- sh
|
|
||||||
- -c
|
|
||||||
args:
|
|
||||||
- |
|
|
||||||
set -eu
|
|
||||||
mkdir -p /workspace/reports
|
|
||||||
cd /workspace
|
|
||||||
pulumi preview --policy-pack ./policy-pack > /workspace/reports/crossguard.json 2>&1 || true
|
|
||||||
volumeMounts:
|
|
||||||
- name: workspace
|
|
||||||
mountPath: /workspace
|
|
||||||
{{- end }}
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
{{- define "template.scan-kics" }}
|
{{- define "template.scan-kics" -}}
|
||||||
- name: scan-kics
|
- name: scan-kics
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
container:
|
container:
|
||||||
image: checkmarx/kics:1.7.14
|
image: {{ .Values.images.kics | quote }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
mkdir -p /workspace/reports
|
mkdir -p /workspace/reports
|
||||||
kics scan -p /workspace -o /workspace/reports --report-formats sarif,json --output-name kics || true
|
kics scan -p "/workspace/{{ `{{inputs.parameters.working-dir}}` }}" -o /workspace/reports --report-formats sarif,json --output-name kics || true
|
||||||
if [ -f /workspace/reports/kics.sarif ]; then
|
if [ -f /workspace/reports/kics.sarif ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{{- define "template.scan-pulumi-crossguard" -}}
|
||||||
|
- name: scan-pulumi-crossguard
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
container:
|
||||||
|
image: {{ .Values.images.pulumiCrossguard }}
|
||||||
|
env:
|
||||||
|
- name: PULUMI_ACCESS_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: amp-security-pipeline-secrets
|
||||||
|
key: PULUMI_ACCESS_TOKEN
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
mkdir -p /workspace/reports
|
||||||
|
cd "/workspace/{{ `{{inputs.parameters.working-dir}}` }}"
|
||||||
|
pulumi preview --policy-pack {{ .Values.pulumi.policyPackPath | quote }} > /workspace/reports/pulumi-crossguard.json 2>&1 || true
|
||||||
|
volumeMounts:
|
||||||
|
- name: workspace
|
||||||
|
mountPath: /workspace
|
||||||
|
{{- end }}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
{{- define "template.scan-semgrep" }}
|
{{- define "template.scan-semgrep" -}}
|
||||||
- name: scan-semgrep
|
- name: scan-semgrep
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
container:
|
container:
|
||||||
image: returntocorp/semgrep:1.85.0
|
image: {{ .Values.images.semgrep | quote }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
mkdir -p /workspace/reports
|
mkdir -p /workspace/reports
|
||||||
semgrep scan --config auto --sarif --output /workspace/reports/semgrep.sarif /workspace || true
|
semgrep scan --config auto --sarif --output /workspace/reports/semgrep.sarif "/workspace/{{ `{{inputs.parameters.working-dir}}` }}" || true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
{{- define "template.scan-socketdev" }}
|
{{- define "template.scan-socketdev" -}}
|
||||||
- name: scan-socketdev
|
- name: scan-socketdev
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
container:
|
container:
|
||||||
image: socketdev/socketcli:latest
|
image: {{ .Values.images.socketdev | quote }}
|
||||||
env:
|
env:
|
||||||
- name: SOCKET_DEV_API_KEY
|
- name: SOCKET_DEV_API_KEY
|
||||||
valueFrom:
|
valueFrom:
|
||||||
@@ -15,7 +18,7 @@
|
|||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
mkdir -p /workspace/reports
|
mkdir -p /workspace/reports
|
||||||
socketdev scan /workspace --format json --output /workspace/reports/socketdev.json || true
|
socketdev scan "/workspace/{{ `{{inputs.parameters.working-dir}}` }}" --format json --output /workspace/reports/socketdev.json || true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
{{- define "template.scan-syft-grype" }}
|
{{- define "template.scan-syft-grype" -}}
|
||||||
- name: scan-syft-grype
|
- name: scan-syft-grype
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
container:
|
container:
|
||||||
image: anchore/syft:latest
|
image: {{ .Values.images.syftGrype | quote }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
mkdir -p /workspace/reports
|
mkdir -p /workspace/reports
|
||||||
syft scan dir:/workspace -o cyclonedx-json=/workspace/reports/sbom.json || true
|
syft scan dir:/workspace/{{ `{{inputs.parameters.working-dir}}` }} -o cyclonedx-json=/workspace/reports/sbom.json || true
|
||||||
grype sbom:/workspace/reports/sbom.json -o sarif=/workspace/reports/grype.sarif || true
|
grype sbom:/workspace/reports/sbom.json -o sarif=/workspace/reports/grype.sarif || true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
{{- define "template.scan-trufflehog" }}
|
{{- define "template.scan-trufflehog" -}}
|
||||||
- name: scan-trufflehog
|
- name: scan-trufflehog
|
||||||
|
inputs:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
container:
|
container:
|
||||||
image: trufflesecurity/trufflehog:latest
|
image: {{ .Values.images.trufflehog | quote }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
mkdir -p /workspace/reports
|
mkdir -p /workspace/reports
|
||||||
trufflehog filesystem /workspace --json > /workspace/reports/trufflehog.json || true
|
trufflehog filesystem "/workspace/{{ `{{inputs.parameters.working-dir}}` }}" --json > /workspace/reports/trufflehog.json || true
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
{{- define "template.upload-defectdojo" }}
|
{{- define "template.upload-defectdojo" -}}
|
||||||
- name: upload-defectdojo
|
- name: upload-defectdojo
|
||||||
container:
|
container:
|
||||||
image: agentguard-tools:latest
|
image: {{ include "template.tools-image" . | quote }}
|
||||||
|
imagePullPolicy: {{ .Values.pipeline.toolsImage.pullPolicy }}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: amp-security-pipeline-secrets
|
||||||
env:
|
env:
|
||||||
- name: DEFECTDOJO_URL
|
- name: DEFECTDOJO_PRODUCT_TYPE_NAME
|
||||||
valueFrom:
|
value: {{ .Values.defectdojo.productTypeName | quote }}
|
||||||
secretKeyRef:
|
- name: DEFECTDOJO_PRODUCT_NAME
|
||||||
name: amp-security-pipeline-secrets
|
value: {{ .Values.defectdojo.productName | quote }}
|
||||||
key: DEFECTDOJO_URL
|
- name: DEFECTDOJO_ENGAGEMENT_NAME
|
||||||
- name: DEFECTDOJO_API_TOKEN
|
value: {{ .Values.defectdojo.engagementName | quote }}
|
||||||
valueFrom:
|
- name: DEFECTDOJO_MINIMUM_SEVERITY
|
||||||
secretKeyRef:
|
value: {{ .Values.defectdojo.minimumSeverity | quote }}
|
||||||
name: amp-security-pipeline-secrets
|
- name: DEFECTDOJO_ACTIVE
|
||||||
key: DEFECTDOJO_API_TOKEN
|
value: {{ .Values.defectdojo.active | quote }}
|
||||||
|
- name: DEFECTDOJO_VERIFIED
|
||||||
|
value: {{ .Values.defectdojo.verified | quote }}
|
||||||
|
- name: DEFECTDOJO_CLOSE_OLD_FINDINGS
|
||||||
|
value: {{ .Values.defectdojo.closeOldFindings | quote }}
|
||||||
|
- name: DEFECTDOJO_AUTO_CREATE_CONTEXT
|
||||||
|
value: {{ .Values.defectdojo.autoCreateContext | quote }}
|
||||||
command:
|
command:
|
||||||
- node
|
- node
|
||||||
- /app/dist/upload-defectdojo.js
|
- /app/dist/upload-defectdojo.js
|
||||||
|
|||||||
@@ -1,38 +1,33 @@
|
|||||||
{{- define "template.upload-storage" }}
|
{{- define "template.upload-storage" -}}
|
||||||
- name: upload-storage
|
- name: upload-storage
|
||||||
container:
|
container:
|
||||||
image: amazon/aws-cli:2.15.40
|
image: {{ .Values.images.awsCli }}
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: amp-security-pipeline-secrets
|
||||||
env:
|
env:
|
||||||
- name: AWS_ACCESS_KEY_ID
|
- name: REPORTS_BUCKET
|
||||||
valueFrom:
|
value: {{ .Values.storage.reportsBucket | quote }}
|
||||||
secretKeyRef:
|
- name: REPO_NAME
|
||||||
name: amp-security-pipeline-secrets
|
value: {{ .Values.pipeline.repoName | quote }}
|
||||||
key: AWS_ACCESS_KEY_ID
|
- name: STORAGE_ENDPOINT
|
||||||
- name: AWS_SECRET_ACCESS_KEY
|
value: {{ .Values.storage.endpoint | quote }}
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: AWS_SECRET_ACCESS_KEY
|
|
||||||
- name: MINIO_ROOT_USER
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: MINIO_ROOT_USER
|
|
||||||
- name: MINIO_ROOT_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: amp-security-pipeline-secrets
|
|
||||||
key: MINIO_ROOT_PASSWORD
|
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
args:
|
args:
|
||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
repo_name="${REPO_NAME:-repo}"
|
export AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY_ID:-}"
|
||||||
|
export AWS_SECRET_ACCESS_KEY="${S3_SECRET_ACCESS_KEY:-}"
|
||||||
commit_sha="${GIT_COMMIT_SHA:-unknown}"
|
commit_sha="${GIT_COMMIT_SHA:-unknown}"
|
||||||
report_date="$(date -u +%F)"
|
report_date="$(date -u +%F)"
|
||||||
aws s3 sync /workspace/reports "s3://${REPORTS_BUCKET:-security-reports}/${repo_name}/${report_date}/${commit_sha}/"
|
sync_target="s3://${REPORTS_BUCKET}/${REPO_NAME}/${report_date}/${commit_sha}/"
|
||||||
|
if [ -n "${STORAGE_ENDPOINT}" ]; then
|
||||||
|
aws --endpoint-url "${STORAGE_ENDPOINT}" s3 sync /workspace/reports "${sync_target}"
|
||||||
|
else
|
||||||
|
aws s3 sync /workspace/reports "${sync_target}"
|
||||||
|
fi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
{{- define "template.workflow.security-pipeline.tasks" -}}
|
||||||
|
- name: clone
|
||||||
|
template: clone-repo
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: repo-url
|
||||||
|
value: {{ `{{workflow.parameters.repo-url}}` | quote }}
|
||||||
|
- name: git-revision
|
||||||
|
value: {{ `{{workflow.parameters.git-revision}}` | quote }}
|
||||||
|
- name: scanners
|
||||||
|
dependencies:
|
||||||
|
- clone
|
||||||
|
template: parallel-scanners
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: {{ `{{workflow.parameters.working-dir}}` | quote }}
|
||||||
|
- name: enforce-policy
|
||||||
|
dependencies:
|
||||||
|
- scanners
|
||||||
|
template: enforce-policy
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: fail-on-cvss
|
||||||
|
value: {{ `{{workflow.parameters.fail-on-cvss}}` | quote }}
|
||||||
|
{{- if .Values.storage.enabled }}
|
||||||
|
- name: upload-storage
|
||||||
|
dependencies:
|
||||||
|
- scanners
|
||||||
|
template: upload-storage
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.defectdojo.enabled }}
|
||||||
|
- name: upload-defectdojo
|
||||||
|
dependencies:
|
||||||
|
- scanners
|
||||||
|
template: upload-defectdojo
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "template.workflow.parallel-scanners.tasks" -}}
|
||||||
|
{{- /* Scanner fan-out is data-driven from pipeline.scanners in values.yaml. */ -}}
|
||||||
|
{{- range $scanner := .Values.pipeline.scanners }}
|
||||||
|
- name: {{ $scanner }}
|
||||||
|
template: scan-{{ $scanner }}
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- name: working-dir
|
||||||
|
value: {{ `{{inputs.parameters.working-dir}}` | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "template.workflow.named-templates" -}}
|
||||||
|
{{- /* Keep the main workflow file focused on orchestration; implementations are included here. */ -}}
|
||||||
|
{{- range $scanner := .Values.pipeline.scanners }}
|
||||||
|
{{ include (printf "template.scan-%s" $scanner) $ }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.storage.enabled }}
|
||||||
|
{{ include "template.upload-storage" . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.defectdojo.enabled }}
|
||||||
|
{{ include "template.upload-defectdojo" . }}
|
||||||
|
{{- end }}
|
||||||
|
{{ include "template.enforce-policy" . }}
|
||||||
|
{{- end }}
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
apiVersion: argoproj.io/v1alpha1
|
apiVersion: argoproj.io/v1alpha1
|
||||||
kind: ClusterWorkflowTemplate
|
kind: ClusterWorkflowTemplate
|
||||||
metadata:
|
metadata:
|
||||||
name: amp-security-pipeline-v1.0.0
|
name: {{ .Values.pipeline.name }}
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: default
|
serviceAccountName: {{ .Values.pipeline.serviceAccountName }}
|
||||||
entrypoint: security-pipeline
|
entrypoint: security-pipeline
|
||||||
|
onExit: pipeline-exit-hook
|
||||||
volumeClaimTemplates:
|
volumeClaimTemplates:
|
||||||
- metadata:
|
- metadata:
|
||||||
name: workspace
|
name: workspace
|
||||||
@@ -14,109 +15,57 @@ spec:
|
|||||||
- ReadWriteOnce
|
- ReadWriteOnce
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: 1Gi
|
storage: {{ .Values.pipeline.workspace.storage }}
|
||||||
arguments:
|
arguments:
|
||||||
parameters:
|
parameters:
|
||||||
- name: working-dir
|
- name: working-dir
|
||||||
value: .
|
value: {{ .Values.pipeline.workingDir | quote }}
|
||||||
- name: fail-on-cvss
|
- name: fail-on-cvss
|
||||||
value: "7.0"
|
value: {{ .Values.pipeline.failOnCvss | quote }}
|
||||||
- name: repo-url
|
- name: repo-url
|
||||||
- name: git-revision
|
- name: git-revision
|
||||||
value: main
|
value: {{ .Values.pipeline.gitRevision | quote }}
|
||||||
templates:
|
templates:
|
||||||
|
# Top-level DAG wiring lives here so the workflow flow stays readable.
|
||||||
- name: security-pipeline
|
- name: security-pipeline
|
||||||
dag:
|
dag:
|
||||||
tasks:
|
tasks:
|
||||||
- name: clone
|
{{ include "template.workflow.security-pipeline.tasks" . | nindent 10 }}
|
||||||
template: clone-repo
|
|
||||||
arguments:
|
# Concrete task implementations stay below.
|
||||||
parameters:
|
|
||||||
- name: repo-url
|
|
||||||
value: "{{workflow.parameters.repo-url}}"
|
|
||||||
- name: git-revision
|
|
||||||
value: "{{workflow.parameters.git-revision}}"
|
|
||||||
- name: scanners
|
|
||||||
dependencies:
|
|
||||||
- clone
|
|
||||||
template: parallel-scanners
|
|
||||||
arguments:
|
|
||||||
parameters:
|
|
||||||
- name: working-dir
|
|
||||||
value: "{{workflow.parameters.working-dir}}"
|
|
||||||
- name: fail-on-cvss
|
|
||||||
value: "{{workflow.parameters.fail-on-cvss}}"
|
|
||||||
- name: upload-storage
|
|
||||||
dependencies:
|
|
||||||
- scanners
|
|
||||||
template: upload-storage
|
|
||||||
- name: upload-defectdojo
|
|
||||||
dependencies:
|
|
||||||
- scanners
|
|
||||||
template: upload-defectdojo
|
|
||||||
- name: enforce-policy
|
|
||||||
dependencies:
|
|
||||||
- upload-storage
|
|
||||||
- upload-defectdojo
|
|
||||||
template: enforce-policy
|
|
||||||
arguments:
|
|
||||||
parameters:
|
|
||||||
- name: fail-on-cvss
|
|
||||||
value: "{{workflow.parameters.fail-on-cvss}}"
|
|
||||||
- name: sinks-and-enforcement
|
|
||||||
dependencies:
|
|
||||||
- scanners
|
|
||||||
template: sinks-and-enforcement
|
|
||||||
- name: clone-repo
|
- name: clone-repo
|
||||||
inputs:
|
inputs:
|
||||||
parameters:
|
parameters:
|
||||||
- name: repo-url
|
- name: repo-url
|
||||||
- name: git-revision
|
- name: git-revision
|
||||||
container:
|
container:
|
||||||
image: alpine/git:2.45.2
|
image: {{ .Values.images.git }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
args:
|
args:
|
||||||
- git clone --branch "{{inputs.parameters.git-revision}}" --single-branch "{{inputs.parameters.repo-url}}" /workspace
|
- git clone --branch {{ `{{inputs.parameters.git-revision}}` | quote }} --single-branch {{ `{{inputs.parameters.repo-url}}` | quote }} /workspace
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: workspace
|
- name: workspace
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
|
||||||
- name: parallel-scanners
|
- name: parallel-scanners
|
||||||
inputs:
|
inputs:
|
||||||
parameters:
|
parameters:
|
||||||
- name: working-dir
|
- name: working-dir
|
||||||
- name: fail-on-cvss
|
|
||||||
dag:
|
dag:
|
||||||
tasks:
|
tasks:
|
||||||
{{- range $scanner := list "trufflehog" "semgrep" "kics" "socketdev" "syft-grype" "defectdojo" }}
|
{{ include "template.workflow.parallel-scanners.tasks" . | nindent 10 }}
|
||||||
- name: {{ $scanner }}
|
|
||||||
template: scan-{{ $scanner }}
|
- name: pipeline-exit-hook
|
||||||
arguments:
|
|
||||||
parameters:
|
|
||||||
- name: working-dir
|
|
||||||
value: "{{inputs.parameters.working-dir}}"
|
|
||||||
{{- end }}
|
|
||||||
- name: sinks-and-enforcement
|
|
||||||
container:
|
container:
|
||||||
image: curlimages/curl:latest
|
image: {{ .Values.images.curl }}
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
args:
|
args:
|
||||||
- |
|
- |
|
||||||
set -eu
|
set -eu
|
||||||
echo "Pipeline complete. You can configure a webhook notification here."
|
echo "Pipeline completed with status: {{ `{{workflow.status}}` }}"
|
||||||
if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
|
{{ include "template.workflow.named-templates" . | nindent 4 }}
|
||||||
curl -X POST -H 'Content-type: application/json' --data '{"text":"Security Pipeline Finished"}' "${SLACK_WEBHOOK_URL}" || true
|
|
||||||
fi
|
|
||||||
{{ include "template.scan-syft-grype" . | indent 4 }}
|
|
||||||
{{ include "template.scan-socketdev" . | indent 4 }}
|
|
||||||
{{ include "template.scan-defectdojo" . | indent 4 }}
|
|
||||||
{{ include "template.scan-semgrep" . | indent 4 }}
|
|
||||||
{{ include "template.scan-trufflehog" . | indent 4 }}
|
|
||||||
{{ include "template.scan-kics" . | indent 4 }}
|
|
||||||
{{ include "template.upload-defectdojo" . | indent 4 }}
|
|
||||||
{{ include "template.upload-storage" . | indent 4 }}
|
|
||||||
{{ include "template.enforce-policy" . | indent 4 }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{{- if .Values.pipeline.enabled }}
|
{{- if and .Values.pipeline.enabled .Values.infisical.enabled }}
|
||||||
apiVersion: infisical.com/v1alpha1
|
apiVersion: infisical.com/v1alpha1
|
||||||
kind: InfisicalSecret
|
kind: InfisicalSecret
|
||||||
metadata:
|
metadata:
|
||||||
@@ -16,18 +16,12 @@ spec:
|
|||||||
- secretKey: PULUMI_ACCESS_TOKEN
|
- secretKey: PULUMI_ACCESS_TOKEN
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: PULUMI_ACCESS_TOKEN
|
key: PULUMI_ACCESS_TOKEN
|
||||||
- secretKey: AWS_ACCESS_KEY_ID
|
- secretKey: S3_ACCESS_KEY_ID
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: AWS_ACCESS_KEY_ID
|
key: S3_ACCESS_KEY_ID
|
||||||
- secretKey: AWS_SECRET_ACCESS_KEY
|
- secretKey: S3_SECRET_ACCESS_KEY
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: AWS_SECRET_ACCESS_KEY
|
key: S3_SECRET_ACCESS_KEY
|
||||||
- secretKey: MINIO_ROOT_USER
|
|
||||||
remoteRef:
|
|
||||||
key: MINIO_ROOT_USER
|
|
||||||
- secretKey: MINIO_ROOT_PASSWORD
|
|
||||||
remoteRef:
|
|
||||||
key: MINIO_ROOT_PASSWORD
|
|
||||||
- secretKey: DEFECTDOJO_URL
|
- secretKey: DEFECTDOJO_URL
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: DEFECTDOJO_URL
|
key: DEFECTDOJO_URL
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"pipeline": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Core Argo workflow settings.",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Render the ClusterWorkflowTemplate when true."
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the ClusterWorkflowTemplate resource.",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"serviceAccountName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service account used by workflow pods.",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"workingDir": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Repository path scanned inside the cloned workspace.",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"gitRevision": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Default git revision to clone when the workflow caller does not override it.",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"failOnCvss": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "CVSS threshold passed to the policy enforcement utility.",
|
||||||
|
"pattern": "^[0-9]+(\\.[0-9]+)?$"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "PVC configuration for the shared workspace volume.",
|
||||||
|
"properties": {
|
||||||
|
"storage": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Requested workspace PVC size, for example 1Gi.",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"storage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"repoName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Repository name used in storage upload paths.",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"scanners": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Ordered list of scanner templates wired into the scanner fan-out DAG.",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"trufflehog",
|
||||||
|
"semgrep",
|
||||||
|
"kics",
|
||||||
|
"socketdev",
|
||||||
|
"syft-grype",
|
||||||
|
"pulumi-crossguard"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"toolsImage": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Custom image that packages the Node utilities used by the workflow.",
|
||||||
|
"properties": {
|
||||||
|
"repository": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"pullPolicy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Always",
|
||||||
|
"IfNotPresent",
|
||||||
|
"Never"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"repository",
|
||||||
|
"tag",
|
||||||
|
"pullPolicy"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"name",
|
||||||
|
"serviceAccountName",
|
||||||
|
"workingDir",
|
||||||
|
"gitRevision",
|
||||||
|
"failOnCvss",
|
||||||
|
"workspace",
|
||||||
|
"repoName",
|
||||||
|
"scanners",
|
||||||
|
"toolsImage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Container images used by each workflow step.",
|
||||||
|
"properties": {
|
||||||
|
"git": { "type": "string", "minLength": 1 },
|
||||||
|
"trufflehog": { "type": "string", "minLength": 1 },
|
||||||
|
"semgrep": { "type": "string", "minLength": 1 },
|
||||||
|
"kics": { "type": "string", "minLength": 1 },
|
||||||
|
"socketdev": { "type": "string", "minLength": 1 },
|
||||||
|
"syftGrype": { "type": "string", "minLength": 1 },
|
||||||
|
"pulumiCrossguard": { "type": "string", "minLength": 1 },
|
||||||
|
"awsCli": { "type": "string", "minLength": 1 },
|
||||||
|
"curl": { "type": "string", "minLength": 1 }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"git",
|
||||||
|
"trufflehog",
|
||||||
|
"semgrep",
|
||||||
|
"kics",
|
||||||
|
"socketdev",
|
||||||
|
"syftGrype",
|
||||||
|
"pulumiCrossguard",
|
||||||
|
"awsCli",
|
||||||
|
"curl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"storage": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Optional raw report upload configuration.",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"reportsBucket": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional custom S3 endpoint for MinIO or another compatible store."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"reportsBucket",
|
||||||
|
"endpoint"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pulumi": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Pulumi CrossGuard scanner settings.",
|
||||||
|
"properties": {
|
||||||
|
"policyPackPath": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"policyPackPath"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"defectdojo": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Optional DefectDojo upload step configuration.",
|
||||||
|
"properties": {
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"productTypeName": { "type": "string", "minLength": 1 },
|
||||||
|
"productName": { "type": "string", "minLength": 1 },
|
||||||
|
"engagementName": { "type": "string", "minLength": 1 },
|
||||||
|
"minimumSeverity": { "type": "string", "minLength": 1 },
|
||||||
|
"active": { "type": "boolean" },
|
||||||
|
"verified": { "type": "boolean" },
|
||||||
|
"closeOldFindings": { "type": "boolean" },
|
||||||
|
"autoCreateContext": { "type": "boolean" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"productTypeName",
|
||||||
|
"productName",
|
||||||
|
"engagementName",
|
||||||
|
"minimumSeverity",
|
||||||
|
"active",
|
||||||
|
"verified",
|
||||||
|
"closeOldFindings",
|
||||||
|
"autoCreateContext"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"infisical": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"description": "Optional Infisical operator integration.",
|
||||||
|
"properties": {
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"workspaceSlug": { "type": "string" },
|
||||||
|
"projectSlug": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"workspaceSlug",
|
||||||
|
"projectSlug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"pipeline",
|
||||||
|
"images",
|
||||||
|
"storage",
|
||||||
|
"pulumi",
|
||||||
|
"defectdojo",
|
||||||
|
"infisical"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
pipeline:
|
||||||
|
enabled: true
|
||||||
|
name: amp-security-pipeline-v1.0.0
|
||||||
|
serviceAccountName: default
|
||||||
|
workingDir: .
|
||||||
|
gitRevision: main
|
||||||
|
failOnCvss: "7.0"
|
||||||
|
workspace:
|
||||||
|
storage: 1Gi
|
||||||
|
repoName: agentguard-ci
|
||||||
|
# Order here matches the scanner fan-out in the workflow DAG.
|
||||||
|
scanners:
|
||||||
|
- trufflehog
|
||||||
|
- semgrep
|
||||||
|
- kics
|
||||||
|
- socketdev
|
||||||
|
- syft-grype
|
||||||
|
- pulumi-crossguard
|
||||||
|
toolsImage:
|
||||||
|
repository: agentguard-tools
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
images:
|
||||||
|
git: alpine/git:2.45.2
|
||||||
|
trufflehog: trufflesecurity/trufflehog:latest
|
||||||
|
semgrep: returntocorp/semgrep:1.85.0
|
||||||
|
kics: checkmarx/kics:1.7.14
|
||||||
|
socketdev: socketdev/socketcli:latest
|
||||||
|
syftGrype: anchore/syft:latest
|
||||||
|
pulumiCrossguard: pulumi/pulumi:3.154.0
|
||||||
|
awsCli: amazon/aws-cli:2.15.40
|
||||||
|
curl: curlimages/curl:8.8.0
|
||||||
|
|
||||||
|
storage:
|
||||||
|
enabled: false
|
||||||
|
reportsBucket: security-reports
|
||||||
|
endpoint: ""
|
||||||
|
|
||||||
|
pulumi:
|
||||||
|
policyPackPath: policy-pack
|
||||||
|
|
||||||
|
defectdojo:
|
||||||
|
enabled: false
|
||||||
|
productTypeName: Homelab Security
|
||||||
|
productName: agentguard-ci
|
||||||
|
engagementName: Default Pipeline
|
||||||
|
minimumSeverity: Info
|
||||||
|
active: true
|
||||||
|
verified: true
|
||||||
|
closeOldFindings: false
|
||||||
|
autoCreateContext: true
|
||||||
|
|
||||||
|
infisical:
|
||||||
|
enabled: false
|
||||||
|
workspaceSlug: ""
|
||||||
|
projectSlug: ""
|
||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
chart_dir="${repo_root}/helm"
|
||||||
|
rendered_manifest="$(mktemp --suffix=.yaml)"
|
||||||
|
release_name="${RELEASE_NAME:-agentguard-ci}"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "${rendered_manifest}"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
if ! command -v "$1" >/dev/null 2>&1; then
|
||||||
|
printf 'Missing required command: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_kubectl_client_check() {
|
||||||
|
require_command kubectl
|
||||||
|
if ! kubectl apply --dry-run=client --validate=false -f "${rendered_manifest}" >/dev/null 2>&1; then
|
||||||
|
cat <<'EOF' >&2
|
||||||
|
kubectl client dry-run failed.
|
||||||
|
For Argo CRDs, this check can still be environment-sensitive and is optional here.
|
||||||
|
Re-run without RUN_KUBECTL_CLIENT_CHECK=1, or use RUN_KUBECTL_SERVER_CHECK=1 against a cluster with the CRDs installed.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_kubectl_server_check() {
|
||||||
|
require_command kubectl
|
||||||
|
kubectl apply --dry-run=server -f "${rendered_manifest}" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
require_command helm
|
||||||
|
require_command argo
|
||||||
|
|
||||||
|
printf '==> helm lint\n'
|
||||||
|
helm lint "${chart_dir}"
|
||||||
|
|
||||||
|
printf '==> helm template\n'
|
||||||
|
helm template "${release_name}" "${chart_dir}" > "${rendered_manifest}"
|
||||||
|
|
||||||
|
printf '==> argo lint --offline\n'
|
||||||
|
argo lint --offline --kinds=clusterworkflowtemplates "${rendered_manifest}"
|
||||||
|
|
||||||
|
if [[ "${RUN_KUBECTL_CLIENT_CHECK:-0}" == "1" ]]; then
|
||||||
|
printf '==> kubectl apply --dry-run=client\n'
|
||||||
|
run_kubectl_client_check
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${RUN_KUBECTL_SERVER_CHECK:-0}" == "1" ]]; then
|
||||||
|
printf '==> kubectl apply --dry-run=server\n'
|
||||||
|
run_kubectl_server_check
|
||||||
|
fi
|
||||||
+2
-4
@@ -1,17 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "tools",
|
"name": "tools",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Custom pipeline utilities for agentguard-ci",
|
||||||
"main": "index.js",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run src",
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
|
|||||||
@@ -1,68 +1,102 @@
|
|||||||
import * as fs from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
export async function uploadReports() {
|
function resolveScanType(fileName: string): string | undefined {
|
||||||
const baseUrl = (process.env.DEFECTDOJO_URL || "").replace(/\/$/, "");
|
if (fileName.endsWith('.sarif')) {
|
||||||
|
return 'SARIF';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName === 'generic-findings.json') {
|
||||||
|
return 'Generic Findings Import';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function uploadReports(): Promise<void> {
|
||||||
|
const baseUrl = (process.env.DEFECTDOJO_URL || '').replace(/\/$/, '');
|
||||||
const apiToken = process.env.DEFECTDOJO_API_TOKEN;
|
const apiToken = process.env.DEFECTDOJO_API_TOKEN;
|
||||||
const productName = process.env.DEFECTDOJO_PRODUCT_NAME || "agentguard-ci";
|
const productTypeName = process.env.DEFECTDOJO_PRODUCT_TYPE_NAME || 'Homelab Security';
|
||||||
|
const productName = process.env.DEFECTDOJO_PRODUCT_NAME || 'agentguard-ci';
|
||||||
|
const engagementName = process.env.DEFECTDOJO_ENGAGEMENT_NAME || 'Default Pipeline';
|
||||||
|
const minimumSeverity = process.env.DEFECTDOJO_MINIMUM_SEVERITY || 'Info';
|
||||||
|
const active = process.env.DEFECTDOJO_ACTIVE || 'true';
|
||||||
|
const verified = process.env.DEFECTDOJO_VERIFIED || 'true';
|
||||||
|
const closeOldFindings = process.env.DEFECTDOJO_CLOSE_OLD_FINDINGS || 'false';
|
||||||
|
const autoCreateContext = process.env.DEFECTDOJO_AUTO_CREATE_CONTEXT || 'true';
|
||||||
|
|
||||||
if (!baseUrl || !apiToken) {
|
if (!baseUrl || !apiToken) {
|
||||||
console.error("DEFECTDOJO_URL and DEFECTDOJO_API_TOKEN must be set.");
|
console.error('DEFECTDOJO_URL and DEFECTDOJO_API_TOKEN must be set.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const scanMap: Record<string, string> = {
|
const reportsDir = '/workspace/reports';
|
||||||
".sarif": "SARIF",
|
let fileNames: string[];
|
||||||
".json": "Generic Findings Import",
|
|
||||||
};
|
|
||||||
|
|
||||||
const reportsDir = "/workspace/reports";
|
try {
|
||||||
if (!fs.existsSync(reportsDir)) {
|
fileNames = (await fs.readdir(reportsDir)).sort();
|
||||||
console.log("No reports directory found.");
|
} catch {
|
||||||
|
console.log('No reports directory found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = fs.readdirSync(reportsDir).sort();
|
for (const fileName of fileNames) {
|
||||||
|
const fullPath = path.join(reportsDir, fileName);
|
||||||
|
const stats = await fs.stat(fullPath);
|
||||||
|
if (!stats.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
const scanType = resolveScanType(fileName);
|
||||||
const fullPath = path.join(reportsDir, file);
|
if (!scanType) {
|
||||||
if (!fs.statSync(fullPath).isFile()) continue;
|
console.log(`Skipping ${fileName}: no DefectDojo importer is configured for this file yet.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const ext = path.extname(file);
|
const reportContents = await fs.readFile(fullPath);
|
||||||
const scanType = scanMap[ext];
|
const form = new FormData();
|
||||||
if (!scanType) continue;
|
form.append('scan_type', scanType);
|
||||||
|
form.append('product_type_name', productTypeName);
|
||||||
|
form.append('product_name', productName);
|
||||||
|
form.append('engagement_name', engagementName);
|
||||||
|
form.append('test_title', fileName);
|
||||||
|
form.append('minimum_severity', minimumSeverity);
|
||||||
|
form.append('active', active);
|
||||||
|
form.append('verified', verified);
|
||||||
|
form.append('close_old_findings', closeOldFindings);
|
||||||
|
form.append('auto_create_context', autoCreateContext);
|
||||||
|
form.append('file', new Blob([reportContents]), fileName);
|
||||||
|
|
||||||
console.log(`Uploading ${file} as ${scanType}...`);
|
console.log(`Uploading ${fileName} to DefectDojo as ${scanType}...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseUrl}/api/v2/import-scan/`, {
|
const response = await fetch(`${baseUrl}/api/v2/reimport-scan/`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Token ${apiToken}`,
|
Authorization: `Token ${apiToken}`,
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: form,
|
||||||
scan_type: scanType,
|
|
||||||
product_name: productName,
|
|
||||||
file_name: file,
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
console.error(`Failed to upload ${file}: ${response.status} ${response.statusText} - ${text}`);
|
console.error(`Failed to upload ${fileName}: ${response.status} ${response.statusText} - ${text}`);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
} else {
|
continue;
|
||||||
console.log(`Successfully uploaded ${file}`);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error(`Network error uploading ${file}:`, e);
|
console.log(`Successfully uploaded ${fileName}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Network error uploading ${fileName}:`, error);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||||
uploadReports();
|
uploadReports().catch((error: unknown) => {
|
||||||
|
console.error('Unexpected upload failure:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -8,7 +8,9 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"types": ["node", "vitest/globals"],
|
||||||
|
"lib": ["ES2022", "DOM"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": ["src/**/*.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user