# Managing secrets

> Keep credentials out of your config files using environment variables, env.conf, or the secrets manager

Source: https://orbitalhq.com/docs/deploying/managing-secrets

When defining either [connections](/docs/describing-data-sources/configuring-connections) or [authentication tokens](/docs/describing-data-sources/authentication-to-services), you'll need to store sensitive values.

Orbital is built around file-based and git-based config, so these are values you generally don't want checked in.

There are three ways to keep secrets out of your config files:

 * **Environment variables** — inject secrets at the OS level, common with Kubernetes and cloud orchestrators.
 * **`env.conf`** — a separate file alongside your config, typical for local development.
 * **Secrets manager** — a managed store (file-backed, Infisical, etc.) that Orbital looks up on demand.

You can mix all three. See [Source precedence](#source-precedence) for the resolution order.

## Environment variables

All Orbital config files are defined in [HOCON](https://github.com/lightbend/config#examples-of-hocon) format, which allows for variable [substitution](https://github.com/lightbend/config/blob/main/HOCON.md#substitutions) [from the environment](https://github.com/lightbend/config/blob/main/HOCON.md#substitution-fallback-to-environment-variables).

```hocon
jdbc {
  my-connection {
    connectionParams {
      password = ${POSTGRES_PASSWORD}
    }
  }
}
```

This is a popular approach when deploying onto Kubernetes, ECS, or similar — the orchestrator injects secrets into the container environment, and Orbital substitutes them at config load.

## Using env.conf for sensitive data

In addition to the standard HOCON rules for resolving variables, Orbital supports the use of an `env.conf` file as a source for substitutions.

This is useful when developing locally, to keep sensitive values out of `connections.conf` without having to configure environment variables.

**Note**
env.conf files support [environment-specific variations](/docs/deploying/configuring-orbital#environment-specific-configuration).

The general workflow is:

 * Define a `connections.conf` file with your connections, using `${variables}` for placeholders of sensitive data.
 * Create a local `env.conf` file alongside it. The location must match the `additionalSources` path defined in your [taxi.conf](/docs/deploying/configuring-orbital) file as `@orbital/config`.
 * Populate `env.conf` with sensitive values.

Eg:

```hocon connections.conf
jdbc {
  my-connection {
    // ... other params omitted for brevity ...
    connectionParams {
      password = ${postgres_password} // Reads the variable "postgres_password"
    }
  }
}
```

```hocon env.conf
postgres_password=hello123
```

```hocon taxi.conf
   name: com.foo/myProject
   version: 0.1.0
   sourceRoot: src/
   additionalSources: {
        "@orbital/config" : "orbital/config/*.conf",
   }
```

## Secrets manager

The secrets manager is a backend store that Orbital looks up to resolve `${secrets.NAME}` placeholders in your config.

Reference a secret in your config the same way you'd reference an env var:

```hocon
connections.jdbc.warehouse {
   url:      "jdbc:postgresql://warehouse.example.com:5432/prod"
   password: ${secrets.DB_PASSWORD}
}
```

Secrets are isolated by **scope**:

| Scope | Visible to |
| --- | --- |
| `Server` | every workspace on this server |
| `Organisation` | every workspace in that organisation |
| `Workspace` | only that workspace |

Reads cascade — a workspace looks first in `Workspace`, then `Organisation`, then `Server`. The narrower scope wins on a name collision.

Secrets are managed through the Orbital UI under **Authentication → Secrets**.

Pick a backend by setting `vyne.secrets.backend`:

| Value | Backend |
| --- | --- |
| `InMemory` (default) | In-process; loses everything on restart. Tests and demos only. |
| `File` | Local file, age-encrypted. Suitable for dev. |
| `Infisical` | [Infisical](https://infisical.com) — cloud or self-hosted. |
| `Aws` | [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/). |
| `Vault` | [HashiCorp Vault](https://www.vaultproject.io/) — KV v2 mount. |
| `Gcp` | [Google Cloud Secret Manager](https://cloud.google.com/secret-manager). |

### In-memory

The default when no backend is configured. Holds secrets in process memory only — restarts wipe everything, and multi-node clusters don't share state.

```yaml
vyne:
  secrets:
    backend: InMemory    # default; can be omitted
```


**Warning: Not for production**
Use for tests, demos, or quick local exploration. Not suitable for production.

### File

A local on-disk store that survives restart, encrypted with [age](https://age-encryption.org).

```yaml
vyne:
  secrets:
    backend: File
    file:
      path: orbital_data/secrets-store    # default
```

The path defaults to `${vyne.app.data.path}/secrets-store` — `./orbital_data/secrets-store` out of the box. Override with `vyne.secrets.file.path`.

On first start, Orbital generates an `identity.txt` (private key) and `recipients.txt` (public key) in that directory. The store layout is:

```
<path>/
├── identity.txt              # private key — back this up
├── recipients.txt            # public key
└── secrets/
    ├── server/<name>.age
    ├── organisation/<orgId>/<name>.age
    └── workspace/<orgId>/<workspaceId>/<name>.age
```


**Warning: Not for production**
The File backend is intended for development. The decryption key sits next to the encrypted data on disk, so anyone with read access to the directory can read every secret. For production, use Infisical (or another managed store).

### Infisical

Stores secrets in [Infisical](https://infisical.com) — cloud or self-hosted.

You'll need:

 * An Infisical project ID
 * An environment slug within that project (e.g. `prod`)
 * A [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth) machine-identity client ID and client secret

```yaml
vyne:
  secrets:
    backend: Infisical
    infisical:
      site-url: https://app.infisical.com    # default; for self-hosted, point at your install
      project-id: <your-project-id>
      environment-slug: prod
      base-path: /orbital                    # all Orbital secrets live under this folder
      auth:
        client-id: ${INFISICAL_CLIENT_ID}
        client-secret: ${INFISICAL_CLIENT_SECRET}
```

Orbital lays out scopes as folders under `base-path`:

```
/orbital/server/<name>
/orbital/tenant/<orgId>/<name>
/orbital/tenant/<orgId>/<workspaceId>/<name>
```

Missing folders are created automatically on first write — you don't need to provision the layout in advance.

### AWS Secrets Manager

Stores secrets in [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/).

```yaml
vyne:
  secrets:
    backend: Aws
    aws:
      region: eu-west-1            # optional; falls back to the AWS SDK provider chain
      base-path: /orbital          # all Orbital secrets live under this prefix
      # endpoint-override: ...     # optional; for VPC endpoints / localstack
```

Authentication uses the standard [AWS SDK credentials chain](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/credentials-chain.html) — environment variables, shared profile, IAM role on EC2 / ECS / EKS, etc. Provision a principal with `secretsmanager:CreateSecret`, `GetSecretValue`, `PutSecretValue`, `UpdateSecret`, `DescribeSecret`, `ListSecrets`, and `DeleteSecret` on the `base-path`'s ARN pattern.

Orbital encodes scopes as a path-style prefix on the secret name:

```
<base-path>/server/<name>
<base-path>/tenant/<orgId>/<name>
<base-path>/tenant/<orgId>/<workspaceId>/<name>
```

Two AWS-specific notes:

 * **Deletes are immediate.** AWS's default delete schedules removal 7–30 days out, during which the name is reserved. Orbital uses `forceDeleteWithoutRecovery` so the name is freed synchronously — once `delete` returns, you can recreate a secret with the same name.
 * **Last-write-wins.** AWS Secrets Manager has no `expectedVersion` hook on `PutSecretValue`, so concurrent updates do not detect conflicts. If your workflow needs optimistic concurrency, use the File or Infisical backend.

### HashiCorp Vault

Stores secrets in a [HashiCorp Vault](https://www.vaultproject.io/) KV v2 mount.

```yaml
vyne:
  secrets:
    backend: Vault
    vault:
      address: https://vault.example.com:8200
      token: ${VAULT_TOKEN}
      mount: secret                      # default; the KV v2 mount path
      base-path: /orbital                # all Orbital secrets live under this prefix
```

Authentication is via a static [Vault token](https://developer.hashicorp.com/vault/docs/concepts/tokens). Provision a periodic token with `read`, `create`, `update`, `delete`, and `list` capability on `<mount>/data/<base-path>/*` and `<mount>/metadata/<base-path>/*`. Inject the token via the standard env-var / secrets-mount mechanism — Orbital reads it once at startup.

Orbital lays out scopes as KV v2 paths under `base-path`:

```
<base-path>/server/<name>
<base-path>/tenant/<orgId>/<name>
<base-path>/tenant/<orgId>/<workspaceId>/<name>
```

The secret value is stored under the data key `value`; an optional description is stored as `custom_metadata.description`. A folder structure isn't pre-provisioned — KV v2 creates intermediate paths on first write.

Two notes:

 * **Delete is permanent.** Vault's standard `delete` is a soft-delete that keeps versions recoverable. To honour the contract (`delete` means delete), Orbital issues a `DELETE` against `<mount>/metadata/<path>`, which destroys all versions and the metadata key in one call. Recreating a secret at the same name starts a fresh version sequence at 1.
 * **Optimistic concurrency uses CAS.** Writes with an `expectedVersion` set Vault's `cas` option, which performs the version check server-side. Conflicting concurrent writes lose deterministically.

### GCP Secret Manager

Stores secrets in [Google Cloud Secret Manager](https://cloud.google.com/secret-manager).

```yaml
vyne:
  secrets:
    backend: Gcp
    gcp:
      project-id: my-gcp-project
      base-path: orbital              # default; encoded into every secret name
      # endpoint-override: ...        # optional; for emulators / VPC endpoints
```

Authentication uses Google's standard [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) chain — `GOOGLE_APPLICATION_CREDENTIALS`, attached service account on GCE / GKE / Cloud Run, or `gcloud auth application-default login` for local development. Provision the principal with the `roles/secretmanager.admin` role (or a tighter custom role granting `secretmanager.secrets.{create,get,list,update,delete}` plus `secretmanager.versions.{add,access}`) on the project.

GCP has a flat per-project namespace and a constrained character set (`[A-Za-z0-9_-]` only). Orbital encodes the scope hierarchy into the secret id using `__` as a segment separator:

```
<base-path>__server__<name>
<base-path>__tenant__<orgId>__<name>
<base-path>__tenant__<orgId>__<workspaceId>__<name>
```

`orgId`, `workspaceId`, and the secret name itself must therefore avoid `__` and any character outside `[A-Za-z0-9_-]`. `description`, when set, is stored on the secret as the `description` annotation.

Two GCP-specific notes:

 * **Last-write-wins.** GCP `AddSecretVersion` has no `etag` hook for conditional writes, so `expectedVersion` is accepted but not enforced. If your workflow needs optimistic concurrency, use the File or Vault backend.
 * **Deletes are immediate.** `deleteSecret` removes the secret and all its versions in one call — there's no recovery window, so recreating a secret with the same id is allowed and starts a fresh version sequence at 1.

## Source precedence

When the same `${name}` could be supplied by more than one source, Orbital resolves in this order (last wins):

```
[OS env]  →  [env.conf]  →  [Server secret]  →  [Organisation secret]  →  [Workspace secret]
```

If a name is defined in more than one source, Orbital logs a warning at workspace startup naming the source that's being used. Values are never logged.
