Skip to main content

Security & Compliance Modules

These modules implement preventive and detective controls for HIPAA compliance and secure CI/CD.

Overview

DocuStack uses a defense-in-depth approach:

LayerModuleTypeScope
PreventiveSCP PoliciesGuardrailsOrganization-wide
DetectiveComplianceMonitoringPer-account
AuthenticationGitHub Actions OIDCCI/CDPer-repo

Compliance

AWS Config recorder and HIPAA conformance pack for continuous compliance monitoring.

Why This Module?

HIPAA requires continuous monitoring of security controls. This module:

  • Records all resource configurations - Complete audit trail
  • Evaluates compliance rules - Automated checks
  • Alerts on violations - SNS notifications
  • Stores history - S3 bucket for compliance evidence

Architecture

┌─────────────────────────────────────────────────────────────┐
│ Compliance Architecture │
├─────────────────────────────────────────────────────────────┤
│ AWS Resources │
│ │ │
│ │ configuration changes │
│ v │
│ ┌──────────────────────┐ │
│ │ AWS Config │ │
│ │ Recorder │ │
│ │ │ │
│ │ - Continuous │ │
│ │ - All resources │ │
│ │ - Global resources │ │
│ └──────┬───────────────┘ │
│ │ │
│ ├──────────► S3 Bucket (Config Snapshots) │
│ │ - Encrypted │
│ │ - Versioned │
│ │ │
│ └──────────► HIPAA Conformance Pack │
│ ┌─────────────────────┐ │
│ │ Config Rules: │ │
│ │ - encrypted-volumes│ │
│ │ - s3-encryption │ │
│ │ - access-keys │ │
│ │ - vpc-flow-logs │ │
│ │ - rds-encryption │ │
│ └──────────┬──────────┘ │
│ │ non-compliant │
│ v │
│ ┌──────────────────────┐ │
│ │ SNS Topic │ │
│ │ (Notifications) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Usage

module "compliance" {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/compliance?ref=v1.0.0"

account_name = "docustack-dev"
sns_topic_arn = aws_sns_topic.compliance_alerts.arn

common_tags = {
Project = "docustack"
Compliance = "HIPAA"
}
}

HIPAA Conformance Pack Rules

RuleHIPAA ControlWhat It Checks
encrypted-volumes§164.312(a)(2)(iv)EBS volumes encrypted
s3-bucket-server-side-encryption-enabled§164.312(a)(2)(iv)S3 buckets encrypted
access-keys-rotated§164.312(d)IAM keys rotated within 90 days
vpc-flow-logs-enabled§164.312(b)VPC Flow Logs enabled
rds-storage-encrypted§164.312(a)(2)(iv)RDS storage encrypted

Viewing Compliance Status

# Get conformance pack status
aws configservice describe-conformance-pack-compliance \
--conformance-pack-name docustack-dev-hipaa-conformance-pack

# List non-compliant resources
aws configservice get-compliance-details-by-config-rule \
--config-rule-name encrypted-volumes \
--compliance-types NON_COMPLIANT

Cost

ComponentMonthly Cost
Configuration items (< 100 resources)~$0.30
Conformance pack evaluations~$1.50
S3 storage~$0.10
Total~$1.90

SCP Policies

Service Control Policies for AWS Organizations to enforce security guardrails.

Why This Module?

SCPs are preventive controls - they block non-compliant actions before they happen:

  • Region restrictions - Prevent resources outside allowed regions
  • Encryption enforcement - Require S3 encryption
  • Audit protection - Prevent CloudTrail tampering
  • Log protection - Prevent log deletion

Architecture

┌─────────────────────────────────────────────────────────────┐
│ SCP Policies Architecture │
├─────────────────────────────────────────────────────────────┤
│ AWS Organization │
│ │ │
│ ├─── Management Account │
│ │ │
│ ├─── Organizational Unit (Workloads) │
│ │ │ │
│ │ ├─── SCP: Region Deny │
│ │ ├─── SCP: Require S3 Encryption │
│ │ ├─── SCP: Protect CloudTrail │
│ │ └─── SCP: Protect Log Archive │
│ │ │
│ │ ├─── Dev Account │
│ │ ├─── Staging Account │
│ │ └─── Prod Account │
│ │ │
│ └─── Organizational Unit (Security) │
│ └─── Security Account │
└─────────────────────────────────────────────────────────────┘

Usage

module "scp_policies" {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/scp-policies?ref=v1.0.0"

organization_id = "o-abc123def456"

target_ou_ids = [
"ou-abc1-12345678", # Workloads OU
]

allowed_regions = [
"us-east-1",
"us-west-2"
]

log_archive_bucket_arns = [
"arn:aws:s3:::docustack-cloudtrail-logs",
"arn:aws:s3:::docustack-config-logs"
]
}

SCP Details

1. Region Deny Policy

Prevents resource creation outside allowed regions:

{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
}
}
}

Exceptions: Global services (IAM, Route53, CloudFront)

2. Require S3 Encryption

Denies unencrypted S3 uploads:

{
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "true"
}
}
}

3. Protect CloudTrail

Prevents audit trail disruption:

{
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail"
],
"Resource": "*"
}

4. Protect Log Archive

Prevents log deletion:

{
"Effect": "Deny",
"Action": [
"s3:DeleteObject",
"s3:DeleteBucket"
],
"Resource": [
"arn:aws:s3:::*-log-archive",
"arn:aws:s3:::*-log-archive/*"
]
}

Testing SCPs

# Test region restriction (should fail)
aws s3 mb s3://test-bucket --region eu-west-1

# Test S3 encryption (should fail)
echo "test" > test.txt
aws s3 cp test.txt s3://my-bucket/test.txt

# Test with encryption (should succeed)
aws s3 cp test.txt s3://my-bucket/test.txt --server-side-encryption AES256

Cost

SCPs are free - no charge for creating or attaching.


GitHub Actions OIDC

IAM OIDC provider and role for GitHub Actions to deploy without long-lived credentials.

Why This Module?

Traditional CI/CD uses long-lived AWS access keys, which:

  • Can be compromised
  • Don't expire automatically
  • Are hard to rotate

OIDC provides:

  • No stored credentials - Tokens issued per workflow run
  • Fine-grained access - Restrict by repo, branch, environment
  • Automatic rotation - Short-lived tokens (15 min - 12 hours)
  • Full audit trail - CloudTrail logs all role assumptions

How It Works

1. GitHub Actions workflow starts
2. GitHub issues OIDC token with claims (repo, branch, actor)
3. Workflow calls AWS STS with AssumeRoleWithWebIdentity
4. AWS validates token against OIDC provider
5. AWS checks trust policy conditions
6. If valid, AWS issues temporary credentials
7. Workflow uses credentials for AWS operations

Usage

module "github_actions_oidc" {
source = "git::git@github.com:docustackapp/docustack-infrastructure-modules.git//modules/github-actions-oidc?ref=v1.0.0"

github_org = "docustackapp"
github_repo = "docustack-mono"
environment = "dev"
allowed_branches = ["main", "develop"]

# Least privilege - restrict to specific resources
lambda_arns = [
"arn:aws:lambda:us-east-1:123456789012:function:docustack-*"
]

ecr_repository_arns = [
"arn:aws:ecr:us-east-1:123456789012:repository/docustack/*"
]

ecs_service_arns = [
"arn:aws:ecs:us-east-1:123456789012:service/docustack-dev/*"
]

max_session_duration = 3600 # 1 hour
}

GitHub Actions Workflow

name: Deploy Services

on:
push:
branches: [main]

permissions:
id-token: write # Required for OIDC
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-dev
aws-region: us-east-1

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build and push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: docustack/api
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

- name: Update ECS service
run: |
aws ecs update-service \
--cluster docustack-dev \
--service api \
--force-new-deployment

Security Best Practices

1. Restrict Branches

# Good: Only main can deploy to production
allowed_branches = ["main"]

# Bad: Any branch can deploy
allowed_branches = ["*"]

2. Use Least Privilege

# Good: Specific resource ARNs
lambda_arns = ["arn:aws:lambda:us-east-1:123456789012:function:docustack-*"]

# Bad: All resources
lambda_arns = ["*"]

3. Separate Roles per Environment

# Dev role - more permissive
module "github_actions_dev" {
environment = "dev"
allowed_branches = ["main", "develop", "feature/*"]
}

# Prod role - restrictive
module "github_actions_prod" {
environment = "prod"
allowed_branches = ["main"] # Only main
}

4. Short Session Duration

# Good: 1 hour max
max_session_duration = 3600

# Bad: 12 hours
max_session_duration = 43200

Troubleshooting

"Not authorized to perform sts:AssumeRoleWithWebIdentity"

Cause: Trust policy doesn't match token claims.

Check:

  1. github_org and github_repo match exactly
  2. Branch is in allowed_branches
  3. OIDC provider ARN is correct
# Debug token claims
- name: Debug OIDC token
run: |
echo "Repository: ${{ github.repository }}"
echo "Ref: ${{ github.ref }}"

"OpenIDConnect provider already exists"

Solution: Use existing provider:

create_oidc_provider = false
oidc_provider_arn = "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"

Cost

IAM resources are free - no direct cost.


HIPAA Compliance Summary

ModuleControls Implemented
Compliance§164.308(a)(1)(ii)(D) Activity review, §164.312(a)(2)(iv) Encryption, §164.312(b) Audit controls
SCP Policies§164.312(a)(2)(iv) Encryption, §164.312(b) Audit controls, §164.312(e)(1) Transmission security
GitHub Actions OIDC§164.312(d) Authentication, §164.312(a)(2)(i) User identification, §164.308(a)(4) Access management