Skip to main content

Audit Logging & Monitoring

Overviewโ€‹

HIPAA Audit Controls standard (45 CFR ยง 164.312(b)) requires mechanisms that record and examine activity in systems that contain ePHI. Audit logs must be retained for a minimum of 6 years.


1. Cloud Audit Logsโ€‹

Log TypeWhat It CapturesNotes
Admin ActivityAPI calls that modify resourcesAlways enabled
Data AccessAPI calls that read data or metadataMust be explicitly enabled
System EventAutomated GCP system actionsAlways enabled
Policy DeniedRequests denied by VPC-SC or IAMEnabled by VPC-SC

Enable Data Access Logsโ€‹

gcloud projects get-iam-policy YOUR_PHI_PROJECT_ID --format=json > iam_policy.json

Edit iam_policy.json to add:

{
"auditConfigs": [
{
"service": "allServices",
"auditLogConfigs": [
{ "logType": "ADMIN_READ" },
{ "logType": "DATA_READ" },
{ "logType": "DATA_WRITE" }
]
},
{
"service": "cloudsql.googleapis.com",
"auditLogConfigs": [
{ "logType": "DATA_READ" },
{ "logType": "DATA_WRITE" }
]
},
{
"service": "secretmanager.googleapis.com",
"auditLogConfigs": [
{ "logType": "DATA_READ" },
{ "logType": "DATA_WRITE" }
]
}
]
}
gcloud projects set-iam-policy YOUR_PHI_PROJECT_ID iam_policy.json

2. 6-Year Log Retentionโ€‹

# Create an immutable log bucket
gcloud storage buckets create gs://your-org-phi-audit-logs \
--location=us-central1 \
--uniform-bucket-level-access \
--public-access-prevention \
--project=YOUR_PHI_PROJECT_ID

# Set 6-year retention policy
gcloud storage buckets update gs://your-org-phi-audit-logs \
--retention-period=2190d --project=YOUR_PHI_PROJECT_ID

# LOCK the retention policy (irreversible โ€” confirm with compliance team)
gcloud storage buckets update gs://your-org-phi-audit-logs \
--lock-retention-policy --project=YOUR_PHI_PROJECT_ID

# Create a log sink
gcloud logging sinks create phi-audit-sink \
"storage.googleapis.com/your-org-phi-audit-logs" \
--log-filter='protoPayload.@type="type.googleapis.com/google.cloud.audit.AuditLog"' \
--project=YOUR_PHI_PROJECT_ID

# Grant the sink write access
SINK_SA=$(gcloud logging sinks describe phi-audit-sink \
--project=YOUR_PHI_PROJECT_ID --format="value(writerIdentity)")
gcloud storage buckets add-iam-policy-binding gs://your-org-phi-audit-logs \
--member="$SINK_SA" --role="roles/storage.objectCreator"

3. Application-Level Audit Loggingโ€‹

from enum import Enum
from google.cloud import logging as gcp_logging

class AuditAction(str, Enum):
LOGIN = "LOGIN"
LOGOUT = "LOGOUT"
PHI_VIEW = "PHI_VIEW"
PHI_CREATE = "PHI_CREATE"
PHI_UPDATE = "PHI_UPDATE"
PHI_DELETE = "PHI_DELETE"
PHI_EXPORT = "PHI_EXPORT"
ACCESS_DENIED = "ACCESS_DENIED"

class PHIAuditLogger:
"""Logs to Cloud Logging AND database. Never logs PHI values."""

def __init__(self, project_id: str, db_connection):
gcp_client = gcp_logging.Client(project=project_id)
self.logger = gcp_client.logger("phi-audit-log")
self.db = db_connection

def log(self, action: AuditAction, user_id: str, resource_type: str,
resource_id: str, ip_address: str, outcome: str = "SUCCESS"):
phi_keys = {"ssn", "name", "dob", "diagnosis", "address", "phone"}
entry = {
"action": action.value, "user_id": user_id,
"resource_type": resource_type, "resource_id": resource_id,
"ip_address": ip_address, "outcome": outcome,
}
self.logger.log_struct(entry, severity="NOTICE",
labels={"hipaa_audit": "true"})
self.db.execute(
"INSERT INTO audit_log (user_id, action, resource_type, resource_id, "
"ip_address, outcome) VALUES (%s, %s, %s, %s, %s, %s)",
(user_id, action.value, resource_type, resource_id, ip_address, outcome)
)

Events to Logโ€‹

EventRequired Fields
User loginuser_id, timestamp, ip, success/failure
PHI record vieweduser_id, resource_type, resource_id (no PHI), timestamp
PHI record created/updateduser_id, resource_type, resource_id, timestamp
PHI exportuser_id, format, record_count, timestamp
Failed accessuser_id or IP, resource, reason, timestamp

4. Security Alertsโ€‹

AlertThresholdPriority
Failed logins> 5 per 5 minutesHIGH
IAM policy changesAny changeHIGH
Firewall rule changesAny changeHIGH
VPC-SC policy violationAny violationCRITICAL
KMS key deletion requestedAny requestCRITICAL
Secret accessed outside business hours10pmโ€“6amMEDIUM
# Create log-based metric for failed logins
gcloud logging metrics create failed-logins \
--description="Failed login attempts" \
--log-filter='jsonPayload.action="LOGIN" AND jsonPayload.outcome="FAILURE"' \
--project=YOUR_PHI_PROJECT_ID

5. Useful Log Queriesโ€‹

# All Cloud SQL data access events
gcloud logging read 'resource.type="cloudsql_database" AND logName:"data_access"' \
--project=YOUR_PHI_PROJECT_ID --limit=100

# IAM policy changes
gcloud logging read 'protoPayload.methodName:"SetIamPolicy"' \
--project=YOUR_PHI_PROJECT_ID --limit=50

# Secret Manager access events
gcloud logging read \
'protoPayload.serviceName="secretmanager.googleapis.com"' \
--project=YOUR_PHI_PROJECT_ID --limit=50

Next: Incident Response โ†’