# AWS KMS drop-in

`seekrit-kms` is a small gateway that speaks the **AWS KMS JSON API** on a local
endpoint. Point an AWS SDK's KMS endpoint at it and your existing KMS code works
unchanged — but every operation runs **in the gateway process**, against a
[managed key](/docs/concepts/kms) fetched as a wrapped grant and unwrapped
locally. The seekrit API never sees your plaintext, your data keys, or the key
material. It's a drop-in KMS that your provider literally cannot read.

> **Note:** This is the envelope-encryption surface of KMS — the ~90% of real usage: S3 and DynamoDB client-side encryption, Tink, and any `GenerateDataKey` pattern. Asymmetric `Sign`/`Verify`, `GenerateMac`, and key-policy management are not part of this gateway today.

## Run the gateway

Give it a [service token](/docs/guides/service-tokens) that holds grants on the
managed keys you want to use. It binds loopback by default.

```bash
export SEEKRIT_TOKEN=skt_…
seekrit-kms                 # listening on 127.0.0.1:9911
```

As a container (bind `0.0.0.0` so the workload can reach it):

```bash
docker run --rm \
  -e SEEKRIT_TOKEN=skt_… -e SEEKRIT_KMS_LISTEN=0.0.0.0:9911 \
  -p 9911:9911 seekritdev/kms
```

Startup is **fail-closed**: a missing or bad token, or an unreachable API, stops
it from starting rather than serving a broken endpoint.

## Point your SDK at it

Only the endpoint changes. The SDK still signs requests with its own credentials
(dummy values are fine) — the gateway ignores the signature; the service token is
the real authority.

```bash
# AWS CLI
export AWS_ENDPOINT_URL_KMS=http://127.0.0.1:9911
export AWS_ACCESS_KEY_ID=ignored AWS_SECRET_ACCESS_KEY=ignored AWS_REGION=us-east-1

aws kms generate-data-key --key-id billing --key-spec AES_256
```

```python
# boto3
import boto3
kms = boto3.client("kms", endpoint_url="http://127.0.0.1:9911")

# Envelope encryption:
dk = kms.generate_data_key(KeyId="billing", KeySpec="AES_256")
data_key   = dk["Plaintext"]        # use locally, then discard
wrapped    = dk["CiphertextBlob"]   # store beside your ciphertext
# ...later...
data_key = kms.decrypt(CiphertextBlob=wrapped)["Plaintext"]

# Direct encrypt/decrypt:
ct = kms.encrypt(KeyId="billing", Plaintext=b"4111 1111 1111 1111")["CiphertextBlob"]
pt = kms.decrypt(CiphertextBlob=ct)["Plaintext"]
```

```js
// @aws-sdk/client-kms (v3)
import { KMSClient, GenerateDataKeyCommand } from "@aws-sdk/client-kms";
const kms = new KMSClient({ endpoint: "http://127.0.0.1:9911", region: "us-east-1" });
const dk = await kms.send(new GenerateDataKeyCommand({ KeyId: "billing", KeySpec: "AES_256" }));
```

## Referring to keys

A KMS `KeyId` resolves to a managed key by **name** or **id** — `billing`,
`alias/billing`, the `kms_…` id, or an ARN the gateway returned all map to the
same key. Create keys the usual way (`seekrit kms create --name billing --purpose
encrypt`, or the dashboard) and grant the gateway's token access to them.

## Encryption context

AWS `EncryptionContext` is bound as additional authenticated data, exactly like
KMS: `Decrypt` must supply the same context or it fails. It's canonicalized
order-independently, so `{tenant, field}` and `{field, tenant}` are equivalent.

## Supported operations

| Operation | Notes |
| --- | --- |
| `Encrypt` / `Decrypt` | AES-256-GCM; encryption context bound as AAD |
| `GenerateDataKey` / `…WithoutPlaintext` | `AES_256`, `AES_128`, or `NumberOfBytes` |
| `DescribeKey`, `ListKeys` | metadata for the keys the token can see |
| `GenerateRandom` | CSPRNG bytes |

> **Warning:** You can't take over ciphertext that real AWS KMS produced. The gateway holds **seekrit** keys, not your AWS keys — so it's a drop-in for new usage or a re-encrypt migration, not a transparent takeover of an existing encrypted corpus. Ciphertext is interchangeable only within seekrit.

## Security

The gateway is a plaintext boundary, like the [agent egress
proxy](/docs/guides/agent-proxy) and the Kubernetes sidecar: it holds a token and
produces data keys and plaintext. It performs **no AWS SigV4 verification**, so
treat reachability to its port as access to everything the token can decrypt —
keep it on loopback or a trusted local network, scoped to a token that grants
only the keys that workload needs. A [disabled key](/docs/concepts/kms) stops
working through the gateway within one refresh interval.
