Vault
Secure pipelines with Vault and dynamic credentials
Challenge
Danielle is the lead developer at the fictional company HashiCups, and they face a common issue with some legacy CI/CD pipelines: hardcoded secrets in a pipeline configuration.
Hardcoded secrets in CI/CD pipelines are insecure. Source control systems, logs, build artifacts, and misconfigured runners can all expose hardcoded secrets, making them a target for attackers or insiders to recover and reuse.
The direct results of compromised credentials can include unauthorized access to infrastructure, secret leakage, supply chain compromise, data loss, and lateral movement into production systems.
Solution
Danielle and the team can use Vault to authenticate and identify pipelines.
When the team uses Vault in this way, they also enable pipelines to use centrally managed, short-lived, and dynamic credentials with least privilege access through access control list policies.
Now the team can mitigate the issues that hardcoded pipeline secrets introduce while also enhancing auditing and reducing the chances of accidental credential leakage.
Scenario
Your role in this tutorial scenario is to act as the developer persona, collaborate with the operations persona, and complete the following practical lab workflow for updating a Jenkins pipeline to use Vault.
You progress through this scenario as follows:
- Explore an insecure Jenkins pipeline for the HashiCups cup printing service that uses hardcoded PostgreSQL credentials.
- Work with Oliver in operations to enable Vault and mitigate the risks using:
- AppRole auth method for Jenkins machine authentication.
- PostgreSQL database secrets engine for dynamic database credentials.
- Run the updated pipeline and verify it authenticates to PostgreSQL with Vault-issued credentials.
- Review benefits, next steps, and related resources.
You can use a combination of terminal and shell commands along with the Jenkins web user interface to follow the examples in this scenario.
Prerequisites
Confirm that you have the following available and installed before you start.
- Vault installed for CLI commands executed on the host computer.
- Docker Engine and Docker Compose.
- Permission to run Docker commands on your host.
- A terminal session where you can run the example commands.
curlinstalled on your host for API requests.jqinstalled on your host for API responses.- A browser to access the Jenkins web UI at http://localhost:8080.
Versions used for this tutorial
This tutorial was last tested 01 May 2026 on macOS 26.3.1 using the following software versions.
$ docker --version
Docker version 29.3.1, build c2be9cc
$ docker compose version
Docker Compose version 5.1.1
$ docker run --rm hashicorp/vault:1.21.0 vault version
Vault v1.21.0 (818ca8b3575ea937ca48b640baf35e1b2ede1833), built 2025-10-21T19:33:18Z
$ docker run --rm jenkins/jenkins:2.553 --version
2.553
$ docker run --rm postgres:16 postgres --version
postgres (PostgreSQL) 16.13 (Debian 16.13-1.pgdg13+1)
$ jq --version
jq-1.8.1
$ curl --version | head -n 1
curl 8.7.1 (x86_64-apple-darwin25.0) libcurl/8.7.1 (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.68.0
Lab environment architecture
In the insecure deployment, the PostgreSQL database credentials get hardcoded
directly in the Jenkinfile.
The example containerized environment includes the following services:
- Vault (
dev-vault) for secrets management and auth. - Jenkins (
dev-jenkins) for pipeline execution. - PostgreSQL (
dev-postgres) with samplecup_printer_jobsdata. - Workstation (
dev-workstation) for CLI and validation steps.
Lab setup
In a production deployment, an operator like Oliver has already helped to build the infrastructure that Danielle uses for development. That infrastructure does not yet exist here.
For this lab, you can deploy a series of Docker containers to emulate parts of the HashiCups cup printing service deployment.
You must perform some setup tasks to establish the lab environment on your computer. Use a terminal session to copy and paste the example commands, and prepare your lab environment.
Clone the lab repository.
$ git clone \ https://github.com/hashicorp-education/learn-vault-pipeline-secrets.gitChange into the
learn-vault-pipeline-secretsproject directory.$ cd learn-vault-pipeline-secretsCreate a unique environment file with example users and credentials.
$ cat > .env << EOF JENKINS_ADMIN_ID=admin JENKINS_ADMIN_PASSWORD="$(openssl rand -base64 16)" # Additional Jenkins users JENKINS_USER_DANIELLE_PASSWORD="$(openssl rand -base64 16)" JENKINS_USER_OLIVER_PASSWORD="$(openssl rand -base64 16)" # PostgreSQL (lab only) POSTGRES_DB=cup_printer POSTGRES_USER=vaultadmin POSTGRES_PASSWORD=vaultadminpass EOFDeploy the infrastructure.
$ docker compose up -d --buildExample output:
[+] up 14/14 ✔ Image dev-vault:latest Built 0.4s ✔ Image dev-jenkins:latest Built 0.4s ✔ Image dev-workstation:latest Built 0.4s ✔ Network work_dev-lab Created 0.0s ✔ Volume work_dev-jenkins-home Created 0.0s ✔ Volume work_dev-postgres-data Created 0.0s ✔ Volume work_dev-tls Created 0.0s ✔ Volume work_dev-vault-data Created 0.0s ✔ Volume work_dev-vault-init Created 0.0s ✔ Container dev-tls-setup Exited 7.9s ✔ Container dev-postgres Healthy 7.8s ✔ Container dev-vault Healthy 12.8s ✔ Container dev-jenkins Started 12.9s ✔ Container dev-workstation Started 12.9s
Validate your infrastructure
Validate that the deployment is healthy before exploring the pipeline.
Check the container status.
$ docker compose ps --format "table {{.Name}}\t{{.Status}}"Example output:
NAME STATUS dev-jenkins Up 49 seconds dev-postgres Up About a minute (healthy) dev-vault Up 54 seconds (healthy) dev-workstation Up 49 secondsAccess Danielle's workstation.
$ docker exec -it dev-workstation bashNow that you are using Danielle's workstation, check the Vault server status to ensure that the server is started and ready.
$ vault statusExample output:
Key Value --- ----- Seal Type shamir Initialized true Sealed false Total Shares 1 Threshold 1 Version 1.21.0 Build Date 2025-10-21T19:33:18Z Storage Type file Cluster Name vault-cluster-c07a7938 Cluster ID c424f559-84ec-7f72-e83e-ff886ae9f1a0 HA Enabled false
Explore the insecure pipeline configuration.
Print Danielle's Jenkins user password from the
.envfile.$ cat .env | awk -F'"' '/^JENKINS_USER_DANIELLE_PASSWORD/ {print $2}'Example output:
C0ff33................Copy the password value to your clipboard.
Open the Jenkins web ui at http://localhost:8080.
Login with the username
danielleand paste the password value that you printed from the.envfile.Click cup-print-service
Click Configure.
Click Pipeline.
Within the Script text area, scroll down to the line starting with
HARDCODED_DB_USERand note the inclusion of hardcoded credentials in the pipeline script.The insecure configuration mimics problematic secrets hygiene by intentionally hardcoding credentials directly in the pipeline code:
# Intentionally insecure for demonstration purposes HARDCODED_DB_USER='vaultadmin' HARDCODED_DB_PASSWORD='vaultadminpass'// Insecure pipeline: initial state before Vault integration. // Demonstrates hardcoded database credentials in pipeline code. pipeline { agent any environment { PGHOST = 'postgres' PGPORT = '5432' PGDATABASE = 'cup_printer' PGSSLROOTCERT = '/tls/ca/ca.crt' PGSSLCERT = '/tls/clients/jenkins.crt' PGSSLKEY = '/tls/clients/jenkins.key' PGSSLMODE = 'verify-full' } stages { stage('Before: hardcoded DB credentials (insecure)') { steps { sh ''' set -euo pipefail # Intentionally insecure for demonstration purposes HARDCODED_DB_USER='vaultadmin' HARDCODED_DB_PASSWORD='vaultadminpass' echo "Running insecure query with hardcoded credentials from pipeline code..." PGPASSWORD="$HARDCODED_DB_PASSWORD" psql \ -h "$PGHOST" \ -p "$PGPORT" \ -U "$HARDCODED_DB_USER" \ -d "$PGDATABASE" \ -c "SELECT id, cup_model, color, status FROM cup_printer_jobs ORDER BY id;" ''' } } } post { always { cleanWs() } } }
Why is this insecure?
Hardcoded pipeline credentials are insecure for several reasons:
- Credentials are in source-controlled pipeline code.
- Rotating credentials requires code changes and redeploys.
- If exposed, credentials often have long term and larger blast radius.
Run the cup-print-service pipeline once to observe that this configuration
works, but with poor secret hygiene.
Click cup-print-service.
Click Build Now.
Observe the build run #1 under Builds. It should complete with a green checkmark.
Click build #1.
Click Console Output.
In Jenkins build logs, verify the insecure stage executed and queried PostgreSQL.
Expected log fragments to demonstrate SQL query with hardcoded credentials:
+ HARDCODED_DB_USER=vaultadmin + HARDCODED_DB_PASSWORD=vaultadminpass + echo Running insecure query with hardcoded credentials from pipeline code... Running insecure query with hardcoded credentials from pipeline code... + PGPASSWORD=vaultadminpass psql -h postgres -p 5432 -U vaultadmin -d cup_printer -c SELECT id, cup_model, color, status FROM cup_printer_jobs ORDER BY id; id | cup_model | color | status ----+---------------+-------+--------- 1 | espresso-mini | black | printed 2 | latte-pro | white | printed 3 | travel-max | blue | queued (3 rows)Started by user danielle [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/cup-print-service [Pipeline] { [Pipeline] withEnv [Pipeline] { [Pipeline] stage [Pipeline] { (Before: hardcoded DB credentials (insecure)) [Pipeline] sh + set -euo pipefail + HARDCODED_DB_USER=vaultadmin + HARDCODED_DB_PASSWORD=vaultadminpass + echo Running insecure query with hardcoded credentials from pipeline code... Running insecure query with hardcoded credentials from pipeline code... + PGPASSWORD=vaultadminpass psql -h postgres -p 5432 -U vaultadmin -d cup_printer -c SELECT id, cup_model, color, status FROM cup_printer_jobs ORDER BY id; id | cup_model | color | status ----+---------------+-------+--------- 1 | espresso-mini | black | printed 2 | latte-pro | white | printed 3 | travel-max | blue | queued (3 rows) [Pipeline] } [Pipeline] // stage [Pipeline] stage [Pipeline] { (Declarative: Post Actions) [Pipeline] cleanWs [WS-CLEANUP] Deleting project workspace... [WS-CLEANUP] Deferred wipeout is used... [WS-CLEANUP] done [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // withEnv [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS
You can observe where hardcoded credentials appear in jenkins/Jenkinsfile,
and the pipeline using the insecure static credentials.
Secure pipeline secrets with Vault
At this point, Danielle raises the hardcoded credentials issue with Oliver, who owns the Vault infrastructure. Oliver enables the database secrets engine and AppRole auth method so Jenkins can authenticate to Vault and request short-lived PostgreSQL credentials.
Oliver configures Vault and Jenkins to enable the following workflow for the cup printing service.
The pipeline will now perform these essential workflow steps to use Vault:
Jenkins retrieves the AppRole auth method values from its credentials store:
- vault-role-id is an identifier that selects the AppRole against which the other credentials are evaluated.
- vault-secret-id is a credential that is required by default for any login (via secret_id) and is intended to always be secret.
Jenkins authenticates to Vault through the Vault API with the AppRole auth method using a POST to the Vault server API /v1/auth/approle/login endpoint. The AppRole login flow is machine-friendly and avoids embedding Vault tokens directly in pipeline code.
Oliver configures Vault database secrets engine with the following paths:
database/roles/cup-printer-readonlyThe Jenkins pipeline then requests credentials with a GET operation on the Vault
/v1/database/creds/cup-printer-readonlyAPI endpoint.Vault returns a generated PostgreSQL username/password pair with an attached lease that enforces timeboxed constraints through a TTL value.
The pipeline uses these credentials with the
psqlutility to access the PostgreSQL server.
You can run a script that makes these changes in the lab environment to simulate the work Oliver does in the scenario.
Return to Danielle's workstation in your terminal session.
Press
CTRL+dto exit from Danielle's workstation. You can do Oliver's work from the host computer with scripts anddockercommands.Run the script to apply the changes Oliver makes to enable Vault.
$ bash ./scripts/oliver-setup.shThe expected output shows completion of all steps by Oliver.
[INFO] Enable Vault database secrets engine and AppRole auth... ○ Enable database secrets engine... Success! Enabled the database secrets engine at: database/ ○ Wait for PostgreSQL... ○ Configure PostgreSQL connection... ○ Create cup-printer-readonly role... ✓ Database secrets engine configured (role: cup-printer-readonly) ○ Enable AppRole auth method... Success! Enabled approle auth method at: approle/ ○ Create Jenkins AppRole... ✓ AppRole configured. Credentials saved to /vault/init/{role_id,secret_id} ✓ Oliver setup complete [INFO] Oliver: I configured Vault for dynamic PostgreSQL credentials.
Check the updated configuration
Before you proceed, confirm that Vault is configured for dynamic database secrets.
Verify the database secrets engine configuration exists.
$ docker compose exec vault sh -lc 'vault read database/config/cup-printer-postgres'Expected output includes:
Key Value --- ----- allowed_roles [cup-printer-readonly] connection_details map[connection_url:postgresql://{{username}}:{{password}}@postgres:5432/cup_printer?sslmode=verify-full&sslrootcert=/tls/ca/ca.crt&sslcert=/tls/clients/vault.crt&sslkey=/tls/clients/vault.key password_authentication:scram-sha-256 username:vaultadmin] disable_automated_rotation false password_policy n/a plugin_name postgresql-database-plugin plugin_version n/a root_credentials_rotate_statements [] rotation_period 0s rotation_schedule n/a rotation_window 0 skip_static_role_import_rotation false verify_connection trueList the configured role for the database secrets engine.
$ docker compose exec vault sh -lc 'vault read database/roles/cup-printer-readonly'Expected output includes the user creation statements and dynamic credential time to live values:
Key Value --- ----- creation_statements [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT CONNECT ON DATABASE cup_printer TO "{{name}}"; GRANT USAGE ON SCHEMA public TO "{{name}}"; GRANT SELECT ON TABLE cup_printer_jobs TO "{{name}}";] credential_type password db_name cup-printer-postgres default_ttl 1h max_ttl 24h renew_statements [] revocation_statements [] rollback_statements []Verify the AppRole auth method configuration.
$ docker compose exec vault sh -lc 'vault read auth/approle/role/jenkins'Expected output includes
token_policies: [jenkins]:Key Value --- ----- bind_secret_id true local_secret_ids false secret_id_bound_cidrs <nil> secret_id_num_uses 0 secret_id_ttl 24h token_bound_cidrs [] token_explicit_max_ttl 0s token_max_ttl 4h token_no_default_policy false token_num_uses 0 token_period 0s token_policies [jenkins] token_ttl 1h token_type batch
Update the Jenkins pipeline
With Vault configured and the secure pattern understood, Danielle updates the Jenkins pipeline to replace hardcoded credentials with Vault-backed stages.
This update script saves you some time by completing these manual steps:
Reads the AppRole role ID and secret ID from the Vault init volume.
Injects the role ID and secret ID into Jenkins as the
vault-role-idandvault-secret-idcredentials.
Run the script to enable the changes.
$ bash ./scripts/update-pipeline.sh
Expected output:
○ Checking Oliver setup is complete...
✓ Oliver setup confirmed
○ Reading AppRole credentials from Vault...
✓ AppRole credentials read
○ Copying Jenkinsfile.secure into Jenkins container...
Successfully copied 3.43kB (transferred 5.12kB) to dev-jenkins:/var/jenkins_home/Jenkinsfile.secure
✓ Jenkinsfile.secure copied
○ Injecting AppRole credentials into Jenkins...
✓ vault-role-id and vault-secret-id credentials stored in Jenkins
○ Updating cup-print-service pipeline definition...
✓ Pipeline updated to use Vault dynamic credentials
✓ Jenkins is ready for the updated secure pipeline configuration script.
Open Jenkins and update the pipeline for the cup-print-service with
Contents of the secured file from jenkins/Jenkinsfile.secure
Update Jenkins with the secured configuration
Here is the updated Jenkinsfile with hardcoded credentials removed, and
replaced by Vault managed credentials. Click the clipboard icon to copy the
configuration to your system clipboard.
// Secure pipeline: Vault integrated, no more hardcoded secrets.
pipeline {
agent any
environment {
VAULT_ADDR = 'https://vault:8200'
VAULT_CACERT = '/tls/ca/ca.crt'
VAULT_CLIENT_CERT = '/tls/clients/jenkins.crt'
VAULT_CLIENT_KEY = '/tls/clients/jenkins.key'
PGHOST = 'postgres'
PGPORT = '5432'
PGDATABASE = 'cup_printer'
PGSSLROOTCERT = '/tls/ca/ca.crt'
PGSSLCERT = '/tls/clients/jenkins.crt'
PGSSLKEY = '/tls/clients/jenkins.key'
PGSSLMODE = 'verify-full'
}
stages {
stage('After: dynamic DB credentials from Vault database engine') {
steps {
withCredentials([
string(credentialsId: 'vault-role-id', variable: 'VAULT_ROLE_ID'),
string(credentialsId: 'vault-secret-id', variable: 'VAULT_SECRET_ID')
]) {
sh '''
set -euo pipefail
# Disable xtrace before capturing secrets to prevent token, credential
# JSON, and PGPASSWORD values from appearing in the console log.
{ set +x; } 2>/dev/null
token=$(curl --cacert "${VAULT_CACERT}" --cert "${VAULT_CLIENT_CERT}" --key "${VAULT_CLIENT_KEY}" -sS --request POST --data '{"role_id":"'"${VAULT_ROLE_ID}"'","secret_id":"'"${VAULT_SECRET_ID}"'"}' \
${VAULT_ADDR}/v1/auth/approle/login | jq -r .auth.client_token)
db_creds=$(curl --cacert "${VAULT_CACERT}" --cert "${VAULT_CLIENT_CERT}" --key "${VAULT_CLIENT_KEY}" -sS --header "X-Vault-Token: ${token}" \
${VAULT_ADDR}/v1/database/creds/cup-printer-readonly)
db_user=$(echo "$db_creds" | jq -r .data.username)
db_pass=$(echo "$db_creds" | jq -r .data.password)
echo "Running secure query with Vault dynamic credentials..."
PGPASSWORD="$db_pass" psql \
-h "$PGHOST" \
-p "$PGPORT" \
-U "$db_user" \
-d "$PGDATABASE" \
-c "SELECT id, cup_model, color, status FROM cup_printer_jobs ORDER BY id;"
'''
}
}
}
}
post {
always {
cleanWs()
}
}
}
The highlighted lines show where Jenkins secure credentials and Vault dynamic credentials replace the hardcoded crendentials in the previous insecure configuration.
Update the pipeline configuration in the Jenkins web UI.
Access the Jenkins browser tab.
Click cup-print-service.
Click Configure.
Click Pipeline.
Scroll down to Script.
Click in the text area and use
CMD+Aon Mac orCTRL+Aon Linux or Windows to select all script content.Press
deleteto remove all script content.Within the Script text area, press
CMD+VorCTRL+Vto paste the updated script content into the text area.Click Save.
Validate the secure configuration
Now you can trigger the pipeline to observe the differences in the secured pipeline.
Click Build Now.
Click build #2.
Click Console Output.
Started by user danielle
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/cup-print-service
[Pipeline] {
[Pipeline] withEnv
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Read static secret from Vault KV v2)
[Pipeline] withCredentials
Masking supported pattern matches of $VAULT_ROLE_ID or $VAULT_SECRET_ID
[Pipeline] {
[Pipeline] sh
+ set -euo pipefail
Retrieved static KV secret value length: 17
[Pipeline] }
[Pipeline] // withCredentials
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (After: dynamic DB credentials from Vault database engine)
[Pipeline] withCredentials
Masking supported pattern matches of $VAULT_ROLE_ID or $VAULT_SECRET_ID
[Pipeline] {
[Pipeline] sh
+ set -euo pipefail
Running secure query with Vault dynamic credentials...
id | cup_model | color | status
----+---------------+-------+---------
1 | espresso-mini | black | printed
2 | latte-pro | white | printed
3 | travel-max | blue | queued
(3 rows)
[Pipeline] }
[Pipeline] // withCredentials
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Declarative: Post Actions)
[Pipeline] cleanWs
[WS-CLEANUP] Deleting project workspace...
[WS-CLEANUP] Deferred wipeout is used...
[WS-CLEANUP] done
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // withEnv
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
The output shows the pipeline use AppRole to authenticate with Vault, retrieve dynamic, short-lived PostgreSQL credentials, then use them to query the print service database.
There are no hardcoded secrets in the pipeline and it produces clean output.
What changed in the pipeline?
Before
The original pipeline stored long-lived credentials directly in source code. This approach creates compounding risk: any developer with repository access could read the database password, rotation requires coordinating a code change and redeployment, and a leaked Jenkinsfile immediately exposes production credentials with no time limit on their validity.
- Static DB credentials hardcoded in the
Jenkinsfile. - No credential TTL/lease controls.
- Rotation is manual and disruptive.
After
The Vault-based pipeline eliminates standing credentials entirely. Jenkins proves its identity through AppRole, receives scoped credentials that expire automatically, and never writes a secret to disk or source control. This shifts the security boundary from "protect the repo" to "protect Vault," where access is centrally governed, logged, and revocable without a deployment.
- Jenkins authenticates to Vault with AppRole.
- Pipeline requests short-lived database credentials on demand.
- Credentials are not stored in pipeline code.
- Access is policy-scoped and auditable in Vault.
Benefits of the Vault-based approach
The Vault-based approach provides the following benefits:
- Reduced secret sprawl: no DB passwords in pipeline source.
- Least privilege: read-only role for pipeline DB access.
- Short-lived credentials: lowers risk from leaked creds.
- Operational agility: easier rotation and revocation.
- Auditability: Vault centralizes secret issuance and auth events.
Troubleshooting
Jenkins job fails before running stages
Check containers:
$ docker compose ps
Check logs:
docker compose logs vault
docker compose logs jenkins
If Jenkins is not reachable at http://localhost:8080, wait for startup completion and retry.
AppRole login returns 403 (permission denied)
Typical signature:
Code: 403. Errors: * permission denied
Remediation:
Confirm
vault-role-idandvault-secret-idin Jenkins credentials are correct.Confirm the AppRole exists and is mapped to the intended policy.
Re-run the
oliver-setup.shscript if AppRole artifacts were not created.bash ./scripts/oliver-setup.sh
Permission denied on database creds path
Typical signature:
permission denied on path "database/creds/cup-printer-readonly"
Remediation:
Confirm the AppRole policy allows
readondatabase/creds/cup-printer-readonly.Confirm the role exists:
$ docker compose exec vault sh -lc 'export VAULT_TOKEN=$(jq -r .root_token /vault/init/.vault.init); vault read database/roles/cup-printer-readonly'Re-run the
oliver-setup.shscript if the database engine role was not configured.bash ./scripts/oliver-setup.sh
PostgreSQL connection refused or auth/query fails
Typical signatures:
connection refused
FATAL: password authentication failed
Remediation:
Confirm
dev-postgresis healthy.Confirm sample table exists:
$ docker compose exec postgres psql -U vaultadmin -d cup_printer -c "SELECT * FROM cup_printer_jobs;"Retry after Vault issues fresh dynamic credentials.
Cleanup
Run the cleanup script to stop all containers and remove all lab state:
- Vault data
- PostgreSQL data
- Jenkins home
- TLS material
$ bash ./scripts/cleanup.sh
Example output:
────────────────────────────────────────────────────────
Step 1 — Stop containers and remove Compose volumes
────────────────────────────────────────────────────────
○ No running containers; ensuring volumes are removed...
✓ Done
────────────────────────────────────────────────────────
Step 2 — Remove orphaned project volumes
────────────────────────────────────────────────────────
✓ No orphaned volumes found
────────────────────────────────────────────────────────
Step 3 — Remove host-side TLS artifacts
────────────────────────────────────────────────────────
✓ No host CA cert found (already clean)
────────────────────────────────────────────────────────
Cleanup complete.
────────────────────────────────────────────────────────
Knowledge checks
A quiz to test your knowledge.
What does a hardcoded database credential in a
Jenkinsfileexpose the pipeline to?🔘 Slower pipeline execution due to repeated credential lookups.
🔘 Database connection failures caused by credential format errors.
🔘 Long-lived credentials that can be recovered from source control, logs, or build artifacts with no automatic expiration.
🔘 Increased network latency when connecting to the database.
❌ Slower pipeline execution due to repeated credential lookups.
❌ Database connection failures caused by credential format errors.
✅ Long-lived credentials that can be recovered from source control, logs, or build artifacts with no automatic expiration.
❌ Increased network latency when connecting to the database.
What controls the lifespan of PostgreSQL credentials that Vault's database secrets engine issues to the Jenkins pipeline?
🔘 The Jenkins pipeline sets the credential expiration in the
Jenkinsfileusing attlparameter.🔘 The PostgreSQL server enforces a fixed 8-hour session limit on all connections.
🔘 The
VAULT_TOKENenvironment variable determines how long the database credentials remain valid.🔘 The
default_ttlandmax_ttlvalues configured on thecup-printer-readonlydatabase role in Vault control credential expiration.❌ The Jenkins pipeline sets the credential expiration in the
Jenkinsfileusing attlparameter.❌ The PostgreSQL server enforces a fixed 8-hour session limit on all connections.
❌ The
VAULT_TOKENenvironment variable determines how long the database credentials remain valid.✅ The
default_ttlandmax_ttlvalues configured on thecup-printer-readonlydatabase role in Vault control credential expiration.Why does the secured pipeline run
{ set +x; } 2>/dev/nullbefore capturing the Vault token and database credentials?Disabling xtrace with
{ set +x; } 2>/dev/nullprevents Bash from echoing the commands that capture sensitive values to the Jenkins build log. Without this step, the Vault token andPGPASSWORDvalues would appear in plain text in the console output.
Summary
You implemented a realistic progression from insecure static credentials to a production-aligned pattern with Vault that demonstrated the following:
- Jenkins authenticating to Vault with AppRole.
- Vault issuing dynamic PostgreSQL credentials.
- Pipeline using ephemeral credentials to authenticate with database.
You implemented machine authentication with dynamic secrets, a practical pattern to harden CI/CD secret handling while improving lifecycle controls through a centralized management platform like Vault.
Next steps and resources
Beginner
- Gate the insecure stage behind a boolean parameter for training-only runs.
- Keep AppRole and database credential flow as the default secure path.
- Review the AppRole auth method workflow:
Intermediate
- Tighten Vault policies by environment and path segmentation.
- Add credential lease revocation checks after pipeline completion.
- Review dynamic database credential patterns:
Advanced
- Integrate an application codebase with its own credential requirements.
- Consider using JWT for machine authentication.
- Integrate Jenkins shared libraries for reusable Vault auth and credential retrieval logic.
- Review Jenkins Pipeline syntax and credential binding patterns: