Audit Logging
Overview
Comprehensive audit logging is critical for HIPAA compliance, security monitoring, and forensic investigation. MenoTime logs all API calls, database access, and system activity in accordance with HIPAA Security Rule requirements.
Golden Rule: Log everything, secure logs, retain for required periods, and monitor for suspicious activity.
Logging Architecture
MenoTime uses a multi-layer logging strategy:
┌─────────────────────┐
│ AWS API Activity │ ──→ CloudTrail
├─────────────────────┤
│ Application Events │ ──→ CloudWatch Logs
├─────────────────────┤
│ Database Queries │ ──→ RDS Audit Logs
├─────────────────────┤
│ Container Logs │ ──→ ECS CloudWatch
├─────────────────────┤
│ Network Activity │ ──→ VPC Flow Logs
└─────────────────────┘
↓
┌──────────────┐
│ Log Analysis │ ──→ Alerts
│ & Monitoring │ ──→ Dashboards
└──────────────┘
↓
┌──────────────┐
│ Long-term │
│ Retention │
└──────────────┘
CloudTrail Configuration
CloudTrail logs all AWS API calls and management events. It is the authoritative log source for "who did what when" in AWS.
CloudTrail Setup
# terraform/cloudtrail.tf
resource "aws_s3_bucket" "cloudtrail_logs" {
bucket = "menotime-cloudtrail-logs"
tags = {
Name = "MenoTime CloudTrail Logs"
Classification = "RESTRICTED"
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "cloudtrail_logs" {
bucket = aws_s3_bucket.cloudtrail_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enforce encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "cloudtrail_logs" {
bucket = aws_s3_bucket.cloudtrail_logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.cloudtrail_key.arn
}
}
}
# Enable versioning for immutability
resource "aws_s3_bucket_versioning" "cloudtrail_logs" {
bucket = aws_s3_bucket.cloudtrail_logs.id
versioning_configuration {
status = "Enabled"
}
}
# Bucket policy: Allow CloudTrail to write logs
resource "aws_s3_bucket_policy" "cloudtrail_logs" {
bucket = aws_s3_bucket.cloudtrail_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSCloudTrailAclCheck"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:GetBucketAcl"
Resource = aws_s3_bucket.cloudtrail_logs.arn
},
{
Sid = "AWSCloudTrailWrite"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cloudtrail_logs.arn}/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
}
}
},
{
Sid = "DenyUnencryptedObjectUploads"
Effect = "Deny"
Principal = "*"
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cloudtrail_logs.arn}/*"
Condition = {
StringNotEquals = {
"s3:x-amz-server-side-encryption" = "aws:kms"
}
}
}
]
})
}
# CloudTrail configuration (all events)
resource "aws_cloudtrail" "menotime" {
name = "menotime-cloudtrail"
s3_bucket_name = aws_s3_bucket.cloudtrail_logs.id
include_global_service_events = true
is_multi_region_trail = true
is_organization_trail = false
enable_log_file_validation = true
kms_key_id = aws_kms_key.cloudtrail_key.arn
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3:::*/"]
}
data_resource {
type = "AWS::Lambda::Function"
values = ["arn:aws:lambda:*:ACCOUNT_ID:function/*"]
}
}
# Log insights queries to CloudWatch
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*"
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch.arn
depends_on = [aws_s3_bucket_policy.cloudtrail_logs]
tags = {
Name = "menotime-cloudtrail"
}
}
# CloudWatch log group for CloudTrail
resource "aws_cloudwatch_log_group" "cloudtrail" {
name = "/aws/cloudtrail/menotime"
retention_in_days = 365
}
CloudTrail Events Logged
CloudTrail logs all AWS API calls, including:
Authentication Events: - Console login attempts (successful and failed) - API key creation and deletion - MFA device changes - Password changes
Data Access Events: - S3 object access (GetObject, PutObject, DeleteObject) - RDS database logins and queries - Lambda function invocations - DynamoDB item access
Administrative Events: - IAM user creation/deletion - Security group changes - VPC modifications - RDS instance modifications - Secrets Manager access
Example CloudTrail Event (JSON):
{
"eventVersion": "1.08",
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDACKCEVSQ6C2EXAMPLE",
"arn": "arn:aws:iam::123456789012:user/john.developer",
"accountId": "123456789012",
"userName": "john.developer"
},
"eventTime": "2024-01-15T14:35:22Z",
"eventSource": "rds.amazonaws.com",
"eventName": "DescribeDBInstances",
"awsRegion": "us-east-1",
"sourceIPAddress": "203.0.113.42",
"userAgent": "aws-cli/2.13.0",
"requestParameters": {
"dBInstanceIdentifier": "menotime-prod"
},
"responseElements": null,
"requestId": "b48e7d62-b405-4c4e-8d3b-1234567890ab",
"eventID": "1234567890abcdef",
"eventType": "AwsApiCall",
"recipientAccountId": "123456789012"
}
Querying CloudTrail
Using AWS CLI:
# Find all failed login attempts in the last 7 days
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin \
--max-results 50 \
--region us-east-1
# Find all RDS modifications
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventSource,AttributeValue=rds.amazonaws.com \
--max-results 50
# Find all API calls by a specific user
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=john.developer \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-31T23:59:59Z
Using CloudTrail Insights (advanced analysis):
CloudTrail Insights automatically detects unusual API activity patterns.
# List all Insights events (anomalies detected)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Insight \
--max-results 50
CloudWatch Logs Configuration
CloudWatch Logs aggregates application, system, and container logs from various sources.
Log Groups and Retention
# terraform/cloudwatch-logs.tf
resource "aws_cloudwatch_log_group" "app_logs" {
name = "/menotime/prod/application"
retention_in_days = 365
kms_key_id = aws_kms_key.logs_key.arn
tags = {
Name = "MenoTime Production Application Logs"
Environment = "prod"
Classification = "RESTRICTED"
}
}
resource "aws_cloudwatch_log_group" "ecs_logs" {
name = "/ecs/menotime-prod"
retention_in_days = 365
kms_key_id = aws_kms_key.logs_key.arn
}
resource "aws_cloudwatch_log_group" "database_logs" {
name = "/rds/menotime-prod"
retention_in_days = 365
kms_key_id = aws_kms_key.logs_key.arn
}
resource "aws_cloudwatch_log_group" "security_logs" {
name = "/menotime/security"
retention_in_days = 730 # 2 years for security events
tags = {
Name = "MenoTime Security Logs"
Classification = "RESTRICTED"
}
}
Log Events to Capture
Application Logs: - User authentication (login/logout) - Data access (read/write operations) - API request/response - Error conditions and exceptions - PHI access events (flagged with "PHI_ACCESS")
Example Application Log Entry:
{
"timestamp": "2024-01-15T14:35:22.123Z",
"level": "INFO",
"service": "menotime-api",
"user_id": "user-12345",
"action": "PHI_ACCESS",
"resource": "patient-record-7890",
"resource_type": "patient_data",
"operation": "read",
"status": "success",
"ip_address": "203.0.113.42",
"request_id": "req-abcdef123"
}
Security-Relevant Events: - Authentication attempts (success and failure) - Authorization decisions (access granted/denied) - Configuration changes - Credential creation/deletion - Role assumption - MFA events
Log Analysis and Searches
CloudWatch Insights Query Language allows searching and analyzing logs:
Example 1: PHI Access Audit
fields @timestamp, user_id, patient_id, operation
| filter action = "PHI_ACCESS"
| stats count() by user_id, operation
Example 2: Failed Login Attempts
fields @timestamp, user_email, source_ip
| filter event_type = "authentication_failed"
| stats count() as attempt_count by source_ip, user_email
| filter attempt_count > 5
Example 3: After-Hours Access to Production
fields @timestamp, user_id, environment
| filter environment = "prod"
AND (strftime(@timestamp, "%H:%M:%S") >= "18:00:00"
OR strftime(@timestamp, "%H:%M:%S") <= "06:00:00")
| stats count() by user_id
Example 4: Bulk Data Export
fields @timestamp, user_id, record_count
| filter operation = "export"
AND record_count > 100
| stats count() as export_count by user_id
RDS Audit Logging (pgAudit)
PostgreSQL audit logging (pgAudit) tracks all database queries and modifications.
pgAudit Configuration
-- Install pgAudit extension
CREATE EXTENSION IF NOT EXISTS pgaudit;
-- Configure audit logging
ALTER SYSTEM SET pgaudit.log = 'ALL';
ALTER SYSTEM SET pgaudit.log_parameter = on;
ALTER SYSTEM SET pgaudit.log_statement_once = off;
-- Restart PostgreSQL for changes to take effect
-- (RDS: Reboot the DB instance)
-- Verify pgAudit is active
SHOW pgaudit.log;
What pgAudit Logs
All Query Types: - SELECT statements (reads) - INSERT statements (creates) - UPDATE statements (modifications) - DELETE statements (deletions) - DDL statements (schema changes)
Example pgAudit Log Entry:
2024-01-15 14:35:22 UTC [1234]: user=menotime_app,db=menotime_prod,app=psql,client=10.0.1.50
AUDIT: SESSION,1,1,SELECT,SELECT * FROM patients WHERE id = $1;,<not logged>
2024-01-15 14:35:23 UTC [1234]: user=menotime_app,db=menotime_prod
AUDIT: SESSION,1,2,UPDATE,UPDATE patients SET severity_score = $1 WHERE id = $2;,<not logged>
Querying RDS Audit Logs
-- View recent audit entries
SELECT * FROM pgaudit.audit_log
WHERE event_time >= NOW() - INTERVAL '1 hour'
ORDER BY event_time DESC
LIMIT 100;
-- Audit all SELECT statements accessing the patients table
SELECT * FROM pgaudit.audit_log
WHERE command_text LIKE '%patients%'
AND statement_type = 'SELECT'
ORDER BY event_time DESC;
-- Audit modifications by date
SELECT event_time, user_name, command_text
FROM pgaudit.audit_log
WHERE statement_type IN ('INSERT', 'UPDATE', 'DELETE')
AND event_time >= '2024-01-15 14:00'
AND event_time <= '2024-01-15 16:00'
ORDER BY event_time;
Exporting RDS Logs to CloudWatch
# terraform/rds-logs.tf
resource "aws_db_instance" "menotime_prod" {
# ... other configuration ...
# Enable log exports
enabled_cloudwatch_logs_exports = ["postgresql"]
}
# CloudWatch log group for RDS
resource "aws_cloudwatch_log_group" "rds_postgresql" {
name = "/aws/rds/instance/menotime-prod/postgresql"
retention_in_days = 365
kms_key_id = aws_kms_key.logs_key.arn
}
VPC Flow Logs
VPC Flow Logs capture network traffic metadata (source/destination IPs, ports, byte counts).
VPC Flow Logs Configuration
# terraform/vpc-flow-logs.tf
resource "aws_flow_log" "rds_network" {
name = "menotime-rds-flow-logs"
iam_role_arn = aws_iam_role.vpc_flow_logs.arn
log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
traffic_type = "REJECT" # Log rejected connections (potential attacks)
vpc_id = aws_vpc.menotime.id
tags = {
Name = "menotime-rds-flow-logs"
}
}
resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
name = "/aws/vpc/flowlogs/menotime"
retention_in_days = 90
}
VPC Flow Log Analysis
# Identify unusual network connections
fields @timestamp, srcip, dstip, dstport
| filter dstport > 1024
| stats count() as connection_count by srcip, dstport
| filter connection_count > 100
GuardDuty Integration
AWS GuardDuty is a managed threat detection service that monitors CloudTrail, DNS logs, and VPC Flow Logs for malicious activity.
GuardDuty Configuration
# Enable GuardDuty (via AWS Console or CLI)
aws guardduty create-detector \
--enable \
--finding-publishing-frequency FIFTEEN_MINUTES
GuardDuty Findings
GuardDuty identifies threats and generates findings:
Examples of GuardDuty Findings:
- CryptoCurrency Activity: Bitcoin mining or other crypto activity detected
- Reconnaissance: Port scanning or DNS enumeration detected
- Resource Compromise: Malware detected or compromised instance identified
- Backdoor: Unexpected connections to external IPs
- Unusual API Calls: Unusual patterns of AWS API calls
Responding to GuardDuty Findings
High Severity Finding Example:
Finding: An EC2 instance is making unusual outbound DNS queries
Finding Type: Trojan.EC2/DnsRequest.C
Instance: i-1234567890abcdef0
Region: us-east-1
Time: 2024-01-15 14:35:22 UTC
Severity: High
RESPONSE ACTIONS:
1. Isolate the instance from the network
2. Create snapshot for forensic analysis
3. Investigate process that initiated DNS queries
4. Check for signs of malware/rootkit
5. Determine if data was exfiltrated
Log Retention and Compliance
Retention Periods
| Log Type | Retention Period | Reason | Archive Location |
|---|---|---|---|
| CloudTrail | 2 years | HIPAA compliance | AWS S3 Glacier |
| Application Logs | 1 year | Operational history | AWS S3 |
| Database Audit Logs | 1 year | Compliance audits | AWS S3 |
| Security Logs | 2 years | Investigation support | AWS S3 Glacier |
| Access Logs | 6 months | Operational logging | CloudWatch |
Log Lifecycle Management
# terraform/log-lifecycle.tf
resource "aws_s3_bucket_lifecycle_configuration" "logs_lifecycle" {
bucket = aws_s3_bucket.menotime_logs.id
rule {
id = "archive-old-logs"
status = "Enabled"
# Move to Glacier after 90 days
transition {
days = 90
storage_class = "GLACIER"
}
# Delete after 2 years (730 days)
expiration {
days = 730
}
}
}
Log Monitoring and Alerting
CloudWatch Alarms
Critical events trigger alerts:
# terraform/log-alarms.tf
# Alert on unauthorized root account access
resource "aws_cloudwatch_log_group_metric_filter" "root_login" {
name = "RootLoginAlarm"
log_group_name = aws_cloudwatch_log_group.cloudtrail.name
filter_pattern = "{ $.userIdentity.type = \"Root\" && $.eventName != \"GetUser\" }"
metric_transformation {
name = "RootLoginCount"
namespace = "SecurityMetrics"
value = "1"
}
}
resource "aws_cloudwatch_metric_alarm" "root_login_alarm" {
alarm_name = "root-account-login"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = "RootLoginCount"
namespace = "SecurityMetrics"
period = 300
statistic = "Sum"
threshold = 1
alarm_actions = [aws_sns_topic.security_alerts.arn]
alarm_description = "Alert on root account login"
}
# Alert on failed login attempts (brute force detection)
resource "aws_cloudwatch_log_group_metric_filter" "failed_login" {
name = "FailedLoginAttempts"
log_group_name = aws_cloudwatch_log_group.cloudtrail.name
filter_pattern = "{ $.eventName = \"ConsoleLogin\" && $.errorCode = \"Failed authentication\" }"
metric_transformation {
name = "FailedLoginCount"
namespace = "SecurityMetrics"
value = "1"
}
}
resource "aws_cloudwatch_metric_alarm" "failed_login_alarm" {
alarm_name = "brute-force-detection"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
metric_name = "FailedLoginCount"
namespace = "SecurityMetrics"
period = 300
statistic = "Sum"
threshold = 10 # 10 failed attempts in 5 minutes
alarm_actions = [aws_sns_topic.security_alerts.arn]
}
Security Dashboard
# terraform/security-dashboard.tf
resource "aws_cloudwatch_dashboard" "security" {
dashboard_name = "MenoTime-Security"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
properties = {
metrics = [
["SecurityMetrics", "RootLoginCount"],
["SecurityMetrics", "FailedLoginCount"],
["SecurityMetrics", "UnauthorizedAPICallsCount"]
]
period = 300
stat = "Sum"
region = "us-east-1"
title = "Security Events"
}
},
{
type = "log"
properties = {
query = "fields @timestamp, user_id, action | filter action = 'PHI_ACCESS' | stats count() by user_id"
region = "us-east-1"
title = "PHI Access Events"
log_group = aws_cloudwatch_log_group.app_logs.name
}
}
]
})
}
Log Analysis for Compliance Audits
Monthly Compliance Reports
#!/bin/bash
# generate-compliance-report.sh
echo "=== MenoTime Monthly Compliance Report ==="
echo "Report Date: $(date)"
# 1. CloudTrail activity summary
echo -e "\n=== API Activity Summary ==="
aws cloudtrail lookup-events \
--start-time $(date -d 'last month' +%Y-%m-01T00:00:00Z) \
--end-time $(date +%Y-%m-01T00:00:00Z) \
--max-results 1000 | jq '.Events | length'
# 2. Failed login attempts
echo -e "\n=== Failed Login Attempts ==="
aws logs start-query \
--log-group-name /aws/cloudtrail/menotime \
--start-time $(date -d '30 days ago' +%s) \
--end-time $(date +%s) \
--query-string '{ $.eventName = "ConsoleLogin" && $.errorCode = "Failed authentication" } | stats count() as FailedAttempts'
# 3. Privileged access usage
echo -e "\n=== Root Account Access ==="
aws logs start-query \
--log-group-name /aws/cloudtrail/menotime \
--start-time $(date -d '30 days ago' +%s) \
--end-time $(date +%s) \
--query-string '{ $.userIdentity.type = "Root" } | stats count() as RootAccess'
# 4. Data access audit
echo -e "\n=== PHI Access Events ==="
aws logs start-query \
--log-group-name /menotime/prod/application \
--start-time $(date -d '30 days ago' +%s) \
--end-time $(date +%s) \
--query-string 'filter action = "PHI_ACCESS" | stats count() by user_id'
Compliance Checklist
- ✓ CloudTrail enabled and logging all API calls
- ✓ CloudTrail logs encrypted at rest with KMS
- ✓ CloudTrail logs immutable (versioning enabled, write-once)
- ✓ CloudTrail logs retained for 2 years minimum
- ✓ Application logs captured for all authentication events
- ✓ Database audit logs (pgAudit) enabled and retained
- ✓ PHI access events logged with user ID and timestamp
- ✓ Failed login attempts detected and alerted on
- ✓ Root account access monitored and alerted
- ✓ Privileged access decisions logged
- ✓ Configuration changes logged
- ✓ All logs encrypted in transit and at rest
- ✓ Log access restricted to authorized staff
- ✓ Monthly compliance reports generated
- ✓ Quarterly access reviews documented
- ✓ Log analysis for security investigations conducted
- ✓ False positives investigated and documented
Questions? Contact the Security Officer at security@timelessbiotech.com.