Configure BYOK with AWS KMS

⏱ Time to complete: 15 minutesπŸ‘€ Requires: AWS account + Organization Owner role🎯 Audience: Security engineers, Compliance teams

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

  1. Log in to the FreeState Portal
  2. Navigate to Settings β†’ General
  3. Copy your Organization ID (UUID format)

Step 3: Configure BYOK in Portal

  1. Navigate to Settings β†’ Encryption
  2. Click Configure BYOK
  3. Select provider: AWS KMS
  4. Paste your KMS key ARN from Step 1
  5. 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

  1. Review the rotation impact notice
  2. Click Rotate to BYOK
  3. 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 role

Security 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:

  1. Navigate to Settings β†’ Encryption
  2. Click Remove BYOK
  3. Confirm: FreeState will rotate to a new managed KEK
  4. Wait for rotation (same process, reverse direction)

Troubleshooting

β€œKEK validation failed: AccessDenied”

  1. Verify the key policy includes the correct FreeState role ARN
  2. Confirm the encryption context condition matches your org ID
  3. Ensure kms:Encrypt, kms:Decrypt, kms:DescribeKey are granted

β€œRotation failed: partial completion”

  1. Check the rotation log in the Portal under Settings β†’ Encryption β†’ History
  2. Retry via API: POST /api/v1/organizations/$ORG_ID/encryption/rotate
  3. The old KEK is retained for rollback β€” contact support@freestate.cloud if needed

Next Steps