Skip to content

docs: sync documentation with current 11-stage pipeline #30

docs: sync documentation with current 11-stage pipeline

docs: sync documentation with current 11-stage pipeline #30

Workflow file for this run

name: workshop-action
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
# ============================================================
# STAGE 1 - JWT Authentication & Basic Secret Retrieval
# Demonstrates: how Conjur authenticates GitHub Actions via
# JWT (no stored credentials) and retrieves a secret.
# ============================================================
stage-1-jwt-auth:
name: "Stage 1 - JWT Auth & Secret Retrieval"
runs-on: self-hosted
environment: dev
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve secret from Conjur via JWT
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: data/vault/dev-demo-aslan/asramos_pcloud_pov/username|APP_USERNAME
- name: Show that secret is available (masked in logs)
run: |
echo "Secret retrieved successfully!"
echo "Value (masked): $APP_USERNAME"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 2 - Multiple Secrets & Masking Demo
# Demonstrates: retrieving multiple secrets in one call and
# how GitHub Actions automatically masks values in the logs.
# ============================================================
stage-2-multiple-secrets:
name: "Stage 2 - Multiple Secrets & Masking"
runs-on: self-hosted
environment: dev
needs: stage-1-jwt-auth
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve multiple secrets from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/asramos_pcloud_pov/username|APP_USERNAME;data/vault/dev-demo-aslan/asramos_pcloud_pov/password|APP_PASSWORD"
- name: "Masking demo: each character spaced out to confirm value was set"
run: |
echo "--- Username (spaced) ---"
printenv APP_USERNAME | sed 's/./& /g'
echo "--- Password (spaced) ---"
printenv APP_PASSWORD | sed 's/./& /g'
- name: "Masking demo: trying to echo directly (will be masked)"
run: |
echo "Username: $APP_USERNAME"
echo "Password: $APP_PASSWORD"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 3 - Database Credentials from Conjur
# Demonstrates: retrieving database credentials stored in
# Privilege Cloud via Conjur and connecting to the database.
# ============================================================
stage-3-database:
name: "Stage 3 - Database Connection with Conjur Credentials"
runs-on: self-hosted
environment: dev
needs: stage-2-multiple-secrets
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve database credentials from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/dbuser_dual/username|DB_USERNAME;data/vault/dev-demo-aslan/dbuser_dual/password|DB_PASSWORD;data/vault/dev-demo-aslan/dbuser_dual/address|DB_ADDRESS"
- name: Show database host (address is not a secret, safe to print)
run: echo "Connecting to database at $DB_ADDRESS"
- name: Query database using Conjur credentials
run: |
MYSQL_PWD=$DB_PASSWORD mysql \
--host="$DB_ADDRESS" \
--user="$DB_USERNAME" \
--execute="SELECT current_user(), database(), now();"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 4 - Hardcoded vs Conjur (Before / After)
# Demonstrates: the risk of hardcoded credentials failing
# versus Conjur delivering the correct secret dynamically.
# ============================================================
stage-4-before-after:
name: "Stage 4 - Hardcoded vs Conjur"
runs-on: self-hosted
environment: dev
needs: stage-3-database
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: "BAD PRACTICE: hardcoded credential (connection will fail)"
run: |
echo ">>> Trying to connect with a hardcoded password..."
MYSQL_PWD="SuperSecret123!" mysql \
--host="${{ secrets.DB_ADDRESS_PLAIN }}" \
--user="app_user" \
--connect-timeout=5 \
--execute="SELECT 'connected!' AS status;" \
|| echo "Connection FAILED — hardcoded credentials are wrong or rotated!"
- name: Retrieve fresh credentials from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/dbuser_dual/username|DB_USERNAME;data/vault/dev-demo-aslan/dbuser_dual/password|DB_PASSWORD;data/vault/dev-demo-aslan/dbuser_dual/address|DB_ADDRESS"
- name: "GOOD PRACTICE: connect with Conjur credentials (always up-to-date)"
run: |
echo ">>> Connecting with credentials from Conjur..."
MYSQL_PWD=$DB_PASSWORD mysql \
--host="$DB_ADDRESS" \
--user="$DB_USERNAME" \
--execute="SELECT 'connected!' AS status;"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 5 - Least Privilege / Access Denied
# Demonstrates: the host only has access to what is explicitly
# permitted in Conjur policy — unauthorized access is denied.
# ============================================================
stage-5-least-privilege:
name: "Stage 5 - Least Privilege & Access Denied"
runs-on: self-hosted
environment: dev
needs: stage-4-before-after
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: "Retrieve authorized secret (should succeed)"
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: data/vault/dev-demo-aslan/asramos_pcloud_pov/username|APP_USERNAME
- name: "Authorized access confirmed"
run: echo "Authorized secret retrieved successfully."
- name: "Attempt to access an unauthorized secret (should fail)"
id: unauthorized
continue-on-error: true
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: data/vault/unauthorized-safe/secret|FORBIDDEN_SECRET
- name: "Confirm access was denied"
run: |
if [ "${{ steps.unauthorized.outcome }}" == "failure" ]; then
echo "Access DENIED by Conjur — least privilege working as expected!"
else
echo "WARNING: access was not denied. Review Conjur policy."
exit 1
fi
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 6 - Real Query & Data Retrieval
# Demonstrates: end-to-end flow — Conjur delivers credentials,
# application uses them to query real data from the database.
# ============================================================
stage-6-real-query:
name: "Stage 6 - Real Query & Data Retrieval"
runs-on: self-hosted
environment: dev
needs: stage-5-least-privilege
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve database credentials from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/dbuser_dual/username|DB_USERNAME;data/vault/dev-demo-aslan/dbuser_dual/password|DB_PASSWORD;data/vault/dev-demo-aslan/dbuser_dual/address|DB_ADDRESS"
- name: Run real query and display results
run: |
echo "=== Database Server Info ==="
MYSQL_PWD=$DB_PASSWORD mysql \
--host="$DB_ADDRESS" \
--user="$DB_USERNAME" \
--execute="SELECT @@version AS mysql_version, @@hostname AS host;"
echo ""
echo "=== Current Session Info ==="
MYSQL_PWD=$DB_PASSWORD mysql \
--host="$DB_ADDRESS" \
--user="$DB_USERNAME" \
--execute="SELECT current_user() AS logged_in_as, now() AS query_time;"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 7 - Credential Rotation (Zero Pipeline Changes)
# Demonstrates: Conjur always delivers the current password
# from Privilege Cloud. After a rotation, this pipeline runs
# unchanged and still connects — no secret stored in the repo.
# ============================================================
stage-7-rotation:
name: "Stage 7 - Credential Rotation Demo"
runs-on: self-hosted
environment: dev
needs: stage-6-real-query
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve credentials from Conjur (run 1)
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/dbuser_dual/username|DB_USERNAME;data/vault/dev-demo-aslan/dbuser_dual/password|DB_PASSWORD;data/vault/dev-demo-aslan/dbuser_dual/address|DB_ADDRESS"
- name: Connect to database (run 1 — current password)
run: |
echo "=== Run 1: connecting with current Conjur credentials ==="
MYSQL_PWD=$DB_PASSWORD mysql \
--host="$DB_ADDRESS" \
--user="$DB_USERNAME" \
--execute="SELECT 'Run 1 OK' AS result, now() AS timestamp;"
echo ""
echo "--- Rotate the password in Privilege Cloud now and re-run ---"
echo "--- This pipeline needs zero changes to keep working ---"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 8 - Environment Promotion (dev → staging → prod)
# Demonstrates: the same pipeline promotes through environments
# automatically. Each environment fetches its own secrets from
# Conjur. The prod job pauses for human approval.
#
# Pre-requisite: create GitHub Environments "staging" and
# "prod" under Settings → Environments. On "prod", add at
# least one Required Reviewer to enable the approval gate.
# ============================================================
stage-8-promote-dev:
name: "Stage 8a - Promote: dev"
runs-on: self-hosted
environment: dev
needs: stage-7-rotation
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve dev secrets from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/asramos_pcloud_pov/username|APP_USERNAME;data/vault/dev-demo-aslan/asramos_pcloud_pov/password|APP_PASSWORD"
- name: Simulate deployment to dev
run: |
echo "=== Deploying to DEV ==="
echo "Authenticated as: $APP_USERNAME"
echo "DEV deployment complete."
- name: Clean Workspace
uses: AutoModality/[email protected]
stage-8-promote-staging:
name: "Stage 8b - Promote: staging"
runs-on: self-hosted
environment: staging
needs: stage-8-promote-dev
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve staging secrets from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/asramos_pcloud_pov/username|APP_USERNAME;data/vault/dev-demo-aslan/asramos_pcloud_pov/password|APP_PASSWORD"
- name: Simulate deployment to staging
run: |
echo "=== Deploying to STAGING ==="
echo "Authenticated as: $APP_USERNAME"
echo "STAGING deployment complete."
- name: Clean Workspace
uses: AutoModality/[email protected]
stage-8-promote-prod:
name: "Stage 8c - Promote: prod (requires approval)"
runs-on: self-hosted
environment: prod
needs: stage-8-promote-staging
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve prod secrets from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/dbuser_dual/username|APP_USERNAME;data/vault/dev-demo-aslan/dbuser_dual/password|APP_PASSWORD"
- name: Simulate deployment to prod
run: |
echo "=== Deploying to PRODUCTION ==="
echo "Authenticated as: $APP_USERNAME"
echo "PRODUCTION deployment complete."
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 9 - SSH Deploy with Credentials from Conjur
# Demonstrates: server credentials (username, password,
# address) stored in Privilege Cloud, injected at runtime.
# The jumpserver account in PCloud holds these values.
#
# Conjur path: data/vault/dev-demo-aslan/jumpserver/
# ============================================================
stage-9-ssh-deploy:
name: "Stage 9 - SSH Deploy with Conjur Credentials"
runs-on: self-hosted
environment: dev
needs: stage-8-promote-prod
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve SSH credentials from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/dev-demo-aslan/jumpserver/username|SSH_USER_CONJUR;data/vault/dev-demo-aslan/jumpserver/password|SSH_PASSWORD;data/vault/dev-demo-aslan/jumpserver/address|SSH_HOST_CONJUR"
- name: Connect via SSH using credentials from Conjur
run: |
echo "Connecting to $SSH_HOST_CONJUR as $SSH_USER_CONJUR (password from Conjur)"
sshpass -p "$SSH_PASSWORD" ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=10 \
"$SSH_USER_CONJUR@$SSH_HOST_CONJUR" \
"echo 'Connected via credentials from Conjur — host: \$(hostname), date: \$(date)'"
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 10 - Docker Registry Login with Conjur Credentials
# Demonstrates: registry credentials managed in Privilege
# Cloud, including the registry address — no GitHub secrets
# needed. Uses dockerhub_aslan account from PCloud.
#
# Conjur path: data/vault/devsecops/dockerhub_aslan/
# ============================================================
stage-10-docker-registry:
name: "Stage 10 - Docker Registry Login with Conjur"
runs-on: self-hosted
environment: dev
needs: stage-9-ssh-deploy
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Retrieve Docker Hub credentials from Conjur
uses: ./
with:
url: ${{ secrets.CONJUR_URL }}
account: conjur
authn_id: ${{ secrets.CONJUR_SERVICE_ID }}
secrets: "data/vault/devsecops/dockerhub_aslan/username|REGISTRY_USERNAME;data/vault/devsecops/dockerhub_aslan/password|REGISTRY_PASSWORD"
- name: Login to Docker Hub using Conjur credentials
run: |
echo "Logging in to docker.io as $REGISTRY_USERNAME (credentials from Conjur)"
echo "$REGISTRY_PASSWORD" | docker login docker.io \
--username "$REGISTRY_USERNAME" \
--password-stdin
echo "Docker login successful — credentials came from Conjur, not from GitHub Secrets!"
- name: Pull public image to confirm login
run: |
docker pull "$REGISTRY_USERNAME/conjur-workshop:latest" 2>/dev/null \
|| docker pull hello-world \
&& echo "Registry access confirmed."
- name: Logout from registry
if: always()
run: docker logout docker.io
- name: Clean Workspace
uses: AutoModality/[email protected]
# ============================================================
# STAGE 11 - Audit Trail
# Demonstrates: every secret access is logged automatically
# in Conjur. This job queries the Conjur audit API to show
# who accessed what and when — full traceability for compliance.
#
# Pre-requisite: the Conjur host must have read permission on
# audit events (contact your Conjur admin to grant auditor role).
# ============================================================
stage-11-audit-trail:
name: "Stage 11 - Audit Trail"
runs-on: self-hosted
environment: dev
needs: stage-10-docker-registry
permissions:
id-token: 'write'
contents: 'read'
steps:
- uses: actions/checkout@v4
- name: Authenticate with Conjur to obtain session token
run: |
JWT_TOKEN=$(curl -s \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value)
SESSION_TOKEN=$(curl -s \
--request POST \
"${{ secrets.CONJUR_URL }}/authn-jwt/${{ secrets.CONJUR_SERVICE_ID }}/conjur/authenticate" \
--header "Content-Type: application/x-www-form-urlencoded" \
--header "Accept-Encoding: base64" \
--data-urlencode "jwt=$JWT_TOKEN")
echo "CONJUR_SESSION_TOKEN=$SESSION_TOKEN" >> $GITHUB_ENV
- name: Query Conjur audit log (last 20 fetch events)
run: |
echo "=== Conjur Audit Log — Secret Fetch Events ==="
curl -s \
-H "Authorization: Token token=\"$CONJUR_SESSION_TOKEN\"" \
"${{ secrets.CONJUR_URL }}/audit?limit=20&kind=fetch" \
| jq -r '.[] | "[\(.timestamp)] \(.subject.role) fetched \(.resource.id)"' \
|| echo "Note: audit access requires the host to have auditor permissions in Conjur."
- name: Workshop summary
run: |
echo ""
echo "============================================"
echo " Workshop Complete — 11 Stages Executed"
echo "============================================"
echo ""
echo "What was demonstrated:"
echo " 1. JWT auth — no stored credentials"
echo " 2. Multiple secrets + log masking"
echo " 3. Database connection (MySQL)"
echo " 4. Hardcoded credentials vs Conjur"
echo " 5. Least privilege enforcement"
echo " 6. Real database query end-to-end"
echo " 7. Credential rotation"
echo " 8. Environment promotion (dev→staging→prod)"
echo " 9. SSH deploy with key from Conjur"
echo " 10. Docker registry login from Conjur"
echo " 11. Audit trail — every access logged"
echo ""
echo "Zero credentials stored in this repository."
- name: Clean Workspace
uses: AutoModality/[email protected]