# Use any KMS

> Protect TulipFarm operator settings and backups with SOPS and AWS KMS, GCP Cloud KMS, Azure Key Vault keys, Vault Transit, or age.

Source: https://tulipfarm.ai/docs/self-hosting/kms/

A key management service (KMS) holds a master key that never leaves it. You ask the KMS
to encrypt or decrypt small values, and it checks your identity each time. TulipFarm
never calls a KMS. Instead, you use a KMS to protect the files that hold TulipFarm's
settings:

- the operator settings file (`self-host/.env`)
- the `state.json` backup and other backups

This guide uses [SOPS](https://getsops.io/docs/). SOPS works with AWS KMS, GCP KMS,
Azure Key Vault keys, HashiCorp Vault Transit, and offline age keys.

## How it works

SOPS uses envelope encryption. It creates a random data key, encrypts each value with
it, then asks every configured KMS key to encrypt the data key. The encrypted file
stores the encrypted data key next to the values.

```text
                encrypt                                   decrypt at startup
 .env ──▶ sops ──┬─ values ⟵ AES-256-GCM(data key)       sops exec-env
                 └─ data key ─▶ KMS ─▶ wrapped key        ├─ wrapped key ─▶ KMS ─▶ data key
                                     │                   └─ values ─▶ environment ─▶ docker compose
           tulipfarm.sops.env ◀──────┘   (safe to commit)
```

Only someone whom the KMS authorizes can decrypt. You can commit the encrypted file
to a private operations repository. The plaintext exists only in the environment
of the process that SOPS starts.

## Set up

1. Install [SOPS](https://github.com/getsops/sops/releases) on the host.
2. Create `self-host/.sops.yaml` with one rule. Pick your provider from the
   [provider table](#providers). This example lists two master keys, so either one
   can decrypt. Keep an offline age key for emergency recovery:

   ```yaml
   creation_rules:
     - path_regex: \.sops\.(env|json)$
       kms: arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
       age: age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290gq
   ```

3. Encrypt the operator settings, then delete the plaintext:

   ```sh
   cp .env.example tulipfarm.env   # fill it in
   sops encrypt --filename-override tulipfarm.sops.env tulipfarm.env > tulipfarm.sops.env
   rm tulipfarm.env
   ```

   `--filename-override` matches the `.sops.yaml` rule and selects the dotenv format.
   Values are taken literally, so `$` needs no quoting.

4. Start TulipFarm with the decrypted values in the environment. Make sure no
   `self-host/.env` exists:

   ```sh
   sops exec-env tulipfarm.sops.env 'docker compose up -d --build'
   ```

5. To change a value, run `sops edit tulipfarm.sops.env`, then repeat step 4.

The host needs permission to decrypt, but not to encrypt. On cloud hosts, use the
instance identity instead of stored keys.

## Providers

| KMS ([SOPS docs](https://getsops.io/docs/)) | `.sops.yaml` key | Key format | Decrypt permission | Host credentials |
| --- | --- | --- | --- | --- |
| AWS KMS | `kms` | Key ARN | `kms:Decrypt` | Instance role, or `AWS_PROFILE` |
| GCP Cloud KMS | `gcp_kms` | `projects/P/locations/L/keyRings/R/cryptoKeys/K` | `roles/cloudkms.cryptoKeyDecrypter` | Attached service account, or `gcloud auth application-default login` |
| Azure Key Vault | `azure_keyvault` | `https://VAULT.vault.azure.net/keys/KEY/VERSION` | Key Vault Crypto User (unwrap key) | Managed identity, or `az login` |
| Vault / OpenBao Transit | `hc_vault_transit_uri` | `https://vault.example.com:8200/v1/transit/keys/tulipfarm` | `update` on `transit/decrypt/tulipfarm` | `VAULT_ADDR` and `VAULT_TOKEN` |
| age | `age` | `age1…` public key | Holds the private key | `SOPS_AGE_KEY_FILE` |

age needs no cloud account. Use it for a laptop, a single server, or emergency
recovery. Store its private key outside the server, for example in a password manager.

## Encrypt backups

Encrypt `state.json` as you export it, so the plaintext never reaches disk:

```sh
docker compose run --rm -T --no-deps --entrypoint cat setup /setup/state.json \
  | sops encrypt --filename-override state.sops.json > state.sops.json
```

Restore it on a new host:

```sh
sops decrypt state.sops.json \
  | docker compose run --rm -T --no-deps --entrypoint sh setup -c 'umask 077 && cat > /setup/state.json'
```

SOPS is designed for small structured files. For database dumps and volume archives,
use storage that encrypts with your KMS, such as S3 SSE-KMS, GCS CMEK, or Azure
customer-managed keys. Or stream the dump through `age -r age1…`.

## Rotate

| What | How |
| --- | --- |
| KMS master key | Enable automatic rotation in your KMS. Old key versions still decrypt, so you do nothing. |
| SOPS data key | `sops rotate -i tulipfarm.sops.env` creates a new data key and encrypts again. |
| Add or remove a master key | Edit `.sops.yaml`, then run `sops updatekeys tulipfarm.sops.env`. |
| A setting, such as an API key | `sops edit tulipfarm.sops.env`, then restart. |
| TulipFarm encryption keys | See [key rotation](https://tulipfarm.ai/docs/self-hosting/configuration/index.md#rotate-encryption-keys). |

After you remove someone's access, rotate the settings they could read. Old copies
of the encrypted file still open with the old master keys.

## Other ways to use a KMS

- **Kubernetes:** [SOPS with Flux](https://fluxcd.io/flux/guides/mozilla-sops/) or
  [Argo CD](https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/)
  decrypts in the cluster. Or use the [External Secrets Operator](https://tulipfarm.ai/docs/self-hosting/secrets/index.md#kubernetes).
- **Cloud secret managers:** AWS Secrets Manager, Google Secret Manager, and Azure Key
  Vault secrets already encrypt with KMS. Choose your own key (CMEK) if you need
  control. Deliver values as described in [Secret stores](https://tulipfarm.ai/docs/self-hosting/secrets/index.md#cloud-secret-managers).
- **Infisical:** SOPS has no Infisical backend. Keep settings in Infisical
  [secrets](https://tulipfarm.ai/docs/self-hosting/secrets/index.md#infisical), which it encrypts for you. To encrypt a
  backup with your own key, use [Infisical KMS](https://infisical.com/docs/documentation/platform/kms/overview)
  through its encrypt and decrypt API, Infisical Cloud or self-hosted. Turn on
  **Allow Export** when you create the key: you can't change it later, and without it
  you can't move the key to another Infisical instance.
