Vault: secrets and encryption keys

Keep passwords, API keys and encryption keys in one place. Programs read them with a service account, so nothing is written into code.

Getting started

  1. Open Vault in the Console and make a vault per app or environment, such as app-production
  2. Add a secret or create a key
  3. Under Access, give a role to the service account your service uses
  4. In your code, get a token and call the API as shown below

Service account roles

RoleAllows
Read secretsReading secret values
Read and write secretsReading values and adding versions
Encrypt and sign with keysEncrypt, decrypt, sign and verify
Use and rotate keysUsing keys and making new versions
Read secrets and use keysBoth of those
Read and write secrets, use and rotate keysEverything

Without a role a service account cannot use a vault, even in the same Project. For people in the Project: Viewers and Operators see names and activity, Editors can see and change values, and Owners and Admins can delete.

A secret as a Container or Function variable

Set a variable to vault://app-production/db-password, or click Use a Vault value on the variables page. Your code reads the variable as usual, with no change. The value is opened when the service starts; the Console and revisions keep only this address.

First give the service's service account a role that reads secrets. After adding a version, restart the service to get the new value. To pin a version, add its number, as in vault://app-production/db-password/2. If the value cannot be read at start, say because access was removed, the service does not start and says why.

Reading a secret

Shell
# Inside a Container or Function: a token from the machine it runs on
TOKEN=$(curl -s -H "Metadata: true" \
  "http://169.254.169.254/identity/oauth2/token" | jq -r .access_token)

curl -H "Authorization: Bearer $TOKEN" \
  https://api.thailandhosting.com/v1/vaults/app-production/secrets/db-password
# {"name":"db-password","version":2,"value":"...","expiresAt":null}

With no version you get the newest one that is enabled and has not expired. Ask for one with ?version=2. Keep the value in your program's memory for a while instead of calling each time.

Encrypting with a key

Shell
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -d "{\"plaintext\":\"$(printf 'hello' | base64)\"}" \
  https://api.thailandhosting.com/v1/vaults/app-production/keys/customer-data/encrypt
# {"ciphertext":"thv1:2:...","version":2}

curl -X POST -H "Authorization: Bearer $TOKEN" \
  -d '{"ciphertext":"thv1:2:..."}' \
  https://api.thailandhosting.com/v1/vaults/app-production/keys/customer-data/decrypt
# {"plaintext":"aGVsbG8="}

Send data as base64, up to 64 KB at a time. The result names its key version, so it still decrypts after a rotation. If you send aad when encrypting, send the same value when decrypting.

For large or numerous items, ask /data-key for a data key, encrypt in your program with its plaintext, and store its ciphertext beside the data. To read, send that ciphertext to decrypt.

Key types

TypeUsed for
AES-256-GCMEncrypt / decrypt
RSA 2048, RSA 4096One of: encrypt / decrypt, or sign / verify
ECDSA P-256, P-384Sign / verify

SDK

Shell
import { defaultCredential } from '@thailandhosting/identity';

const vault = defaultCredential().vault('app-production');

const secret = await vault.getSecret('db-password');
const ciphertext = await vault.encrypt('customer-data', 'hello');
const plaintext = await vault.decrypt('customer-data', ciphertext);
Shell
from thailandhosting_identity import default_credential

vault = default_credential().vault("app-production")

secret = vault.get_secret("db-password")
ciphertext = vault.encrypt("customer-data", b"hello")
plaintext = vault.decrypt("customer-data", ciphertext)
Shell
cred, err := identity.Default()
vault := cred.Vault("app-production")

secret, err := vault.GetSecret(ctx, "db-password")
ciphertext, err := vault.Encrypt(ctx, "customer-data", []byte("hello"), nil)
plaintext, err := vault.Decrypt(ctx, "customer-data", ciphertext, nil)

th CLI

Shell
th vault create app-production
th vault secret set app-production db-password   # reads the value from standard input
th vault secret get app-production db-password

th vault key create app-production customer-data --type aes256 --rotation-days 90
th vault key encrypt app-production customer-data 'hello'

th vault acts as whoever ran th auth login. API keys cannot call it, so automation and CI use a service account. Purging is only in the Console.

Pricing

A vault itself is free. You pay for the secrets and keys you keep (all versions included) and for each time a program reads a secret or uses a key. See Pricing for rates.

Free: reading a value as a variable when a service starts, the Console and the th CLI, listing, adding versions and rotating keys, calls that fail, and deleted items. If a Project has unpaid usage, programs cannot call its vaults until it is topped up.

Deleting and recovering

Deleted items stop working at once and can be recovered within the period set on the vault (7-90 days). Then they are purged. Once a key is purged, anything encrypted with it can never be opened again.

Turn on purge protection so nobody can purge before the period ends, not even the Project owner. It cannot be turned off.

Activity

The Activity tab shows who did what, when and from which IP, including refused calls. A program's uses are grouped into one line per hour per caller, and kept for 1 year.

If something goes wrong

I get 403 forbidden

The service account has no role that allows this call. The Activity tab shows which name was refused. Give it a role under Access.

Are keys kept in an HSM?

No. Keys are encrypted in software and stored in the system's database. There is no HSM option yet.

Still stuck? Let our team help

Open a ticket from the Console. Tell us the site or service, what you did and the message on screen. Our reply comes back in the same ticket, so everything stays in one place.