Access secrets from external tools
- Tier: Premium, Ultimate
- Offering: GitLab.com, GitLab Self-Managed
- Status: Beta
Use the Secrets Manager API to read GitLab Secrets Manager secrets from external applications and tools, such as Kubernetes applications and infrastructure as code tools. To read secrets in CI/CD jobs, you can use secrets in job scripts.
After a client mints an access token, it reads secrets directly from the OpenBao backend, not through the GitLab application. Minting an access token requires the GitLab API.
Access token flow
- A client authenticates to GitLab with a token that can call the Secrets Manager API. For the supported token types, see prerequisites.
- The client calls the Secrets Manager API to mint a short-lived access token. The access token is a JSON web token (JWT) for OpenBao, not a GitLab access token. The response includes the JWT and the OpenBao connection details.
- The client exchanges the JWT for an OpenBao token, then uses the OpenBao token to read the secret value. Alternatively, the client can send the JWT with each request by using inline authentication.
The JWT and the OpenBao token each expire after five minutes. Because OpenBao implements the Vault API, you can use any HashiCorp Vault compatible client.
Every OpenBao connection detail comes from the mint response, so you do not construct namespace, mount, or authentication paths yourself.
Prerequisites
- GitLab Secrets Manager is enabled.
- The Secrets Manager was provisioned in GitLab 19.2 or later.
- You authenticate with one of:
- A personal access token, project or group access token, or
service account token that has the
apiscope. - A fine-grained personal access token with the Secrets Manager API JWT permission.
- A personal access token, project or group access token, or
service account token that has the
- Your role is at least Reporter.
- You have the read value permission. A project Owner (or a group Owner, for group secrets) can grant this permission. See Manage secrets permissions.
Secrets Managers provisioned before GitLab 19.2 do not support access from external requests or services. To enable external access, see Enable secrets access from external requests.
Authenticate with a service account
Prerequisites:
- Permission to create service accounts.
- The Owner role for the project or group, to grant secrets permissions.
To authenticate with a service account:
- Create a service account.
- Create a personal access token
for the service account with the
apiscope. - Add the service account to the project or group with at least the Reporter role.
- Grant the service account the read value permission.
Read a secret
This example mints an access token for a project, authenticates to OpenBao, then reads a secret value.
To read a group’s secrets, call the
group endpoint
with groups/<group_id> instead of projects/<project_id>.
Mint an access token:
RESPONSE=$(curl --silent --request POST \ --header "PRIVATE-TOKEN: <your_access_token>" \ --url "https://gitlab.example.com/api/v4/projects/<project_id>/secrets_manager/access_token")The response contains an
expires_attimestamp and aprovider.vaultobject withserver,namespace,path,secrets_path, andauth.jwtdetails. The JWT is inprovider.vault.auth.jwt.token.Exchange the JWT for an OpenBao token, then read the value:
SERVER=$(echo "$RESPONSE" | jq --raw-output .provider.vault.server) NAMESPACE=$(echo "$RESPONSE" | jq --raw-output .provider.vault.namespace) MOUNT=$(echo "$RESPONSE" | jq --raw-output .provider.vault.path) SECRETS_PATH=$(echo "$RESPONSE" | jq --raw-output .provider.vault.secrets_path) AUTH_PATH=$(echo "$RESPONSE" | jq --raw-output .provider.vault.auth.jwt.path) ROLE=$(echo "$RESPONSE" | jq --raw-output .provider.vault.auth.jwt.role) JWT=$(echo "$RESPONSE" | jq --raw-output .provider.vault.auth.jwt.token) # Exchange the JWT for a short-lived OpenBao token. VAULT_TOKEN=$(curl --silent --request POST \ --header "X-Vault-Namespace: $NAMESPACE" \ --data "{\"role\":\"$ROLE\",\"jwt\":\"$JWT\"}" \ "$SERVER/v1/auth/$AUTH_PATH/login" | jq --raw-output .auth.client_token) # Read the secret value. curl --silent \ --header "X-Vault-Token: $VAULT_TOKEN" \ --header "X-Vault-Namespace: $NAMESPACE" \ "$SERVER/v1/$MOUNT/data/$SECRETS_PATH/<secret_name>"
On GitLab.com, server is https://secrets.gitlab.com.
On GitLab Self-Managed, server is the OpenBao URL configured for the instance.
For the full request and response format, see the Secrets Manager API.
Use with the Vault CLI
Because OpenBao implements the Vault API, you can use the Vault CLI with the values from the response:
export VAULT_ADDR="<server>"
export VAULT_NAMESPACE="<namespace>"
# Exchange the minted JWT for an OpenBao token.
export VAULT_TOKEN="$(vault write -field=token "auth/<auth_jwt_path>/login" role=<role> jwt=<auth_jwt_token>)"
# Read the secret value.
vault kv get -mount=<path> "<secrets_path>/<secret_name>"Use with the External Secrets Operator
The External Secrets Operator syncs GitLab secrets into Kubernetes secrets through its HashiCorp Vault provider. A workload such as a CronJob in the cluster keeps a fresh access token in a Kubernetes secret, and the operator reads that token to authenticate to OpenBao.
Map the mint response onto a SecretStore, and reference each secret by <secrets_path>/<secret_name>:
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: gitlab-secrets-manager
namespace: my-app
spec:
provider:
vault:
server: https://secrets.gitlab.com # provider.vault.server
path: secrets/kv # provider.vault.path
version: v2
namespace: org_5/group_42/project_99 # provider.vault.namespace
auth:
jwt:
path: api_jwt/cel # provider.vault.auth.jwt.path
role: all_api # provider.vault.auth.jwt.role
secretRef:
name: gitlab-access-token # Kubernetes secret holding the minted token
key: token
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: my-secret
namespace: my-app
spec:
refreshInterval: 1m
secretStoreRef:
name: gitlab-secrets-manager
kind: SecretStore
target:
name: synced-secret
data:
- secretKey: value
remoteRef:
key: explicit/<secret_name> # <secrets_path>/<secret_name>
property: valueThe access token expires after five minutes, so a workload must refresh the gitlab-access-token
Kubernetes secret before it expires.
A native Kubernetes integration is proposed in epic 20382.
Authentication flow
You can view the authentication flow with the External Secrets Operator in this diagram:
sequenceDiagram
accTitle: Access token flow between Workload refresher, Secrets Manager API, ESO, and OpenBao
accDescr: A workload refresher mints a short-lived access token and stores it in a Kubernetes secret. ESO reads the token, authenticates to OpenBao with it, reads the secret value, and writes it to a synced Kubernetes secret on each sync interval.
participant W as Workload <br/> refresher
participant API as Secrets Manager API
participant KAuth as K8s Secret <br/> auth token
participant ESO as ESO
participant OB as OpenBao
participant KApp as K8s Secret <br/> synced
W->>API: Mint access token
API-->>W: Short-lived token
W->>KAuth: Store token
loop Sync interval
ESO->>KAuth: Read token
ESO->>OB: JWT login
OB-->>ESO: Client token
ESO->>OB: Read KV path
OB-->>ESO: Secret value
ESO->>KApp: Write value
end
Note over W,API: Access token expires in 5 min
Use with Terraform
A Terraform or OpenTofu configuration can read GitLab secrets as a data source.
Terraform cannot mint the access token itself, so an
external data source
runs a script that calls the Secrets Manager API and returns the provider.vault connection details.
The Vault provider then
authenticates with the minted JWT and reads the secret.
The script mints the token and prints the connection details as JSON. It reads the GitLab
token from the GITLAB_TOKEN environment variable.
To read a group’s secrets, call the group endpoint with groups/<group_id> instead of projects/<project_id>:
#!/usr/bin/env bash
# scripts/mint_token.sh
set -euo pipefail
eval "$(jq --raw-output '@sh "PROJECT_ID=\(.project_id)"')"
curl --silent --request POST \
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
--url "https://gitlab.example.com/api/v4/projects/${PROJECT_ID}/secrets_manager/access_token" \
| jq '{
server: .provider.vault.server,
namespace: .provider.vault.namespace,
mount: .provider.vault.path,
secrets_path: .provider.vault.secrets_path,
auth_path: .provider.vault.auth.jwt.path,
role: .provider.vault.auth.jwt.role,
jwt: .provider.vault.auth.jwt.token
}'Reference the script from an external data source, configure the Vault provider, then read the secret
with an ephemeral resource:
data "external" "gitlab_secrets_token" {
program = ["bash", "${path.module}/scripts/mint_token.sh"]
query = {
project_id = var.gitlab_project_id
}
}
provider "vault" {
address = data.external.gitlab_secrets_token.result.server
namespace = data.external.gitlab_secrets_token.result.namespace
skip_child_token = true
auth_login_jwt {
mount = data.external.gitlab_secrets_token.result.auth_path
role = data.external.gitlab_secrets_token.result.role
jwt = data.external.gitlab_secrets_token.result.jwt
}
}
ephemeral "vault_kv_secret_v2" "my_secret" {
mount = data.external.gitlab_secrets_token.result.mount
name = "${data.external.gitlab_secrets_token.result.secrets_path}/<secret_name>"
}
# Pass the value to a provider configuration or a write-only argument, for example:
# password_wo = ephemeral.vault_kv_secret_v2.my_secret.data["value"]Set skip_child_token = true in the Vault provider.
By default, the provider uses the OpenBao token from the JWT login to create a child token,
but that OpenBao token cannot create child tokens.
Without this setting, the provider fails with a 403 error and the message
failed to create limited child token.
Ephemeral resources require Terraform 1.10 or later, or OpenTofu 1.11 or later, and Vault provider 5.0 or later.
Terraform and OpenTofu do not store their values in the state or plan files.
With earlier versions, use the vault_kv_secret_v2 data source instead.
The data source stores the secret value in the state in plain text, so restrict access to the state.
The minted JWT is valid for five minutes:
- The Vault provider must read the secret within five minutes of the start of the run. The provider authenticates to OpenBao when it first reads a secret, not when the run starts.
- If you save a plan with
terraform plan -out, apply it within five minutes. During the apply, Terraform reads ephemeral resources again with the JWT from the plan.
A native GitLab Terraform provider integration is proposed in epic 21177.