Secrets
Keep each environment's keys, passwords and settings in one place and hand them to functions, apps, servers and CI, with versions, tokens, an audit trail and rotation reminders. Values are encrypted at rest with a key for each team.
Get started#
A secrets project holds a set of secrets, say every key of one shop. A project has environments, by default production (production), preview (preview) and development (development), and each secret has its own value and versions in each. A project's name is lowercase letters, digits and hyphens; a secret's name is letters, digits and _, up to 64 characters, not starting with a digit (such as STRIPE_KEY).
cloudhost secrets create shop --rotate-days 90
# created secrets project shop (production, preview, development)
printf %s "$STRIPE_KEY" | cloudhost secrets set STRIPE_KEY --project shop --env production
cloudhost secrets list --project shop
cloudhost secrets get STRIPE_KEY --project shop --env productionA value is never taken from the command line, so it stays out of your shell history: pipe it on stdin or type it at the prompt. Without --env, secrets set sets the value in every environment of the project.
# cloudhost.toml
[secrets]
project = "shop"
env = "development"With a [secrets] section in the project file, or the CLOUDHOST_SECRETS_PROJECT variable, you no longer need --project on every command.
In the dashboard#
In the dashboard, Secrets lists the team's secrets projects with how many secrets, tokens and reads in the last 30 days each has. Create one there or from the Create button at the top.
- Secrets: a table of name, environments (Production, Preview, Development), type (sensitive or plain), last change and who made it; add, import a
.envfile, edit, and show a value (with a second factor). - Versions: each secret's history in each environment with time, author and note, and "roll back to this version".
- Tokens, Access, Audit, Usage and Settings (environments, rotation reminders, links to functions and apps, deleting the project).
The CLI#
| Command | |
|---|---|
cloudhost secrets projects · cloudhost secrets create <project> [--env …] [--rotate-days <n>] [--restricted] | The team's projects and the plan's limits; create a project |
cloudhost secrets list · cloudhost secrets get <NAME> [--env <env>] [--version <n>] | Names, types and each environment's version (no values); one secret's value |
cloudhost secrets set <NAME> [--env …] [--type plain|sensitive] [--note <text>] | Set or change; the value from stdin or a prompt |
cloudhost secrets import <FILE> [--env …] [--type …] | The secrets of a .env file (- reads stdin) |
cloudhost secrets rm <NAME> [--env <env>] | Delete with its history, or from one environment only |
cloudhost secrets rotate <NAME> [--generate [--length <n>]] · cloudhost secrets versions <NAME> · cloudhost secrets rollback <NAME> --env <env> --to <n> | Rotate, history and rollback |
cloudhost secrets pull [--env <env>] [--file <path>] [--yes] | The plain values into .env.local (mode 600); sensitive ones are not written |
cloudhost run [--env <env>] -- <command…> | Run a program with the secrets in its environment; nothing is left on disk |
cloudhost secrets tokens [list] · tokens create <name> [--access read|write] [--env …] [--expires <days>] · tokens revoke <id> | The project's tokens |
cloudhost secrets link --to <function or app> [--mode binding|variables] · cloudhost secrets unlink --to <…> | Link the project to a function or app |
cloudhost secrets audit [--env <env>] [--limit <n>] · cloudhost secrets usage · cloudhost secrets delete-project <project> | The audit trail, this month's usage and deleting a project |
Every command above also takes --project (default: the project file).
cloudhost run --env production -- node server.js
cloudhost run -- npm run dev # development, or env in [secrets]
# in CI, with a read token instead of signing in:
CLOUDHOST_SECRETS_TOKEN=$TOKEN cloudhost run --project shop --env production -- npm testrun -- fetches the current values, puts them over your environment and runs the command; no file is written. secrets pull is for tools that only read .env: it leaves sensitive secrets out and names them at the top of the file. It does not overwrite a file it did not write without --yes.
Tokens#
For a server, CI or a script outside the platform, make a project token. A token sees one project only, is read-only (read) or read-write (write), reaches all environments or some, and can expire. It looks like chsm_<id>_<secret> and is shown once.
cloudhost secrets tokens create ci --project shop --access read --env production --expires 90
# token chsm_3f9a1c2b7d4e_… (shown once)
cloudhost secrets tokens --project shop
cloudhost secrets tokens revoke 3f9a1c2b7d4e --project shopA token never reaches more than the person who made it, and making one in the dashboard asks for a second factor. A token for one environment needs no ?env. Revoking applies at once on the server that took it and within 15 seconds everywhere.
The API#
Routes are under /v1/secrets/projects/<project>. The value routes (list, read, set, import, rotate, versions, rollback, export) take a project token or the team's API key; management (projects, environments, tokens, access, audit, usage, links) takes an API key or a session only. All of them are in the API reference.
curl "https://api.ibel.ai/v1/secrets/projects/shop/secrets/STRIPE_KEY?env=production" \
-H "Authorization: Bearer $TOKEN"
# {"name":"STRIPE_KEY","environment":"production","version":3,"type":"sensitive","updated_at":"…","value":"…"}Errors are {"error": {"code", "message"}}: 401 unauthorized for a wrong or revoked token, 403 forbidden for a read-only token or an environment the token does not reach, 404 not_found for a project or secret that does not exist (or another project the token cannot see), 403 step_up_required when a dashboard session needs a second factor.
The SDK#
import { Secrets } from "@cloudhost/secrets"
// token: a project token, or CLOUDHOST_SECRETS_TOKEN; project: or CLOUDHOST_SECRETS_PROJECT
const secrets = new Secrets({ token: process.env.SECRETS_TOKEN, project: "shop", env: "production" })
const key = await secrets.get("STRIPE_KEY")
const all = await secrets.all() // { NAME: value } of the environment
await secrets.set("LOG_LEVEL", "info", { environments: ["development"], type: "plain" })
await secrets.rollback("STRIPE_KEY", { env: "production", to: 3 })The SDK only calls the API above and has no dependencies, so it runs in Node, browsers and functions. Each read is kept in memory for 60 seconds (cacheTtl); after that the cached value is answered at once while a fresh one is fetched in the background, so a running program sees a rotation within about a minute without a restart. Errors are SecretsError with a status and a code.
In functions and apps#
A function or app can read one secrets project. The values travel to the servers with the deployment's signed configuration and are opened only in that function's memory: no network call, no token in your code, no file on disk.
cloudhost secrets link --project shop --to my-api # env.SECRETS
cloudhost secrets link --project shop --to my-api --mode variables # each secret as env.NAME
cloudhost secrets unlink --project shop --to my-api- A production deployment gets the production values, and previews get the preview values. Development is for
run --andpullon your own machine. - In variables mode, a variable of the project or team with the same name wins.
- A function or app reads one secrets project. Linking needs read access to production and preview, since it hands their values to that code.
- Every change, rotation or rollback reaches linked functions without a redeploy. A project a function reads cannot be deleted; unlink first.
Access and restricted projects#
| Names and history | Read values | Change | |
|---|---|---|---|
| Owner, admin | every project | yes | yes |
| Developer | open projects, and restricted ones they are listed on | yes | yes; on a restricted project with write access |
| Viewer, billing | open projects | no | no |
| Project token | its own project | its environments | write tokens only |
A restricted project is seen only by owners, admins and the people listed on it; making a project restricted and listing people is for owners and admins (PUT /v1/secrets/projects/<project>/access/<person> with {"access": "read"|"write"}). A member limited to some projects by an access group cannot reach Secrets, and a build's key never reads a value.
Audit#
Every read, change, rotation, rollback, pull and export, token made or revoked, access change and link is recorded: the time, project, secret, environment, version, who (a person, an API key or a token) and the IP address. A value is never recorded. Identical reads within a minute are one row with a count.
cloudhost secrets audit --project shop --limit 50
curl "https://api.ibel.ai/v1/secrets/projects/shop/audit?secret=STRIPE_KEY&env=production" -H "Authorization: Bearer $KEY"
curl "https://api.ibel.ai/v1/secrets/audit?action=read" -H "Authorization: Bearer $KEY" # the whole team (owner or admin)Results come in pages: send next back as before.
Rotation#
Set how many days values should live, for a project (--rotate-days) or a secret (rotate_days). When a secret is due, the list marks it due for rotation and the team's developers and admins hear once, in the notification centre and by email.
printf %s "$NEW_PASSWORD" | cloudhost secrets rotate DB_PASSWORD --project shop --env production
cloudhost secrets rotate WEBHOOK_SECRET --project shop --generate --length 48 # shown once
cloudhost secrets versions DB_PASSWORD --project shop --env production
cloudhost secrets rollback DB_PASSWORD --project shop --env production --to 3A rotation is a new version; without a value, a random one is made and shown once. A rollback makes an old version's value the new current version, so history is never rewritten.
How your secrets are kept#
Each team's secrets, environment variables, versions and audit trail are kept in that team's own store, and every value is encrypted with the team's own key. That key is never written down whole: it is split in three, and each part is opened by one of three key servers run at three separate Iranian cloud providers. Any two of the three open the key; one part alone says nothing about it.
- One stolen server, one provider, or even every stored file together is not enough to read your values.
- The key servers hold their key in locked memory only (no disk, no swap), answer only our secrets service over mutual certificates, and log every call.
- With one key server down, secrets keep working.
- Each server that runs your code receives only the values of the functions and apps placed on it, encrypted to that server, and keeps them in memory only.
- A team key can be rotated: every value is re-encrypted under the new key; versions and tokens stay as they are.
Limits#
| Hobby | Pro | |
|---|---|---|
| Secrets projects | 3 | 100 |
| Environments per project | 3 | 10 |
| Tokens | 10 | 200 |
| Version history | 30 days | 1 year |
| Audit kept | 7 days | 90 days |
| Size of a value | 5 KB | 5 KB |
On Hobby, past the month's free amount, reads and writes are refused until next month, like the other Hobby caps. GET /v1/secrets/projects also returns the team's plan limits.
Billing#
| Meter | What counts |
|---|---|
| Secrets stored | Each secret for each hour it is kept, however many environments it has; a secret kept all month is one secret-month |
| Secret reads | Each value read over the API, the CLI (get, run --, pull) or the SDK, per 10,000 |
| Secret writes | Each new version in each environment: set, import, rotate, rollback, delete |
Reading env.SECRETS, or the variables mode, in a function is not counted. Prices and each plan's free amounts are on the pricing page; this month's usage is in cloudhost secrets usage or GET /v1/secrets/usage. Projects' own environment variables stay free.