How to Use Terraform for HIPAA Compliance: Requirements, Safeguards, and Examples
Terraform helps you codify HIPAA-aligned technical safeguards so your cloud resources are consistent, reviewable, and auditable. This guide maps core requirements to practical configurations and examples you can adapt for HIPAA infrastructure automation across environments.
The patterns below emphasize least privilege, CloudTrail logging, cryptographic integrity checks, AWS KMS encryption, Multi-Factor Authentication, and encrypted Terraform backends. Examples target AWS but the principles apply to any supported provider.
Implement Access Control Measures
Access control for ePHI hinges on least privilege, network segmentation, and clear separation of duties. Use IAM roles and policies to scope actions to specific cloud resources, restrict access paths, and require TLS for data access.
Terraform pattern: least-privilege ePHI access
The example below grants read-only access to a designated ePHI bucket, only over TLS, and only for objects tagged as ePHI. This enforces ePHI access control with clear, testable intent.
resource "aws_iam_policy" "ephi_readonly" {
name = "ephi-readonly"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "ListBucketOverTLS"
Effect = "Allow"
Action = ["s3:ListBucket"]
Resource = "arn:aws:s3:::ephi-bucket"
Condition = {
Bool = { "aws:SecureTransport" = "true" }
}
},
{
Sid = "GetObjectsTaggedEPHIOverTLS"
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = "arn:aws:s3:::ephi-bucket/*"
Condition = {
Bool = { "aws:SecureTransport" = "true" }
StringEquals = { "s3:ExistingObjectTag/classification" = "ephi" }
}
}
]
})
}
Implementation tips
- Scope IAM actions to ARNs, not wildcards; prefer explicit resource lists or tags.
- Use security groups and subnets to segment admin planes from data planes.
- Continuously test policy impact with automated access reviews and drift detection.
Enable Audit Controls
HIPAA expects you to record access and administrative activity. Centralize CloudTrail logging across all regions, enable log file validation, and stream to CloudWatch for alerting and retention.
Terraform pattern: multi-Region CloudTrail with encryption
resource "aws_s3_bucket" "ct_logs" {
bucket = "hipaa-ct-logs-123456"
}
resource "aws_s3_bucket_versioning" "ct" {
bucket = aws_s3_bucket.ct_logs.id
versioning_configuration { status = "Enabled" }
}
resource "aws_kms_key" "ct" {
description = "KMS for CloudTrail logs"
enable_key_rotation = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "ct" {
bucket = aws_s3_bucket.ct_logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.ct.arn
}
}
}
resource "aws_cloudwatch_log_group" "trail" {
name = "/hipaa/cloudtrail"
retention_in_days = 365
}
resource "aws_iam_role" "ct_to_cw" {
name = "cloudtrail-to-cloudwatch"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Principal = { Service = "cloudtrail.amazonaws.com" },
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "ct_to_cw" {
role = aws_iam_role.ct_to_cw.id
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = ["logs:CreateLogStream", "logs:PutLogEvents"],
Resource = "${aws_cloudwatch_log_group.trail.arn}:*"
}]
})
}
resource "aws_cloudtrail" "org" {
name = "org-trail"
s3_bucket_name = aws_s3_bucket.ct_logs.id
is_multi_region_trail = true
include_global_service_events = true
enable_log_file_validation = true
cloud_watch_logs_group_arn = aws_cloudwatch_log_group.trail.arn
cloud_watch_logs_role_arn = aws_iam_role.ct_to_cw.arn
}
- Enable data events for S3 and Lambda where ePHI may flow to capture object-level activity.
- Set retention aligned to policy; use immutable storage where required.
- Alert on high-risk events (KMS key changes, policy updates, failed Console logins).
Ensure Data Integrity
Data integrity safeguards prove that ePHI was not altered or destroyed improperly. Use versioning, immutability, and cryptographic integrity checks to detect and prevent tampering.
Ready to simplify HIPAA compliance?
Join thousands of organizations that trust Accountable to manage their compliance needs.
Terraform pattern: versioning and object lock
resource "aws_s3_bucket" "ephi" {
bucket = "ephi-data-123456"
object_lock_enabled = true
}
resource "aws_s3_bucket_versioning" "ephi" {
bucket = aws_s3_bucket.ephi.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_object_lock_configuration" "ephi_lock" {
bucket = aws_s3_bucket.ephi.id
rule {
default_retention {
mode = "COMPLIANCE"
days = 30
}
}
}
- Turn on CloudTrail log file validation to verify log integrity end-to-end.
- Use checksums and signed artifacts for deployments; validate hashes in CI/CD.
- Continuously monitor resource drift; fail builds when unauthorized changes appear.
Apply Encryption Methods
Encrypt ePHI at rest with AWS KMS encryption and enforce TLS in transit. Prefer customer-managed KMS keys with rotation and tightly scoped key policies.
Terraform pattern: KMS + default encryption
resource "aws_kms_key" "ephi" {
description = "KMS CMK for ePHI"
enable_key_rotation = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "ephi" {
bucket = aws_s3_bucket.ephi.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.ephi.arn
}
}
}
resource "aws_ebs_encryption_by_default" "on" {
enabled = true
}
resource "aws_db_instance" "ephi_pg" {
identifier = "ephi-db"
engine = "postgres"
instance_class = "db.m6g.large"
allocated_storage = 100
storage_encrypted = true
kms_key_id = aws_kms_key.ephi.arn
publicly_accessible = false
multi_az = true
backup_retention_period = 7
deletion_protection = true
}
Enforce TLS for apps
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = aws_acm_certificate.app.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
Configure Authentication
Use strong identity controls for administrators and workloads. Require Multi-Factor Authentication for privileged roles and restrict session duration. Where possible, federate with an enterprise IdP.
Terraform pattern: require MFA to assume role
resource "aws_iam_role" "ephi_operator" {
name = "ephi-operator"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Action = "sts:AssumeRole",
Principal = {
AWS = "arn:aws:iam::123456789012:role/Enterprise-SSO-Admins"
},
Condition = {
Bool = { "aws:MultiFactorAuthPresent" = "true" }
}
}]
})
}
resource "aws_iam_role_policy_attachment" "ephi_access" {
role = aws_iam_role.ephi_operator.name
policy_arn = aws_iam_policy.ephi_readonly.arn
}
- Use short session lifetimes and require justification tags for elevated access.
- Rotate access keys for non-interactive principals; prefer role assumption over long-lived keys.
- Audit role assumptions in CloudTrail and alert on anomalies.
Manage Terraform State Securely
Terraform state can contain resource metadata and, in some cases, sensitive values. Store it in encrypted Terraform backends, lock it during changes, and limit who can read it.
Terraform pattern: S3 + DynamoDB with KMS
resource "aws_kms_key" "state" {
description = "KMS for Terraform state"
enable_key_rotation = true
}
resource "aws_s3_bucket" "tf_state" {
bucket = "hipaa-tf-state-123456"
}
resource "aws_s3_bucket_versioning" "tf_state" {
bucket = aws_s3_bucket.tf_state.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" {
bucket = aws_s3_bucket.tf_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.state.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "tf_state" {
bucket = aws_s3_bucket.tf_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "tf_lock" {
name = "tf-state-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute { name = "LockID"; type = "S" }
server_side_encryption { enabled = true }
}
# backend configuration (in your root module)
terraform {
backend "s3" {
bucket = "hipaa-tf-state-123456"
key = "prod/global/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-state-lock"
encrypt = true
}
}
- Restrict bucket access to CI/CD roles; deny public access and cross-account reads by default.
- Avoid placing secrets in state; fetch at apply time from a secret manager.
- Use workspaces or separate keys per environment to reduce blast radius.
Use HIPAA Compliance Modules
Package repeatable guardrails into modules so teams can launch compliant foundations quickly. A baseline module can provision CloudTrail logging, KMS keys, VPC controls, and opinionated defaults for encryption and retention.
Example: opinionated baseline module
# modules/hipaa-baseline/main.tf
module "kms_ephi" {
source = "../kms-key"
alias = "ephi"
}
module "cloudtrail" {
source = "../cloudtrail"
kms_key_arn = module.kms_ephi.arn
retention_days = 365
multi_region = true
log_validation = true
}
module "network" {
source = "../vpc-hardened"
# private subnets, restricted SGs, flow logs, etc.
}
Ready to simplify HIPAA compliance?
Join thousands of organizations that trust Accountable to manage their compliance needs.