Skip to content

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:

  1. CryptoCurrency Activity: Bitcoin mining or other crypto activity detected
  2. Reconnaissance: Port scanning or DNS enumeration detected
  3. Resource Compromise: Malware detected or compromised instance identified
  4. Backdoor: Unexpected connections to external IPs
  5. 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.