Check your KMS permissions
Prove with real AWS KMS calls that your self-hosted server can seal secrets, that a wrong context fails, and that an unauthorised principal is denied.
A self-hosted server seals each secret with a data key from AWS KMS. The server needs a reachable KMS key and the right permissions. Run these checks once in your chosen region, before you store real secrets. Use a test key. Do not use production data.
You need the AWS CLI and two principals:
- The app role. The server runs as this role. It can use the key.
- A denied principal. It has no
kms:Decryptpermission on the key.
Set these values first:
export KEY_ID=<your test key ID or ARN>
export AWS_REGION=<your chosen region>
What the server sends to KMS
The server sends an encryption context of two values, scope and name. KMS binds the data key to that context. A decrypt call with a different context must fail.
Run the checks
Run the first three commands as the app role.
-
Confirm the key is reachable. The server makes this call at boot.
aws kms describe-key --key-id "$KEY_ID" --query KeyMetadata.KeyStateExpect
"Enabled". -
Make a data key with a context, and keep the wrapped copy.
aws kms generate-data-key --key-id "$KEY_ID" --key-spec AES_256 \ --encryption-context scope=probe,name=probe \ --query CiphertextBlob --output text | base64 -d > wrapped.bin -
Decrypt with the same context. Then decrypt with a changed context.
aws kms decrypt --ciphertext-blob fileb://wrapped.bin \ --encryption-context scope=probe,name=probe --query KeyId --output text aws kms decrypt --ciphertext-blob fileb://wrapped.bin \ --encryption-context scope=probe,name=other --query KeyId --output textThe first call prints the key ARN. The second call fails with
InvalidCiphertextException. -
Switch to the denied principal. Decrypt with the correct context.
aws kms decrypt --ciphertext-blob fileb://wrapped.bin \ --encryption-context scope=probe,name=probeExpect
AccessDeniedException. If the call succeeds, fix the key policy and IAM policies before you go live.
Delete wrapped.bin when you finish.
Record the evidence
Copy this table. Fill in one row for each check.
| Command | Principal | Expected | Actual | Date |
|---|---|---|---|---|
describe-key |
App role | Enabled |
||
generate-data-key |
App role | Succeeds | ||
decrypt, same context |
App role | Succeeds | ||
decrypt, changed context |
App role | InvalidCiphertextException |
||
decrypt, same context |
Denied principal | AccessDeniedException |
If an error code differs from the table, record the real code. Do not mark the row as passed.
If you cannot reach KMS
The server fails closed. It keeps no plaintext key and no fallback. If a check fails, fix the key policy or the role. Then run the check again.
Last updated