> ## Documentation Index
> Fetch the complete documentation index at: https://ona.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# HashiCorp Vault OIDC Integration

> Access HashiCorp Vault secrets from Ona environments using JWT authentication, without static tokens.

<Note>Available on the Enterprise plan. [Contact sales](https://ona.com/contact/sales) to learn more.</Note>

Ona environments can authenticate to [HashiCorp Vault](https://www.vaultproject.io/) using the [JWT auth method](https://developer.hashicorp.com/vault/docs/auth/jwt). The Ona CLI generates a JWT token that Vault validates against Ona's OIDC discovery endpoint, then issues a Vault token with the policies you configure.

No static Vault tokens or AppRole credentials are needed.

## Prerequisites

* Enable V3 tokens on the [OIDC Token Configuration](https://app.gitpod.io/settings/security/oidc) page in your organization settings. See [Enable V3 tokens](/ona/configuration/oidc#enable-v3-tokens).
* Vault CLI installed in your environment and `VAULT_ADDR` configured.

## Step 1: Enable the JWT auth method

Enable the JWT auth method in Vault and configure it to trust Ona's OIDC endpoint:

```bash theme={null}
vault auth enable jwt

vault write auth/jwt/config \
  oidc_discovery_url="https://app.gitpod.io" \
  default_role="ona-environment"
```

This tells Vault to fetch Ona's signing keys from `https://app.gitpod.io/.well-known/jwks.json` and validate tokens against them.

## Step 2: Create a Vault role

Create a role that defines which Ona tokens are accepted and what Vault policies they receive.

### Organization-scoped role

Allow any environment in a specific Ona organization:

```bash theme={null}
vault write auth/jwt/role/ona-environment \
  role_type="jwt" \
  bound_audiences="vault.hashicorp.com" \
  bound_claims='{"organization_id": "<ONA_ORG_ID>"}' \
  user_claim="sub" \
  token_policies="ona-read" \
  token_ttl="1h" \
  token_max_ttl="4h"
```

### Project-scoped role

Restrict to environments from a specific project:

```bash theme={null}
vault write auth/jwt/role/ona-project \
  role_type="jwt" \
  bound_audiences="vault.hashicorp.com" \
  bound_claims='{"organization_id": "<ONA_ORG_ID>", "project_id": "<ONA_PROJECT_ID>"}' \
  user_claim="sub" \
  token_policies="ona-read" \
  token_ttl="1h" \
  token_max_ttl="4h"
```

### User-scoped role

Restrict to a specific user by email:

```bash theme={null}
vault write auth/jwt/role/ona-dev \
  role_type="jwt" \
  bound_audiences="vault.hashicorp.com" \
  bound_claims='{"organization_id": "<ONA_ORG_ID>", "email": "dev@example.com"}' \
  user_claim="email" \
  token_policies="dev-secrets" \
  token_ttl="1h" \
  token_max_ttl="4h"
```

### Role parameters

| Parameter         | Description                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `bound_audiences` | Must match the `--audience` used in `ona idp token`. Default: `vault.hashicorp.com`                  |
| `bound_claims`    | JSON object of claim-value pairs. All must match for authentication to succeed. Uses V3 flat claims. |
| `user_claim`      | The token claim to use as the Vault identity alias. Common choices: `sub`, `email`, `user_id`        |
| `token_policies`  | Vault policies to attach to the issued token                                                         |
| `token_ttl`       | Initial TTL of the Vault token                                                                       |
| `token_max_ttl`   | Maximum TTL the token can be renewed to                                                              |

### Available V3 claims for `bound_claims`

You can bind on any V3 flat claim. Common choices:

* `organization_id` - restrict to an Ona organization
* `project_id` - restrict to a specific project
* `environment_id` - restrict to a specific environment
* `user_id` - restrict to a specific user
* `email` - restrict by user email
* `creator_email` - restrict by who created the environment
* `runner_id` - restrict to environments on a specific runner
* `service_account_id` - restrict to a specific service account

## Step 3: Create a Vault policy

Create a policy that grants the access your environments need:

```bash theme={null}
vault policy write ona-read - <<EOF
path "secret/data/my-app/*" {
  capabilities = ["read"]
}

path "secret/data/shared/*" {
  capabilities = ["read", "list"]
}
EOF
```

## Step 4: Authenticate from an environment

### Using the CLI shortcut

The Ona CLI has built-in Vault login support:

```bash theme={null}
ona idp login vault --role ona-environment
```

This runs `vault write auth/jwt/login` under the hood and calls `vault login` with the resulting token.

Options:

* `--role` - the Vault role to authenticate against (defaults to `IDP_VAULT_ROLE` env var)
* `--audience` - the token audience (default: `vault.hashicorp.com`)

### Manual flow

If you need more control, use the Vault CLI directly:

```bash theme={null}
TOKEN=$(ona idp token --audience vault.hashicorp.com)
vault write -format=json auth/jwt/login role=ona-environment jwt="$TOKEN"
```

Or as a one-liner that also logs in:

```bash theme={null}
vault login -method=jwt role=ona-environment jwt="$(ona idp token --audience vault.hashicorp.com)"
```

### Automate on environment startup

Add the login to your [automations](/ona/configuration/tasks-and-services/overview):

```yaml theme={null}
# automations.yaml
tasks:
  vault-login:
    name: Vault Login
    command: |
      ona idp login vault --role ona-environment
    triggeredBy:
      - postDevcontainerStart
```

After authentication, read secrets normally:

```bash theme={null}
vault kv get secret/my-app/database
```

### Using the Vault SDK

For programmatic access in your application:

```go theme={null}
import (
    vault "github.com/hashicorp/vault/api"
    "os/exec"
)

func vaultClient() (*vault.Client, error) {
    client, err := vault.NewClient(vault.DefaultConfig())
    if err != nil {
        return nil, err
    }

    // Get Ona OIDC token
    out, err := exec.Command("ona", "idp", "token",
        "--audience", "vault.hashicorp.com").Output()
    if err != nil {
        return nil, err
    }

    // Authenticate with Vault
    secret, err := client.Logical().Write("auth/jwt/login", map[string]interface{}{
        "role": "ona-environment",
        "jwt":  string(out),
    })
    if err != nil {
        return nil, err
    }

    client.SetToken(secret.Auth.ClientToken)
    return client, nil
}
```

## Using V2 tokens with Vault

V2 tokens also work with Vault's JWT auth method. V2 tokens include `org` and `gsub` claims instead of the flat V3 claims. Use `bound_claims` on `org` for organization-scoped access:

```bash theme={null}
vault write auth/jwt/role/ona-v2 \
  role_type="jwt" \
  bound_audiences="vault.hashicorp.com" \
  bound_claims='{"org": "<ONA_ORG_ID>"}' \
  user_claim="sub" \
  token_policies="ona-read" \
  token_ttl="1h"
```

V2 tokens do not include `email`, `project_id`, or other flat claims, so fine-grained access control is limited to matching the `sub` claim. See the [OIDC overview V2 section](/ona/configuration/oidc#v2-tokens) for the full V2 token reference.

## Further reading

* [Vault: JWT/OIDC auth method](https://developer.hashicorp.com/vault/docs/auth/jwt)
* [Vault: Policies](https://developer.hashicorp.com/vault/docs/concepts/policies)
* [Vault: JWT auth API](https://developer.hashicorp.com/vault/api-docs/auth/jwt)
* [Ona OIDC overview](/ona/configuration/oidc)

## Troubleshooting

<Accordion title="&#x22;error validating token: invalid audience claim&#x22;">
  * The `bound_audiences` in the Vault role must match the `--audience` used in `ona idp token`.
  * Default audience for Vault: `vault.hashicorp.com`.
</Accordion>

<Accordion title="&#x22;error validating token: claim organization_id does not match&#x22;">
  * The `bound_claims` values must exactly match the claims in your Ona token.
  * Decode your token: `ona idp token --audience vault.hashicorp.com --decode`
  * Verify the claim values match what you configured in the Vault role.
</Accordion>

<Accordion title="&#x22;error fetching keys from OIDC provider&#x22;">
  * Vault cannot reach `https://app.gitpod.io/.well-known/openid-configuration`.
  * Verify network connectivity from your Vault server to `app.gitpod.io`.
</Accordion>

<Accordion title="&#x22;permission denied&#x22; when reading secrets">
  * The Vault token was issued, but the attached policies do not grant access to the requested path.
  * Check the `token_policies` on the Vault role and the policy definitions.
</Accordion>
