Compare commits

..

3 Commits

Author SHA1 Message Date
ada 0d80215207 notes about validation workflow 2026-04-30 20:22:08 -06:00
ada e0e7018a55 adding values and their definitions 2026-04-30 20:21:44 -06:00
ada 3676ccf990 adding precommit hooks 2026-04-30 20:21:29 -06:00
5 changed files with 586 additions and 8 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"
+20 -8
View File
@@ -44,18 +44,30 @@ The rendered reference reflects the default values in `helm/values.yaml`, so opt
For fast validation while wiring up infrastructure, use these tools together: For fast validation while wiring up infrastructure, use these tools together:
- `helm lint ./helm` - `./scripts/check-chart.sh`
- `helm template agentguard-ci ./helm` - `RUN_KUBECTL_CLIENT_CHECK=1 ./scripts/check-chart.sh`
- `helm template agentguard-ci ./helm | kubectl apply --dry-run=client -f -` - `RUN_KUBECTL_SERVER_CHECK=1 ./scripts/check-chart.sh`
- `helm template agentguard-ci ./helm | kubectl apply --dry-run=server -f -`
- `argo lint rendered.yaml` 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: Notes:
- `helm lint` catches Helm chart problems. - `helm lint` catches Helm chart problems.
- `kubectl --dry-run=client` catches basic Kubernetes schema issues without talking to the cluster. - `helm template` proves the chart renders successfully with the current values.
- `kubectl --dry-run=server` is better once the cluster already has the Argo and Infisical CRDs installed. - `argo lint --offline` is the most useful Argo-specific local check because it validates the rendered `ClusterWorkflowTemplate` without needing cluster access.
- `argo lint` is the most useful Argo-specific check once you have the Argo CLI installed. - `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 ## Installation
@@ -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
+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"
]
}
+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