HashiCorp Cloud Platform
Scan for secrets in CI/CD pipelines with HCP Vault Radar
HCP Vault Radar provides a CLI tool that allows you to run scans and integrate directly into your continuous integration and continuous deployment (CI/CD) pipelines. By embedding secret scanning into your build process, you can detect leaked secrets before they reach production and optionally block deployments when HCP Vault Radar detects secrets.
Challenge
Development teams need a way to automatically detect secrets in their codebase during the build process. Without automated scanning, leaked credentials can persist in repositories, increasing the risk of unauthorized access and security breaches. Teams require a solution that can:
- Scan code during CI/CD execution without manual intervention
- Generate actionable reports for security teams
- Optionally block deployments when it detects new secrets
- Integrate with existing tooling
For guidance on detecting leaked secrets in CI/CD pipelines, see Detect leaked secrets.
Solution
HCP Vault Radar provides a CLI that works in most CI/CD environments. You can run HCP Vault Radar scans as containerized tasks within your existing CI/CD pipelines. The CLI supports multiple output formats including SARIF (for integration with GitHub Advanced Security and other tools) and JSON.
In this tutorial, you will configure a Tekton pipeline to scan a repository for secrets using the HCP Vault Radar CLI. You will also implement both alerting and blocking modes to meet different security requirements in your development workflow.
For guidance on securing CI/CD pipelines, see CI/CD secrets.
Prerequisites
- Access to the HCP Portal with a user assigned the admin role
- minikube installed
- kubectl installed
Set up the lab
Set up minikube and Tekton to run CI/CD pipelines.
Start minikube
From the terminal where you exported the GitHub personal access token, create a minikube cluster.
$ minikube start 😄 minikube v1.30.1 on Darwin 13.4 (arm64) ✨ Automatically selected the docker driver 📌 Using Docker Desktop driver with root privileges 👍 Starting control plane node minikube in cluster minikube 🚜 Pulling base image ... 🔥 Creating docker container (CPUs=2, Memory=4000MB) ... 🐳 Preparing Kubernetes v1.26.3 on Docker 23.0.2 ... ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔗 Configuring bridge CNI (Container Networking Interface) ... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🔎 Verifying Kubernetes components... 🌟 Enabled addons: storage-provisioner, default-storageclass 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by defaultThe minikube CLI starts a single-node cluster and configures
kubectlfor the cluster.Verify the cluster is running.
$ minikube status minikube type: Control Plane host: Running kubelet: Running apiserver: Running kubeconfig: ConfiguredCreate a PVC for the pipeline workspace.
$ kubectl apply -f - <<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-for-scanning spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi EOFExample output:
persistentvolumeclaim/pvc-for-scanning createdThe PVC allows the pipeline to store cloned repository content and scan results.
minikube is running and ready to use.
Install Tekton on minikube
Before creating Tekton tasks and pipelines, you need Tekton installed on your Kubernetes cluster.
Install Tekton Pipelines on your minikube cluster.
$ kubectl apply --filename https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml ...snip... deployment.apps/tekton-pipelines-webhook created service/tekton-pipelines-webhook createdThis installs the Tekton custom resources and controllers in the tekton-pipelines namespace.
Wait for Tekton pods to be ready.
$ kubectl wait --for=condition=ready pod --all \ --namespace tekton-pipelines \ --timeout=120sExample output:
pod/tekton-events-controller-5cbc777ccd-89dfq condition met pod/tekton-pipelines-controller-65f567589b-pdvhh condition met pod/tekton-pipelines-webhook-75cd84877-tkf6j condition metInstall the Tekton CLI.
$ brew install tektoncd-cliRefer to the Tekton CLI installation guide for alternative installation methods.
Verify the Tekton CLI installation.
$ tkn version Client version: 0.44.0 Pipeline version: v1.6.0The output displays the Tekton CLI version.
Create an HCP service principal with CLI access
(Persona: Operations)
Before configuring the Tekton pipeline, create and store your HCP credentials as a Kubernetes secret. The HCP Vault Radar CLI requires these credentials to authenticate with HCP Vault Radar and perform scans.
Create a service-scoped service principal with the Vault Radar CLI User role and generate a service principal key. The role allows you to follow the principle of least privilege while granting the necessary permissions.
Plan to rotate the service principal key periodically to manage the key's full lifecycle by revoking keys older than your rotation period and generating new keys.
Create the service principal
Sign in to the HCP portal as a user with admin privileges.
Select your organization and project.
In the left sidebar, click Access control (IAM).
Click Service principals.
Click Create service principal.
Enter
vault-radar-cicdin the Service principal name field.Click the Select service dropdown and select Vault Radar.
Click the Select role dropdown and select Vault Radar CLI User.
Click Create service principal.
Generate a service principal key
On the service principal detail page, click Keys.
Click Generate key.
Copy the Client ID.
Open a terminal and export the client ID as an environment variable.
$ export HCP_CLIENT_ID=Return to the HCP portal, copy the Client secret.
Return to the same terminal you exported the
HCP_CLIENT_IDand export the secret as an environment variable.$ export HCP_CLIENT_SECRET=Click Back to service principals.
Click Back to dashboard.
Click View project settings.
Copy the Project ID.
Return to the same terminal you exported the
HCP_CLIENT_IDandHCP_CLIENT_SECRETand export the project ID as an environment variable.$ export HCP_PROJECT_ID=Create a Kubernetes secret containing your HCP credentials.
$ kubectl create secret generic vault-radar-credentials \ --from-literal=HCP_PROJECT_ID="$HCP_PROJECT_ID" \ --from-literal=HCP_CLIENT_ID="$HCP_CLIENT_ID" \ --from-literal=HCP_CLIENT_SECRET="$HCP_CLIENT_SECRET"Example output:
secret/vault-radar-credentials createdVerify you can access the secret.
$ kubectl get secret vault-radar-credentials NAME TYPE DATA AGE vault-radar-credentials Opaque 3 32s
Create the Vault Radar Tekton task
(Persona: Developer/Operations)
Create a Tekton task that runs the HCP Vault Radar CLI to scan a repository. This task clones the repository, runs the scan, and produces SARIF output.
Create a file named
vault-radar-scan-task.yaml.$ touch vault-radar-scan-task.yamlAdd the task definition with the following content.
apiVersion: tekton.dev/v1 kind: Task metadata: name: vault-radar-scan spec: workspaces: - name: source params: - name: repo-url description: URL of the repository to scan type: string - name: branch description: Branch to scan default: main type: string - name: format description: Output format (sarif or json) default: sarif type: string - name: exit-code-mode description: Exit code mode (always-success, on-new-findings) default: always-success type: string - name: baseline description: Path to baseline file for incremental scanning default: "" type: string - name: output-file description: Path to output file default: $(workspaces.source.path)/scan-results.sarif type: string steps: - name: clone image: alpine/git:v2.43.0 securityContext: runAsNonRoot: true runAsUser: 1000 env: - name: HOME value: $(workspaces.source.path) script: | #!/bin/sh set -e cd $(workspaces.source.path) rm -rf repo git clone --branch $(params.branch) $(params.repo-url) repo workingDir: $(workspaces.source.path) - name: scan image: hashicorp/vault-radar:latest securityContext: runAsUser: 0 env: - name: HOME value: $(workspaces.source.path) - name: HCP_PROJECT_ID valueFrom: secretKeyRef: name: vault-radar-credentials key: HCP_PROJECT_ID - name: HCP_CLIENT_ID valueFrom: secretKeyRef: name: vault-radar-credentials key: HCP_CLIENT_ID - name: HCP_CLIENT_SECRET valueFrom: secretKeyRef: name: vault-radar-credentials key: HCP_CLIENT_SECRET script: | #!/bin/sh set -e echo "Installing git and jq..." apk add --no-cache git jq 2>/dev/null || (apt-get update && apt-get install -y git jq) # Configure git to trust the cloned directory ownership git config --global --add safe.directory $(workspaces.source.path)/repo git config --global --add safe.directory $(workspaces.source.path)/repo/.git echo "Starting Vault Radar scan..." echo "Repository: $(params.repo-url)" echo "Branch: $(params.branch)" cd $(workspaces.source.path)/repo # Build the scan command CMD="vault-radar scan repo \ --clone-dir . \ --format $(params.format) \ --outfile $(params.output-file) \ --disable-ui" # Add baseline if provided if [ -n "$(params.baseline)" ]; then CMD="$CMD --baseline $(params.baseline)" fi echo "Running: $CMD" # Run the scan eval $CMD echo "Scan complete. Results saved to $(params.output-file)" # Print the results so they are easily visible in the CI/CD pipeline logs echo "=== SCAN RESULTS ===" cat $(params.output-file) | jq . || cat $(params.output-file) echo "====================" # Enforce pipeline blocking if secrets are found if [ "$(params.format)" = "sarif" ]; then # Count findings in the SARIF JSON FINDINGS=$(cat $(params.output-file) | jq '.runs[0].results | length') if [ "$FINDINGS" != "0" ] && [ "$FINDINGS" != "null" ]; then if [ "$(params.exit-code-mode)" = "on-new-findings" ]; then echo "🚨 BLOCKING PIPELINE: Vault Radar found $FINDINGS secret(s)!" exit 1 else echo "⚠️ ALERTING PIPELINE: Vault Radar found $FINDINGS secret(s). Proceeding without failure." fi else echo "✅ No secrets found. Pipeline successful." fi fi workingDir: $(workspaces.source.path)- A source workspace for cloned repository content
- Parameters for repo-url, branch, format, exit-code-mode, baseline, and output-file
- A clone step that uses an Alpine git image
- A scan step that runs Vault Radar with HCP credentials from the Kubernetes secret
- Security contexts for both steps
Apply the task to your Kubernetes cluster.
$ kubectl apply -f vault-radar-scan-task.yaml task.tekton.dev/vault-radar-scan createdThe Tekton task is now available in the cluster.
Configure alerting mode pipeline
(Persona: Developer/Operations)
Alerting mode generates scan reports without blocking the pipeline. This mode is useful when you want to identify secrets without preventing deployments, giving security teams time to remediate findings.
Create a file named
vault-radar-alert-pipeline.yaml.$ touch vault-radar-alert-pipeline.yamlAdd the pipeline definition with for the alerting pipeline.
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: vault-radar-alert-pipeline spec: workspaces: - name: source params: - name: repo-url description: URL of the repository to scan type: string - name: branch description: Branch to scan default: main type: string - name: repo-name description: Name of the repository for output naming default: "" type: string tasks: - name: scan-for-secrets taskRef: name: vault-radar-scan workspaces: - name: source workspace: source params: - name: repo-url value: "$(params.repo-url)" - name: branch value: "$(params.branch)" - name: format value: sarif - name: exit-code-mode value: always-success - name: output-file value: $(workspaces.source.path)/$(params.repo-name)-findings.sarifThis pipeline runs the Vault Radar scan in always-success mode, meaning the pipeline completes even if Vault Radar finds secrets. The SARIF output can then be uploaded to security dashboards or GitHub Advanced Security for visualization.
Apply the alerting pipeline.
$ kubectl apply -f vault-radar-alert-pipeline.yaml pipeline.tekton.dev/vault-radar-alert-pipeline created
Configure blocking mode pipeline
(Persona: Developer/Operations)
Blocking mode fails the pipeline when Vault Radar detects new secrets. This enforces a security gate that prevents deployments when HCP Vault Radar discovers sensitive data.
Create a file named
vault-radar-block-pipeline.yaml.$ touch vault-radar-block-pipeline.yamlAdd the pipeline definition for the blocking pipeline.
apiVersion: tekton.dev/v1 kind: Pipeline metadata: name: vault-radar-block-pipeline spec: workspaces: - name: source params: - name: repo-url description: URL of the repository to scan type: string - name: branch description: Branch to scan default: main type: string - name: repo-name description: Name of the repository for output naming default: "" type: string - name: baseline-file description: Path to baseline file for comparison default: "" type: string tasks: - name: scan-for-secrets taskRef: name: vault-radar-scan workspaces: - name: source workspace: source params: - name: repo-url value: "$(params.repo-url)" - name: branch value: "$(params.branch)" - name: format value: sarif - name: exit-code-mode value: on-new-findings - name: baseline value: "$(params.baseline-file)" - name: output-file value: $(workspaces.source.path)/$(params.repo-name)-findings.sarifThe key difference in blocking mode is the exit-code-mode parameter set to on-new-findings. When Vault Radar detects new secrets that were not present in a baseline, it exits with a non-zero code, causing the Tekton task to fail.
Apply the blocking pipeline.
$ kubectl apply -f vault-radar-block-pipeline.yaml pipeline.tekton.dev/vault-radar-block-pipeline created
Run the pipeline
(Persona: Developer)
Now you can run a pipeline to scan a repository for secrets.
Start the alerting pipeline to scan a repository.
$ tkn pipeline start vault-radar-alert-pipeline \ --param repo-url=https://github.com/hashicorp-education/hcp-vault-radar-foundations \ --param branch=main \ --param repo-name=hcp-vault-radar-foundations \ --workspace name=source,claimName=pvc-for-scanning \ --showlogThe pipeline runs and displays the scan results in the logs. The output shows secrets detected during the scan. In alerting mode, the pipeline completes regardless of findings.
Example output:
{ "version": "2.1.0", "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "runs": [ { "tool": { "driver": { "name": "HashiCorp Vault Radar", "rules": [ { "id": "password_assignment", "name": "Password assignment", "shortDescription": { "text": "Detected risk of type password_assignment and category secret" } }, { "id": "secret_assignment", "name": "Secret assignment", "shortDescription": { "text": "Detected risk of type secret_assignment and category secret" } } ] } }, "results": [ { "properties": { "activeness": "unknown", "author_email": "92055993+billpreston@users.noreply.github.com", "category": "secret", "git/author_email": "92055993+billpreston@users.noreply.github.com", "git/author_name": "William Preston", "git/author_time": "2024-02-13T17:09:05-05:00", "git/commit_message": "Update main.go", "git/commit_sha": "dd8d31bc0e33e837e8a6b37c90b59ab7b1137f2e", "git/committer_email": "noreply@github.com", "git/committer_name": "GitHub", "git/committer_time": "2024-02-13T17:09:05-05:00", "git/git_reference": "refs/heads/main", "git/org_name": "hashicorp-education", "git/repo_name": "hcp-vault-radar-foundations", "git/scan_type": "full-scan", "is_historic": false, "is_metadata_risk": false, "rel_path": "main.go", "tags": null, "textual_context": "const password = \"***\"", "value_hash": "9l6AeNBEB9oFWkEqY4oqkcvwXLOw1X7CL91/iOnK/no" }, "ruleId": "password_assignment", "ruleIndex": 0, "level": "warning", "message": { "text": "Password assignment" }, "locations": [ { "physicalLocation": { "artifactLocation": { "uri": "git://github.com/hashicorp-education/hcp-vault-radar-foundations.git/dd8d31bc0e33e837e8a6b37c90b59ab7b1137f2e/main.go#L12" ...snip...Review the scan results from the pipeline run.
$ tkn pipelinerun logs -LThe
-Lflag automatically grabs the logs from the last pipeline run.Understanding the output:
The pipeline logs output the results in SARIF (Static Analysis Results Interchange Format), an industry-standard JSON format.
Review the
resultsarray in the JSON payload:ruleId: Identifies the type of secret found (e.g.,password_assignment).is_historic: Vault Radar scans the entire git history. Iffalse, the secret is in the current code. Iftrue, it was found in a previous commit.locations: Provides the exact file (main.go) and line number (startLine: 12).
Notice the end of the log states
⚠️ ALERTING PIPELINE: Vault Radar found 4 secret(s). Proceeding without failure.. The task natively allows the pipeline to succeed so your security team can collect reports without actively blocking CI/CD deployments.Run the blocking pipeline to test the failure behavior.
$ tkn pipeline start vault-radar-block-pipeline \ --param repo-url=https://github.com/hashicorp-education/hcp-vault-radar-foundations \ --param branch=main \ --param repo-name=hcp-vault-radar-foundations \ --workspace name=source,claimName=pvc-for-scanning \ --showlogExample output:
... [scan-for-secrets : scan] 🚨 BLOCKING PIPELINE: Vault Radar found 4 secret(s)! failed to step run: exit status 1The blocking pipeline evaluates the SARIF output. Because HCP Vault Radar found secrets and you set the
exit-code-modeparameter toon-new-findings, the script explicitly forces anexit 1. This stops the deployment pipeline, acting as a security gate that ensures no leaked secrets can reach your production environments.
Knowledge checks
A quiz to test your knowledge.
How do you configure the Tekton pipeline to complete successfully even if HCP Vault Radar detects secrets?
🔘 Set the
exit-code-modeparameter toalways-success🔘 Set the
formatparameter tojson🔘 Set the
exit-code-modeparameter toon-new-findings🔘 Delete the
baselineparameter✅ Set the
exit-code-modeparameter toalways-success❌ Set the
formatparameter tojson❌ Set the
exit-code-modeparameter toon-new-findings❌ Delete the
baselineparameterSetting
exit-code-modetoalways-successensures the pipeline does not fail, allowing security teams to collect scan reports without blocking deployments. Setting it toon-new-findingscreates a blocking pipeline.What does the
is_historicproperty indicate in the SARIF JSON output from an HCP Vault Radar scan?🔘 The secret was found in a previous commit rather than the current code.
🔘 The secret has been automatically rotated and is no longer valid.
🔘 The secret was detected by a different security tool.
🔘 The secret is a false positive that was previously ignored.
✅ The secret was found in a previous commit rather than the current code.
❌ The secret has been automatically rotated and is no longer valid.
❌ The secret was detected by a different security tool.
❌ The secret is a false positive that was previously ignored.
Vault Radar scans the entire git history. If
is_historicisfalse, the secret is in the current code. Iftrue, it was found in a previous commit.How does the HCP Vault Radar CLI authenticate during the Tekton pipeline task?
The CLI authenticates using an HCP service principal. The project ID, client ID, and client secret are stored in a Kubernetes secret and passed to the scanning container as environment variables.
Summary
In this tutorial, you configured HCP Vault Radar scanning in a CI/CD pipeline. You learned how to:
- Use HCP Vault Radar CLI with HCP credentials in a CI/CD pipeline
- Configure CI/CD pipelines to succeed or fail based on HCP Vault Radar scan results
Next steps
To continue building your secret scanning workflow, explore these related resources:
- CI/CD secrets detection - WAF guidance on detecting secrets in CI/CD pipelines
- Detect leaked secrets - WAF guidance on identifying leaked credentials
- HCP Vault Radar CLI reference - Complete documentation for all CLI commands and options
- HCP Vault Radar documentation - Overview of Vault Radar features and capabilities