| 1 | import * as GCP from "alchemy/GCP"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | |
| 4 | /** |
| 5 | * The container for the key. Cloud KMS has no key ring delete API, so |
| 6 | * `alchemy destroy` only forgets the ring; an empty ring costs nothing. |
| 7 | */ |
| 8 | export const Keys = GCP.KMS.KeyRing("Keys", {}); |
| 9 | |
| 10 | /** |
| 11 | * The symmetric key the service encrypts with. Key material never leaves |
| 12 | * KMS — the service only ever sees ciphertext and plaintext. |
| 13 | * |
| 14 | * KMS keeps destroyed keys for at least a day, so `alchemy destroy` |
| 15 | * *releases* this key (schedules every version for destruction and labels |
| 16 | * it `alchemy-released`). A deploy that asks for the same key id reclaims |
| 17 | * it and mints a fresh primary version. |
| 18 | */ |
| 19 | export const DataKey = Effect.gen(function* () { |
| 20 | const keys = yield* Keys; |
| 21 | return yield* GCP.KMS.CryptoKey("DataKey", { |
| 22 | keyRing: keys.name, |
| 23 | purpose: "ENCRYPT_DECRYPT", |
| 24 | }); |
| 25 | }); |
| 26 | |
| 27 | /** |
| 28 | * The key callers must present in `x-api-key`. Only the service reads it, |
| 29 | * and only at request time. |
| 30 | * |
| 31 | * Alchemy creates the secret; adding the *version* that holds the value |
| 32 | * is an operator step: |
| 33 | * |
| 34 | * ```sh |
| 35 | * printf 'my-key' | gcloud secrets versions add "$SECRET_ID" --data-file=- |
| 36 | * ``` |
| 37 | */ |
| 38 | export const ApiKey = GCP.SecretManager.Secret("ApiKey", {}); |