# Connect a secret provider

> Resolve secrets from HashiCorp Vault, Azure Key Vault, Kubernetes Secrets or the server environment instead of storing them in Sluice.

This guide shows you how to keep secret values in an external store and let Sluice read them when a task starts. You add a provider, create a secret that holds a reference, and check that the reference resolves.

## Providers

A provider turns a reference into a value. Sluice reads from the store and never writes to it.

| Type | UI name | Configuration | Reference | Credentials |
|---|---|---|---|---|
| `builtin` | Builtin | None. It always exists. | None. Sluice stores the value, encrypted. | `SLUICE_MASTER_KEYS` |
| `env` | Environment | None. It always exists. | `NAME`: Sluice reads `SLUICE_SECRET_<NAME>`. Default: the secret key. | The environment of the server process. |
| `vault` | HashiCorp Vault | `mount` (default `secret`), `address` (optional) | `path#field` in a KV version 2 mount | `SLUICE_VAULT_TOKEN`, or `SLUICE_VAULT_K8S_ROLE` |
| `azure_key_vault` | Azure Key Vault | `vault_url` (required, `https://`) | `name` or `name/version` | `DefaultAzureCredential`: the `AZURE_*` variables, workload identity or managed identity. |
| `kubernetes` | Kubernetes | `namespace` (optional) | `secret-name/key` | The service account of the server pod, or `SLUICE_K8S_KUBECONFIG`. |

The configuration of a provider holds no credentials. Sluice accepts only the fields in the table, and it refuses any other field with 422 `validation_failed`. The credentials come only from the environment of the server.

Set the credentials on every server instance. Any instance can dispatch a task, so any instance can resolve a secret.

## Add a provider

You need the admin role. `builtin` and `env` always exist, so you add only `vault`, `azure_key_vault` and `kubernetes`.

<Steps>

1. Set the credentials in the server environment and restart the instances. See the tabs below.

2. Open **Settings → Secret providers** and click **Add provider**.

3. Type a **Name**, for example `vault-prod`. A name has lower-case letters, digits, `-` and `_`, and starts with a letter or a digit.

4. Select the **Type** and fill in its fields.

5. Click **Add provider**.

6. Click **Check** in the row of the provider. Type a **Reference** and click **Check**. The dialog shows whether the reference resolves, and never the value.

</Steps>

<Tabs>
  <TabItem label="Vault">
    The `vault` provider reads HashiCorp Vault KV version 2. The reference `data/postgres#url` reads the field `url` at the path `data/postgres` of the mount.

    | Item | Source |
    |---|---|
    | Address | The provider field **Address**, else `SLUICE_VAULT_ADDR`. One of them must be set. |
    | Mount | The provider field **KV v2 mount**. Default `secret`. |
    | Token auth | `SLUICE_VAULT_TOKEN`. It wins when it is set. |
    | Kubernetes auth | `SLUICE_VAULT_K8S_ROLE`. Sluice logs in at `auth/kubernetes/login` with the token of the pod service account. |

    With Kubernetes auth, Sluice logs in again at three quarters of the lease, and once after a 401 or 403 answer. It reads the service account token file at each login, so a rotated token works.

    A field that is not a string resolves to its JSON text.

    ```sh
    SLUICE_VAULT_ADDR=https://vault.example.com:8200
    SLUICE_VAULT_K8S_ROLE=sluice
    ```
  </TabItem>
  <TabItem label="Azure Key Vault">
    The `azure_key_vault` provider reads Azure Key Vault secrets. Set **Vault URL** to the vault, for example `https://acme.vault.azure.net`. The reference is the secret name, or `name/version` for one version.

    Sluice signs in with `DefaultAzureCredential`. It tries the `AZURE_*` environment variables, workload identity and managed identity. Give that identity the right to get secrets of the vault.
  </TabItem>
  <TabItem label="Kubernetes">
    The `kubernetes` provider reads a key of a Kubernetes Secret. The reference is `secret-name/key`.

    Sluice reads the Secret from the **Kubernetes namespace** of the provider. Without it, Sluice uses `SLUICE_K8S_NAMESPACE`, which defaults to the namespace of the server pod. Outside a cluster, Sluice reads `SLUICE_K8S_KUBECONFIG`, and the fallback namespace is `default`.

    The service account of the server needs `get` on Secrets. The Helm chart grants it with `kubernetesSecretProvider.enabled: true`.
  </TabItem>
  <TabItem label="Environment">
    The `env` provider reads a variable of the server process. A secret with the reference `PG_URL` reads `SLUICE_SECRET_PG_URL`. Without a reference, the reference is the secret key.

    ```sh
    SLUICE_SECRET_PG_URL=postgres://loader:pw@db:5432/app
    ```

    Use it for a value that your platform already injects, for example from a Kubernetes Secret mounted as an environment variable. A variable that is not set gives `not_found`.
  </TabItem>
</Tabs>

The API offers the same operations:

```sh
curl -X POST "$SLUICE_URL/api/v1/secret-providers" \
  -H "Authorization: Bearer $SLUICE_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "vault-prod", "type": "vault", "config": {"mount": "kv"}}'

curl -X POST "$SLUICE_URL/api/v1/secret-providers/vault-prod/check" \
  -H "Authorization: Bearer $SLUICE_TOKEN" -H "Content-Type: application/json" \
  -d '{"ref": "data/postgres#url"}'
```

## Create a secret with a reference

A flow never names a provider. It reads `${{ secret('PG_URL') }}`, and the secret `PG_URL` decides where the value comes from.

<Steps>

1. Open the **Secrets** tab of the namespace, or **Secrets** in the side bar for a global secret.

2. Click **Add secret** and type the **Key**, for example `PG_URL`.

3. Select the **Provider**, for example `vault-prod`.

4. Type the **Reference**, for example `data/postgres#url`, and save.

5. Click **Check** in the row of the secret. The check resolves the secret of exactly that scope.

</Steps>

```sh
curl -X PUT "$SLUICE_URL/api/v1/secrets/PG_URL" \
  -H "Authorization: Bearer $SLUICE_TOKEN" -H "Content-Type: application/json" \
  -d '{"provider": "vault-prod", "ref": "data/postgres#url"}'
```

An external secret stores only the reference. A request with a `value` returns 422 `validation_failed`. A reference has at most 512 characters.

## Read the check result

**Check** on a provider or on a secret returns one status. The message explains a failure and never holds the value.

| Status | Meaning | What to do |
|---|---|---|
| `ok` | The reference resolves. | Nothing. |
| `not_found` | The store has no value for the reference. | Check the path, the field, the name or the key. |
| `access_denied` | The store refused access, for example a Vault 403. | Give the identity of the server the right to read the value. |
| `provider_error` | Another error, for example a network error or no Vault address. | Check the address and the credentials in the server environment. |

A check of an inherited key returns 404 `secret_not_found`. Check it in its own scope.

At dispatch, `not_found` fails the task with reason `secret_not_found`. The other errors fail it with `secret_provider_error`.

## Cache

Sluice keeps resolved `vault`, `azure_key_vault` and `kubernetes` values in memory on each instance for `SLUICE_SECRET_CACHE_TTL`, 60 seconds by default. A change in the store reaches later tasks after at most that time, without a restart. A change of the provider configuration takes effect at once. Sluice does not cache `builtin` and `env` values.

## Change or remove a provider

| Rule | Result |
|---|---|
| A second provider with the same name | 409 `provider_exists` |
| A change or delete of `builtin` or `env` | 409 `provider_fixed` |
| A delete of a provider that a secret uses | 409 `provider_in_use`. Move or delete those secrets first. |

<Aside type="note">
  Sluice has no AWS Secrets Manager provider. To use a value from AWS, inject it into the server environment and read it with the `env` provider.
</Aside>

## Related pages

- [Use secrets and variables](/how-to/use-secrets-and-variables/)
- [Environment variables](/reference/env/)
- [Security model](/concepts/security-model/)
