Key service

A root key for your project that never leaves Appentic's key store. Keep your app's master key wrapped, and unwrap it only inside your project.

Your app probably has a master key that encrypts sensitive data, such as documents, identity numbers or anything personal. Kept in an environment variable, that key is only as safe as everything that can read the environment: a backup, a log or a leaked file.

A Key service gives your project a root key that never leaves Appentic's key store. Your app keeps its master key wrapped by that root key. At start, it asks the Key service to unwrap it and keeps it in memory. A wrapped key on its own is useless to anyone who finds it.

Only your project's own services, workers and cron jobs can use the key. Every use is logged.

A project has one Key service. There's no machine or size to choose. Key services are available for projects whose machines are all in the EU for now.

Add a Key service

  1. On the project's Services page, click Add next to Key service.
  2. Click Add Key service.

The form shows the monthly fee under Price. Your key is ready in a few seconds. Then every web service, worker and cron job in the project restarts once to pick up its settings.

You can also ask a coding agent to add one. It needs your go-ahead for the fee.

Use it in your app

Appentic sets these variables on every web service, worker and cron job in the project. None of them is a secret.

VariableWhat it is
APPENTIC_KMS_ADDRThe key store's address. It answers only inside Appentic.
APPENTIC_KMS_KEYYour project's key name
APPENTIC_KMS_ROLEThe role your app signs in as
APPENTIC_KMS_MOUNTtransit
APPENTIC_KMS_AUTH_PATHauth/kubernetes/login
APPENTIC_KMS_TOKEN_FILEA file holding your app's sign-in token. It renews itself.
APPENTIC_KMS_CA_FILEThe certificate your app trusts the key store by

The Key service's Connect page shows the same list. Your app signs in with the token file, then calls encrypt or decrypt. In Python:

import base64, os, requests

ADDR = os.environ["APPENTIC_KMS_ADDR"]
CA = os.environ["APPENTIC_KMS_CA_FILE"]


def _token() -> str:
    with open(os.environ["APPENTIC_KMS_TOKEN_FILE"]) as f:
        jwt = f.read()
    r = requests.post(f"{ADDR}/v1/{os.environ['APPENTIC_KMS_AUTH_PATH']}",
                      json={"role": os.environ["APPENTIC_KMS_ROLE"], "jwt": jwt}, verify=CA, timeout=10)
    r.raise_for_status()
    return r.json()["auth"]["client_token"]


def _transit(op: str, body: dict) -> dict:
    url = f"{ADDR}/v1/{os.environ['APPENTIC_KMS_MOUNT']}/{op}/{os.environ['APPENTIC_KMS_KEY']}"
    r = requests.post(url, json=body, headers={"X-Vault-Token": _token()}, verify=CA, timeout=10)
    r.raise_for_status()
    return r.json()["data"]


def wrap(master_key: bytes) -> str:
    """Run once. Store what it returns (vault:v1:...) in an environment variable."""
    return _transit("encrypt", {"plaintext": base64.b64encode(master_key).decode()})["ciphertext"]


def unwrap(wrapped: str) -> bytes:
    """Run at start. Keep the master key in memory only."""
    return base64.b64decode(_transit("decrypt", {"ciphertext": wrapped})["plaintext"])

Wrap once, unwrap at start

  1. Make a new master key and wrap it from inside your project, for example in a web service's Shell: python -c "import os; from app.kms import wrap; print(wrap(os.urandom(32)))".
  2. Add the result, which starts vault:v1:, as an environment variable, such as MASTER_KEY_WRAPPED.
  3. At start, your app calls unwrap(os.environ["MASTER_KEY_WRAPPED"]) and keeps the master key in memory. Never write it to the database, a file or a log.

Unwrap once per process, not per request. Your app only calls the Key service when it starts, so running traffic never waits for it.

If unwrapping fails, stop at start with a clear message. Never fall back to a key in plain text.

Activity

The Key service's Activity page lists every call in the last 30 days: when, which service, worker or cron job made it, whether it signed in, encrypted or decrypted, and whether it was allowed. Hover a refused call to see why. Nothing your app encrypted is ever logged.

Rotate the key

On the Key service's Settings, click Rotate key. This adds a new version of the key. Anything you wrap from then on uses the new version, and everything wrapped before still unwraps, so your app needs no change. To move your stored master key onto the new version, unwrap it and wrap it again.

Emergency stop

If you think someone is decrypting your data who shouldn't be, click Disable key on the Key service's Settings. Every use of the key is refused at once, and your services and workers restart so none keeps the master key in memory. Your app can't start until you enable the key again. Your data is safe.

Click Enable key to undo it. Your services and workers restart and sign in again.

Remove a Key service

On the Key service's Settings, click Remove Key service. Nothing in the project can use the key from then on, and billing stops.

The key is kept for 30 days, then deleted for good. Data wrapped with it can't be unwrapped after that. Add a Key service to the same project within those 30 days to get the same key back, with all its versions.

Deleting the project removes its Key service the same way.