agentleFS
ghivelyghively/infra-ops/skills/secrets-vault/SKILL.md

secrets-vault

Skill0 starsChanged 5 months agoMITView on GitHub

HashiCorp Vault as source of truth: community.hashi_vault runtime lookups, repo holds only paths never values, no_log true, gitleaks MR gate, JWT/OIDC bound claims for per-env access. Agent never sees plaintext. Triggers on: vault, secret, no_log, hashi_vault, gitleaks, plaintext credential, rotate, jwt oidc, ansible vault.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every task that handles a secret must have no_log: true.

    line 83
  2. Distinctive:

    Prefer JWT/OIDC over static Vault tokens in CI — no static credentials stored in GitLab:

    line 99
  3. Very specific:

    Add TruffleHog (verified-secrets) as a complementary CI stage — it confirms a match is a *live* credential, deeper than gitleaks' regex.

    line 149
  4. Very specific:

    Document the break-glass procedure in knowledge/runbooks/vault-break-glass.md.

    line 265

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Secrets & Vault Skill

When to Use

Load this skill when authoring any Ansible task or CI job that references credentials, when reviewing a playbook for secret-handling practices, or when configuring GitLab↔Vault JWT/OIDC integration. Also load for gitleaks gate configuration or Vault policy design.

How It Works

Core Principle: Agent Never Sees Plaintext

The agent edits references (Vault paths, variable names), never values. Decryption keys and Vault tokens live only in the CI runner's protected/masked variables or are fetched by the runner at runtime. The agent has zero access to vault passwords or Vault tokens. (ansible-iac-gitops.md §3; pci-dss-devops.md §8)

HashiCorp Vault — the Source of Truth

Vault is preferred over ansible-vault for enterprise estates:

  • Dynamic/short-lived secrets (rotated automatically).
  • Fine-grained ACLs (per-path, per-environment).
  • Tamper-evident audit log of every secret access.
  • Vault Radar scans repos for hard-coded secrets.

(ansible-iac-gitops.md §3; pci-dss-devops.md §8 — HashiCorp Vault directly satisfies PCI DSS Req 8.6.2 no-hard-coded creds, 8.6.3 rotation, Req 3 key management, Req 10 audit of secret access)

community.hashi_vault Runtime Lookup

The repo (and the agent) holds only the path, never the secret value. In community.hashi_vault 7.x, do not pass a raw token= inline on every lookup — configure auth once via the collection's environment variables / ansible.cfg [hashi_vault_collection], or do a single community.hashi_vault.vault_login and reuse the session. Prefer auth_method: jwt / approle / aws over a static token.

# Env-driven auth (set VAULT_ADDR + the auth env vars on the runner); lookups carry
# only the path. The runner fetches at run-time; nothing is stored in the repo.
- name: Deploy with DB password from Vault
  ansible.builtin.template:
    src: db.conf.j2
    dest: /etc/myapp/db.conf
  vars:
    db_password: "{{ lookup('community.hashi_vault.vault_kv2_get', 'ansible/prod/db_password').secret.value }}"
  no_log: true        # REQUIRED: prevents value appearing in logs/ARA output

Useful modules: community.hashi_vault.vault_login (establish a session), vault_kv2_get/vault_read (read), and the dynamic-secret pattern below. Source: ansible-iac-gitops.md §3; modular-ansible-repos.md §3.

Dynamic (short-lived) secrets — preferred over static

Vault's strongest control is short-lived, auto-revoked credentials (PCI DSS 8.6.x favors short-lived over static). Read a dynamic DB credential from a Vault database secrets-engine role instead of a stored password:

- name: Get a short-lived DB credential
  community.hashi_vault.vault_read:
    path: database/creds/app-prod      # Vault issues a fresh user/pass, auto-revoked at TTL
  register: db_cred
  no_log: true
# db_cred.data.data.username / .password — used immediately, never persisted

no_log: true — Non-Negotiable

Every task that handles a secret must have no_log: true. The agent reads ARA run output and GitLab job logs — if a secret appears in output the agent has seen it.

- name: Configure secret (no_log mandatory)
  ansible.builtin.template:
    src: config.j2
    dest: /etc/service/config
  no_log: true

Failure to set no_log on secret tasks is a lint violation. Add a custom ansible-lint rule to enforce it. (ansible-iac-gitops.md §3; pci-dss-devops.md §4)

GitLab↔Vault JWT/OIDC Integration

Prefer JWT/OIDC over static Vault tokens in CI — no static credentials stored in GitLab:

  1. GitLab job authenticates to Vault using a JWT ID token (VAULT_ID_TOKEN).
  2. Vault role has bound claims: restrict to specific GitLab project, namespace, or Git reference pattern.
  3. Production Vault paths are only accessible by jobs with ref_type=tag on protected v*.*.* tags — feature branches cannot read prod secrets.
# .gitlab-ci.yml (Vault JWT auth)
deploy_prod:
  id_tokens:
    VAULT_ID_TOKEN:
      aud: https://vault.example.com
  secrets:
    PROD_DB_PASSWORD:
      vault: ansible/prod/db_password@secret
      file: false

Environment-specific secret paths:

secret/data/ansible/dev/db_password     ← dev jobs only
secret/data/ansible/test/db_password    ← test jobs only
secret/data/ansible/prod/db_password    ← protected tags v*.*.* only

Source: multi-env-versioning.md §6.3; pci-dss-devops.md §8.

Gitleaks MR Gate

gitleaks (GitLab Secret Detection or standalone) runs on every MR as a required gate to catch any secret the agent accidentally hardcodes. This directly mitigates the observed AI failure mode: "knows vault syntax but sometimes hardcodes values it shouldn't." (ansible-iac-gitops.md §3 §6)

# .gitlab-ci.yml (gitleaks gate) — pin the digest (the skill forbids :latest); note the
# project moved to the gitleaks/gitleaks org.
secret-detection:
  stage: lint
  image: zricethezav/gitleaks@sha256:<pinned>   # or ghcr.io/gitleaks/gitleaks@sha256:<pinned>
  tags: [linux, docker, ci]
  script:
    - gitleaks detect --source . --redact --exit-code 1
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Add TruffleHog (verified-secrets) as a complementary CI stage — it confirms a match is a live credential, deeper than gitleaks' regex. See the iac-sast-scanning skill for the full secret/SAST chain.

Variable Naming Convention

Prefix secret variables with vault_ to clearly separate them from plain vars:

# group_vars/<group>/vars.yml    (plain — version controlled)
db_host: "db.prod.example.com"
db_port: 5432
db_user: "myapp"

# group_vars/<group>/vault.yml   (ansible-vault encrypted — for low-sensitivity)
vault_db_password: !vault |
  $ANSIBLE_VAULT;1.2;AES256;prod
  ...

For production secrets, Vault runtime lookups (above) are preferred over ansible-vault files. Ansible-vault is acceptable for low-sensitivity or bootstrap secrets only. (ansible-iac-gitops.md §3; multi-env-versioning.md §6.4)

Trust Boundary

  • Agent edits paths and templates; never sets or reads secret values.
  • Vault tokens / vault passwords live only in protected CI/CD variables on the runner.
  • Prod Vault paths are bound-claim restricted to protected tags — feature branches cannot trigger prod-credential access.
  • no_log: true on every secret-handling task without exception.
  • Gitleaks gate runs on every MR before merge. (SPEC.md §2; DESIGN.md §8)

Examples

# Correct: runtime lookup, no_log
- name: Retrieve and apply API key
  ansible.builtin.lineinfile:
    path: /etc/myapp/config
    line: "api_key={{ lookup('community.hashi_vault.vault_kv2_get', 'ansible/prod/api_key') }}"
  no_log: true

# Wrong: hardcoded secret — will be caught by gitleaks gate
- name: BAD — hardcoded secret
  ansible.builtin.lineinfile:
    path: /etc/myapp/config
    line: "api_key=sk-prod-abc12345"  # NEVER

TODO: Add Vault cluster address and namespace once environment discovery is complete. TODO: Add Vault policy examples for the agent service account (read-only on paths it needs, no access to prod paths) once the Vault policy structure is ingested. TODO: Confirm whether ansible-vault is in use for any existing playbooks and migrate to Vault runtime lookups per the documentation playbook rework task.

Deep Reference

Vault Reference Patterns (HashiCorp Vault)

# CORRECT — runtime lookup, value never in source control
- name: Retrieve DB password
  community.hashi_vault.hashi_vault_secret:
    url: "{{ vault_addr }}"
    auth_method: approle
    role_id: "{{ vault_role_id }}"
    secret_id: "{{ vault_secret_id }}"
    path: "secret/data/db/app"
  register: vault_secret
  no_log: true

- name: Configure app
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  vars:
    db_password: "{{ vault_secret.secret.data.password }}"
  no_log: true

# WRONG — never acceptable
- name: Configure app
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  vars:
    db_password: "SuperSecret123"   # CRITICAL — hardcoded

AppRole Authentication (Ansible → Vault)

The recommended auth method for Ansible is AppRole:

  • role_id: stored as a protected CI variable (non-secret)
  • secret_id: fetched from Vault at runtime or stored as a protected/masked CI variable
  • Rotate secret_id on any team member departure or suspected compromise

Vault Policy Principle (least privilege)

Each Ansible service account gets a Vault policy granting read-only access to exactly the paths it needs. Wildcard * policies are never acceptable.

# CORRECT — scoped policy for ansible-deploy
path "secret/data/db/app" {
  capabilities = ["read"]
}

# WRONG
path "secret/*" {
  capabilities = ["read", "write", "delete"]
}

Breaking Glass (emergency credential access)

Document the break-glass procedure in knowledge/runbooks/vault-break-glass.md. Break-glass use must be logged to the governance ledger and reported in the next change control window.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.