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:
| Layer | Module | Type | Scope |
|---|---|---|---|
| Preventive | SCP Policies | Guardrails | Organization-wide |
| Detective | Compliance | Monitoring | Per-account |
| Authentication | GitHub Actions OIDC | CI/CD | Per-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
| Rule | HIPAA Control | What 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
| Component | Monthly 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:
github_organdgithub_repomatch exactly- Branch is in
allowed_branches - 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
| Module | Controls 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 |