Configure BYOK with AWS KMS
Bring Your Own Key (BYOK) lets you use your own AWS KMS key as the master encryption key for FreeState. You maintain full control: FreeState cannot access your state data if you revoke KMS access.
Note on placeholder values: Code examples use 123456789012 as the FreeState AWS account ID placeholder. Replace this with the actual FreeState AWS account ID shown in your organization's Encryption settings, or contact support@freestate.cloud.
Architecture
ββββββββββββββββββββββββββ Cross-Account ββββββββββββββββββββββββββ β Your AWS Account βββββββββββββββββββββββββββββ FreeState AWS Account β β β Trust β β β βββββββββββββββββββ β β βββββββββββββββββββ β β β Your KMS Key β β β β API Servers β β β β (KEK) β β Encrypt/Decrypt DEKs β β β β β βββββββββββββββββββ βββββββββββββββββββββββββββββ βββββββββββββββββββ β β β β β β You control access β β FreeState calls KMS β ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
Prerequisites
- AWS account with KMS permissions (
kms:CreateKey,kms:PutKeyPolicy) - FreeState Organization Owner role
- Terraform or AWS CLI installed
- Encryption already enabled β see Enable Encryption guide
Step 1: Create KMS Key with Terraform
Create a new directory freestate-byok/ with the following files:
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_caller_identity" "current" {}
resource "aws_kms_key" "freestate_byok" {
description = "FreeState BYOK Master Key"
deletion_window_in_days = 30
enable_key_rotation = true
# Cross-account policy: your account manages, FreeState encrypts/decrypts
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "Allow FreeState Cross-Account Access"
Effect = "Allow"
Principal = {
# Replace 123456789012 with the actual FreeState AWS account ID
# from Portal > Settings > Encryption
AWS = "arn:aws:iam::123456789012:role/freestate-api-task-role"
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:DescribeKey"
]
Resource = "*"
Condition = {
StringEquals = {
"kms:EncryptionContext:organization_id" = var.freestate_org_id
}
}
}
]
})
tags = {
Name = "FreeState BYOK"
ManagedBy = "terraform"
}
}
resource "aws_kms_alias" "freestate_byok" {
name = "alias/freestate-byok-${var.org_name}"
target_key_id = aws_kms_key.freestate_byok.key_id
}
output "kek_arn" {
value = aws_kms_key.freestate_byok.arn
description = "Provide this ARN to FreeState Portal"
}
output "kek_id" {
value = aws_kms_key.freestate_byok.key_id
}variables.tf
variable "freestate_org_id" {
description = "Your FreeState organization ID (UUID)"
type = string
}
variable "org_name" {
description = "Your organization name (for alias)"
type = string
}Apply:
terraform init
terraform apply \
-var="freestate_org_id=550e8400-e29b-41d4-a716-446655440000" \
-var="org_name=acme-corp"
# Expected output:
# Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
# Outputs:
# kek_arn = "arn:aws:kms:us-east-1:999988887777:key/12345678-..."
# kek_id = "12345678-1234-1234-1234-123456789012"Copy the kek_arn output β you'll need it in Step 3.
Step 2: Find Your Organization ID
- Log in to the FreeState Portal
- Navigate to Settings β General
- Copy your Organization ID (UUID format)
Step 3: Configure BYOK in Portal
- Navigate to Settings β Encryption
- Click Configure BYOK
- Select provider: AWS KMS
- Paste your KMS key ARN from Step 1
- Click Validate Access
FreeState performs a test encrypt/decrypt to verify cross-account access before saving the configuration.
Validation checks:
- β KEK is accessible from FreeState's AWS account
- β Encrypt operation succeeds with your organization context
- β Decrypt operation succeeds with your organization context
- β Encryption context matches your organization ID
Step 4: Rotate to BYOK
- Review the rotation impact notice
- Click Rotate to BYOK
- Wait for rotation to complete (typically 5β10 seconds for 1,000 workspaces)
During rotation, all workspace DEKs are atomically re-encrypted with your BYOK KEK. State operations continue without interruption β zero downtime.
Verification
Portal UI
- Encryption status: BYOK Enabled (blue badge)
- Key type: Customer-Managed (AWS KMS)
- KEK ARN: your key ARN
API
curl -H "Authorization: Bearer $TOKEN" \
https://api.freestate.cloud/api/v1/organizations/$ORG_ID/encryption/status
# Response:
{
"organization_id": "11111111-2222-3333-4444-555555555555",
"encryption_enabled": true,
"byok_enabled": true,
"kms_provider": "aws-kms",
"key_type": "byok",
"kek_id": "arn:aws:kms:us-east-1:999988887777:key/12345678-...",
"encryption_enabled_at": "2026-01-03T12:15:00Z"
}CloudTrail Logs (Your AWS Account)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Encrypt \
--max-items 5
# You should see Encrypt/Decrypt events from the FreeState roleSecurity Considerations
You control access
Revoke FreeState's IAM access β workspaces become read-only immediately. Delete your key β state data permanently unrecoverable (by design).
Full audit trail
All KMS operations appear in your CloudTrail logs. You can monitor exactly when FreeState accesses your key and for which organization context.
Encryption context
Every KMS call includes organization_id in the encryption context. This cryptographically prevents your key from being misused across organizations.
AWS KMS Cost Considerations
AWS KMS pricing is usage-based and varies by region. See the official AWS KMS pricing page for current details. FreeState caches decrypted DEKs to minimize KMS API calls, reducing the per-operation cost compared to calling KMS on every state read.
Any cost estimates in documentation are illustrative only. Monitor your actual KMS usage in AWS Cost Explorer and CloudWatch metrics.
Revoking BYOK
To revert to FreeState-managed encryption:
- Navigate to Settings β Encryption
- Click Remove BYOK
- Confirm: FreeState will rotate to a new managed KEK
- Wait for rotation (same process, reverse direction)
Troubleshooting
βKEK validation failed: AccessDeniedβ
- Verify the key policy includes the correct FreeState role ARN
- Confirm the encryption context condition matches your org ID
- Ensure
kms:Encrypt,kms:Decrypt,kms:DescribeKeyare granted
βRotation failed: partial completionβ
- Check the rotation log in the Portal under Settings β Encryption β History
- Retry via API:
POST /api/v1/organizations/$ORG_ID/encryption/rotate - The old KEK is retained for rollback β contact support@freestate.cloud if needed