# HashiCorp Vault (https://docs.capy.ai/cloud-credentials/vault)

> Let cloud machines log in to HashiCorp Vault with the JWT auth method, with no stored Vault token.

Machines log in with Vault's JWT auth method. Capy's servers never call your Vault, so the check runs from a thread instead. Your Vault server must reach the discovery and JWKS URLs over the internet to verify tokens. Machines reach Vault directly, or through your tailnet with [Tailscale](/integrations/tailscale). Read [OIDC federation](/cloud-credentials) first for how the token, the issuer, and refresh work.

## Set up HashiCorp Vault [#set-up-hashicorp-vault]

Open **Settings → Cloud credentials → Vault** and follow these steps. The setup page fills in your issuer URL, organization ID, and project ID, so copy the commands from there. The commands below use placeholders.

<Steps>
  <Step title="Add the project in Capy">
    On **Vault**, choose **Add project**. Pick the Capy project, then enter:

    - **Vault address**: an `https` URL, for example `https://vault.example.com`.
    - **Role**: the JWT auth role machines log in with. The default is `capy-<project id>`, one role per project, so another project's setup never replaces it.
    - **Auth mount**: where the JWT auth method is enabled. The default is `capy`. Use a mount that only Capy uses, because its config trusts Capy's issuer alone.
    - **Namespace**: optional. HCP Vault uses `admin`.
    - **Audience**: the value for the role's `bound_audiences`. The default is the Vault address.

    <Screenshot name="cloud-access-vault-setup" alt="Vault setup with the project, Vault address, role, auth mount, namespace, and audience" caption="Each field feeds the Trust Capy commands below it." />
  </Step>
  <Step title="Trust Capy">
    Run these once with a Vault admin token:

    ```bash
    ISSUER='<issuer URL>'
    export VAULT_ADDR='https://vault.example.com'
    vault auth enable -path='capy' jwt
    vault write 'auth/capy/config' oidc_discovery_url="$ISSUER" bound_issuer="$ISSUER"
    vault write 'auth/capy/role/capy-<project id>' - <<'EOF'
    {
      "role_type": "jwt",
      "user_claim": "project",
      "bound_audiences": ["https://vault.example.com"],
      "bound_claims_type": "glob",
      "bound_claims": { "sub": "org/<org id>/project/<project id>/thread/*" },
      "claim_mappings": { "thread": "thread", "machine": "machine" },
      "token_policies": ["capy-<project id>-read"],
      "token_ttl": "1h",
      "token_max_ttl": "1h"
    }
    EOF
    ```

    Vault matches `bound_claims` as a glob, so the role accepts every thread in this project and no other project. `claim_mappings` copies the thread code and machine ID into the token's metadata, which Vault's audit log records.
  </Step>
  <Step title="Grant access">
    Write the policy the role names, with the paths the agent needs:

    ```bash
    vault policy write 'capy-<project id>-read' - <<'EOF'
    path "secret/data/capy-<project id>/*" {
      capabilities = ["read"]
    }
    EOF
    ```
  </Step>
  <Step title="Verify from a thread">
    Choose **Save**, then **Verify from a thread**. Capy opens a new thread in the project that runs the login line and `vault token lookup`, and reports the token's display name and policies. The status card shows **Checked from a machine**.

    <Screenshot name="cloud-access-vault-status" alt="Vault status card with the exported variables and the login line" caption="Machines export these variables, and the agent logs in with this line." />
  </Step>
</Steps>

## What machines get [#what-machines-get]

| Files in `/etc/capy/cloud-access/` | Exports |
| --- | --- |
| `vault.jwt` | `VAULT_ADDR`, `VAULT_NAMESPACE` when set, `CAPY_VAULT_AUTH_MOUNT`, and `CAPY_VAULT_ROLE` |

The Vault CLI is preinstalled on machines. The CLI doesn't read a token file, so the agent logs in with this line, which leaves a Vault token in `~/.vault-token` for up to 1 hour:

```bash
vault login -no-print "$(vault write -field=token "auth/$CAPY_VAULT_AUTH_MOUNT/login" role="$CAPY_VAULT_ROLE" jwt=@/etc/capy/cloud-access/vault.jwt)"
```

## Revoke access [#revoke-access]

To stop every login at once, delete the role or disable the auth mount. To stop Capy from sending tokens, remove the project from **Vault** in Capy. Vault tokens that machines already hold last until their TTL ends, at most 1 hour.

## Troubleshooting [#troubleshooting]

The check runs in a thread, so the thread shows Vault's error. For a machine with no token files, see [OIDC federation troubleshooting](/cloud-credentials#troubleshooting).

<AccordionGroup>
  <Accordion title="The login from a thread fails">Check that the role's `bound_audiences` matches the **Audience** field, that the auth mount and role match the setup, and that your Vault server can reach the discovery URL. If Vault is on a private network, connect machines with [Tailscale](/integrations/tailscale).</Accordion>
</AccordionGroup>
