Compare commits

...

16 Commits

27 changed files with 1342 additions and 352 deletions
+5
View File
@@ -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
View File
@@ -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
+99 -37
View File
@@ -1,68 +1,130 @@
# 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:
* **TruffleHog**: Verifies leaked API keys dynamically to prevent false-positives from AI hallucinations.
* **Semgrep**: Scans first-party application logic for vulnerabilities (e.g., SQLi, XSS).
* **Socket.dev**: Analyzes dependencies for supply chain attacks, malware, and typosquatting.
* **Pulumi CrossGuard**: Validates Infrastructure as Code against policy packs.
* **Syft + Grype**: Generates SBOMs and scans for container vulnerabilities scored via EPSS.
* **KICS**: Scans infrastructure misconfigurations.
* **DefectDojo & MinIO**: Uploads findings to a centralized ASPM dashboard and raw SARIF/JSON reports to S3-compatible storage.
* **Policy Enforcement**: Custom TypeScript logic automatically fails the build if any findings exceed your defined CVSS severity threshold.
- Runs TruffleHog for secret scanning.
- Runs Semgrep for first-party code scanning.
- Runs KICS for infrastructure misconfiguration scanning.
- Runs Socket.dev for dependency risk scanning.
- Runs Syft and Grype for SBOM generation and vulnerability scanning.
- Runs Pulumi CrossGuard for policy-pack validation.
- Uploads supported reports to DefectDojo when enabled.
- Uploads raw reports to S3-compatible storage when enabled.
- 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**
* **Infisical Kubernetes Operator** (for secret injection)
* **DefectDojo** (for vulnerability dashboards)
* **MinIO / S3** (for raw report storage)
- Argo Workflows
- Infisical Kubernetes Operator, if you want this chart to sync secrets automatically
- DefectDojo, if you want report ingestion enabled
- MinIO or another S3-compatible store, if you want raw report uploads enabled
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
cd tools
docker build -t 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
Update `helm/values.yaml` (if applicable) and configure your Infisical integration:
### 2. Configure values
Start from [`helm/values.yaml`](helm/values.yaml) and set at least:
```yaml
pipeline:
enabled: true
toolsImage:
repository: your-registry/agentguard-tools
tag: latest
infisical:
workspaceSlug: "your-workspace-id"
projectSlug: "your-project-id"
enabled: true
workspaceSlug: your-workspace-id
projectSlug: your-project-id
storage:
enabled: false
defectdojo:
enabled: false
```
### 3. Deploy via Helm
Install the pipeline and its associated resources to your cluster:
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.
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
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.
+7
View File
@@ -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
+207
View File
@@ -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.
+6
View File
@@ -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"
+4 -3
View File
@@ -1,16 +1,17 @@
{{- define "template.enforce-policy" }}
{{- define "template.enforce-policy" -}}
- name: enforce-policy
inputs:
parameters:
- name: fail-on-cvss
container:
image: agentguard-tools:latest
image: {{ include "template.tools-image" . | quote }}
imagePullPolicy: {{ .Values.pipeline.toolsImage.pullPolicy }}
command:
- node
- /app/dist/enforce-policy.js
env:
- name: FAIL_ON_CVSS
value: "{{inputs.parameters.fail-on-cvss}}"
value: {{ `{{inputs.parameters.fail-on-cvss}}` | quote }}
volumeMounts:
- name: workspace
mountPath: /workspace
+3
View File
@@ -0,0 +1,3 @@
{{- define "template.tools-image" -}}
{{- printf "%s:%s" .Values.pipeline.toolsImage.repository .Values.pipeline.toolsImage.tag -}}
{{- end }}
-33
View File
@@ -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 }}
+6 -3
View File
@@ -1,7 +1,10 @@
{{- define "template.scan-kics" }}
{{- define "template.scan-kics" -}}
- name: scan-kics
inputs:
parameters:
- name: working-dir
container:
image: checkmarx/kics:1.7.14
image: {{ .Values.images.kics | quote }}
command:
- sh
- -c
@@ -9,7 +12,7 @@
- |
set -eu
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
exit 0
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 }}
+6 -3
View File
@@ -1,7 +1,10 @@
{{- define "template.scan-semgrep" }}
{{- define "template.scan-semgrep" -}}
- name: scan-semgrep
inputs:
parameters:
- name: working-dir
container:
image: returntocorp/semgrep:1.85.0
image: {{ .Values.images.semgrep | quote }}
command:
- sh
- -c
@@ -9,7 +12,7 @@
- |
set -eu
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:
- name: workspace
mountPath: /workspace
+6 -3
View File
@@ -1,7 +1,10 @@
{{- define "template.scan-socketdev" }}
{{- define "template.scan-socketdev" -}}
- name: scan-socketdev
inputs:
parameters:
- name: working-dir
container:
image: socketdev/socketcli:latest
image: {{ .Values.images.socketdev | quote }}
env:
- name: SOCKET_DEV_API_KEY
valueFrom:
@@ -15,7 +18,7 @@
- |
set -eu
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:
- name: workspace
mountPath: /workspace
+6 -3
View File
@@ -1,7 +1,10 @@
{{- define "template.scan-syft-grype" }}
{{- define "template.scan-syft-grype" -}}
- name: scan-syft-grype
inputs:
parameters:
- name: working-dir
container:
image: anchore/syft:latest
image: {{ .Values.images.syftGrype | quote }}
command:
- sh
- -c
@@ -9,7 +12,7 @@
- |
set -eu
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
volumeMounts:
- name: workspace
+6 -3
View File
@@ -1,7 +1,10 @@
{{- define "template.scan-trufflehog" }}
{{- define "template.scan-trufflehog" -}}
- name: scan-trufflehog
inputs:
parameters:
- name: working-dir
container:
image: trufflesecurity/trufflehog:latest
image: {{ .Values.images.trufflehog | quote }}
command:
- sh
- -c
@@ -9,7 +12,7 @@
- |
set -eu
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:
- name: workspace
mountPath: /workspace
+22 -12
View File
@@ -1,18 +1,28 @@
{{- define "template.upload-defectdojo" }}
{{- define "template.upload-defectdojo" -}}
- name: upload-defectdojo
container:
image: agentguard-tools:latest
image: {{ include "template.tools-image" . | quote }}
imagePullPolicy: {{ .Values.pipeline.toolsImage.pullPolicy }}
envFrom:
- secretRef:
name: amp-security-pipeline-secrets
env:
- name: DEFECTDOJO_URL
valueFrom:
secretKeyRef:
name: amp-security-pipeline-secrets
key: DEFECTDOJO_URL
- name: DEFECTDOJO_API_TOKEN
valueFrom:
secretKeyRef:
name: amp-security-pipeline-secrets
key: DEFECTDOJO_API_TOKEN
- name: DEFECTDOJO_PRODUCT_TYPE_NAME
value: {{ .Values.defectdojo.productTypeName | quote }}
- name: DEFECTDOJO_PRODUCT_NAME
value: {{ .Values.defectdojo.productName | quote }}
- name: DEFECTDOJO_ENGAGEMENT_NAME
value: {{ .Values.defectdojo.engagementName | quote }}
- name: DEFECTDOJO_MINIMUM_SEVERITY
value: {{ .Values.defectdojo.minimumSeverity | quote }}
- name: DEFECTDOJO_ACTIVE
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:
- node
- /app/dist/upload-defectdojo.js
+19 -24
View File
@@ -1,38 +1,33 @@
{{- define "template.upload-storage" }}
{{- define "template.upload-storage" -}}
- name: upload-storage
container:
image: amazon/aws-cli:2.15.40
image: {{ .Values.images.awsCli }}
envFrom:
- secretRef:
name: amp-security-pipeline-secrets
env:
- 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
- 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
- name: REPORTS_BUCKET
value: {{ .Values.storage.reportsBucket | quote }}
- name: REPO_NAME
value: {{ .Values.pipeline.repoName | quote }}
- name: STORAGE_ENDPOINT
value: {{ .Values.storage.endpoint | quote }}
command:
- sh
- -c
args:
- |
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}"
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:
- name: workspace
mountPath: /workspace
+64
View File
@@ -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 }}
+20 -71
View File
@@ -2,10 +2,11 @@
apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
name: amp-security-pipeline-v1.0.0
name: {{ .Values.pipeline.name }}
spec:
serviceAccountName: default
serviceAccountName: {{ .Values.pipeline.serviceAccountName }}
entrypoint: security-pipeline
onExit: pipeline-exit-hook
volumeClaimTemplates:
- metadata:
name: workspace
@@ -14,109 +15,57 @@ spec:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storage: {{ .Values.pipeline.workspace.storage }}
arguments:
parameters:
- name: working-dir
value: .
value: {{ .Values.pipeline.workingDir | quote }}
- name: fail-on-cvss
value: "7.0"
value: {{ .Values.pipeline.failOnCvss | quote }}
- name: repo-url
- name: git-revision
value: main
value: {{ .Values.pipeline.gitRevision | quote }}
templates:
# Top-level DAG wiring lives here so the workflow flow stays readable.
- 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: 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
{{ include "template.workflow.security-pipeline.tasks" . | nindent 10 }}
# Concrete task implementations stay below.
- name: clone-repo
inputs:
parameters:
- name: repo-url
- name: git-revision
container:
image: alpine/git:2.45.2
image: {{ .Values.images.git }}
command:
- sh
- -c
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:
- name: workspace
mountPath: /workspace
- name: parallel-scanners
inputs:
parameters:
- name: working-dir
- name: fail-on-cvss
dag:
tasks:
{{- range $scanner := list "trufflehog" "semgrep" "kics" "socketdev" "syft-grype" "defectdojo" }}
- name: {{ $scanner }}
template: scan-{{ $scanner }}
arguments:
parameters:
- name: working-dir
value: "{{inputs.parameters.working-dir}}"
{{- end }}
- name: sinks-and-enforcement
{{ include "template.workflow.parallel-scanners.tasks" . | nindent 10 }}
- name: pipeline-exit-hook
container:
image: curlimages/curl:latest
image: {{ .Values.images.curl }}
command:
- sh
- -c
args:
- |
set -eu
echo "Pipeline complete. You can configure a webhook notification here."
if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
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 }}
echo "Pipeline completed with status: {{ `{{workflow.status}}` }}"
{{ include "template.workflow.named-templates" . | nindent 4 }}
{{- end }}
+5 -11
View File
@@ -1,4 +1,4 @@
{{- if .Values.pipeline.enabled }}
{{- if and .Values.pipeline.enabled .Values.infisical.enabled }}
apiVersion: infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
@@ -16,18 +16,12 @@ spec:
- secretKey: PULUMI_ACCESS_TOKEN
remoteRef:
key: PULUMI_ACCESS_TOKEN
- secretKey: AWS_ACCESS_KEY_ID
- secretKey: S3_ACCESS_KEY_ID
remoteRef:
key: AWS_ACCESS_KEY_ID
- secretKey: AWS_SECRET_ACCESS_KEY
key: S3_ACCESS_KEY_ID
- secretKey: S3_SECRET_ACCESS_KEY
remoteRef:
key: AWS_SECRET_ACCESS_KEY
- secretKey: MINIO_ROOT_USER
remoteRef:
key: MINIO_ROOT_USER
- secretKey: MINIO_ROOT_PASSWORD
remoteRef:
key: MINIO_ROOT_PASSWORD
key: S3_SECRET_ACCESS_KEY
- secretKey: DEFECTDOJO_URL
remoteRef:
key: DEFECTDOJO_URL
+234
View File
@@ -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"
]
}
+57
View File
@@ -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: ""
+59
View File
@@ -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
View File
@@ -1,17 +1,15 @@
{
"name": "tools",
"version": "1.0.0",
"description": "",
"main": "index.js",
"description": "Custom pipeline utilities for agentguard-ci",
"type": "module",
"scripts": {
"test": "vitest run",
"test": "vitest run src",
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"@types/node": "^25.6.0",
"tsx": "^4.21.0",
+69 -35
View File
@@ -1,68 +1,102 @@
import * as fs from 'node:fs';
import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
export async function uploadReports() {
const baseUrl = (process.env.DEFECTDOJO_URL || "").replace(/\/$/, "");
function resolveScanType(fileName: string): string | undefined {
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 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) {
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);
}
const scanMap: Record<string, string> = {
".sarif": "SARIF",
".json": "Generic Findings Import",
};
const reportsDir = '/workspace/reports';
let fileNames: string[];
const reportsDir = "/workspace/reports";
if (!fs.existsSync(reportsDir)) {
console.log("No reports directory found.");
try {
fileNames = (await fs.readdir(reportsDir)).sort();
} catch {
console.log('No reports directory found.');
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 fullPath = path.join(reportsDir, file);
if (!fs.statSync(fullPath).isFile()) continue;
const scanType = resolveScanType(fileName);
if (!scanType) {
console.log(`Skipping ${fileName}: no DefectDojo importer is configured for this file yet.`);
continue;
}
const ext = path.extname(file);
const scanType = scanMap[ext];
if (!scanType) continue;
const reportContents = await fs.readFile(fullPath);
const form = new FormData();
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 {
const response = await fetch(`${baseUrl}/api/v2/import-scan/`, {
method: "POST",
const response = await fetch(`${baseUrl}/api/v2/reimport-scan/`, {
method: 'POST',
headers: {
"Authorization": `Token ${apiToken}`,
"Content-Type": "application/json",
Authorization: `Token ${apiToken}`,
},
body: JSON.stringify({
scan_type: scanType,
product_name: productName,
file_name: file,
})
body: form,
});
if (!response.ok) {
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;
} else {
console.log(`Successfully uploaded ${file}`);
continue;
}
} 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;
}
}
}
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
View File
@@ -8,7 +8,9 @@
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"types": ["node", "vitest/globals"],
"lib": ["ES2022", "DOM"]
},
"include": ["src/**/*"]
"include": ["src/**/*.ts"]
}