Audit Logging Best Practices


Introduction

Audit logs are the authoritative record of who did what, when, and where in a system. They are essential for incident investigation, compliance reporting, and operational troubleshooting. A robust audit logging architecture ensures logs are complete, tamper-evident, and readily accessible when needed — often months or years after the event.

Immutable Logs

Log immutability prevents attackers or insiders from covering their tracks by modifying or deleting log entries.

Write-Once, Read-Many (WORM) Storage




import hashlib


import hmac


import json


from datetime import datetime




class ImmutableAuditLogger:


def __init__(self, storage_backend, hmac_key):


self.storage = storage_backend


self.hmac_key = hmac_key.encode()




def write_log(self, event):


"""Write a tamper-evident log entry."""


# Create log entry with metadata


entry = {


'timestamp': datetime.utcnow().isoformat(),


'event': event,


'sequence': self._next_sequence(),


}




# Add hash of previous entry (blockchain-style chain)


prev_entry = self.storage.get_last()


if prev_entry:


entry['prev_hash'] = prev_entry['hash']


else:


entry['prev_hash'] = '0' * 64




# Calculate hash of this entry


entry_json = json.dumps(entry, sort_keys=True)


entry_hash = hashlib.sha256(entry_json.encode()).hexdigest()


entry['hash'] = entry_hash




# HMAC sign the hash


entry['signature'] = hmac.new(


self.hmac_key,


entry_hash.encode(),


hashlib.sha256


).hexdigest()




# Write to WORM storage


log_id = f"{entry['timestamp']}_{entry['sequence']}"


self.storage.write(log_id, entry)




return entry




def verify_chain(self):


"""Verify integrity of the entire log chain."""


entries = self.storage.get_all()


prev_hash = '0' * 64




for entry in entries:


# Verify hash


entry_copy = {k: v for k, v in entry.items()


if k not in ('hash', 'signature')}


computed_hash = hashlib.sha256(


json.dumps(entry_copy, sort_keys=True).encode()


).hexdigest()




if computed_hash != entry['hash']:


return False, f"Hash mismatch at sequence {entry['sequence']}"




# Verify chain link


if entry['prev_hash'] != prev_hash:


return False, f"Chain break at sequence {entry['sequence']}"




# Verify HMAC


expected_sig = hmac.new(


self.hmac_key,


entry['hash'].encode(),


hashlib.sha256


).hexdigest()




if not hmac.compare_digest(expected_sig, entry['signature']):


return False, f"Signature mismatch at sequence {entry['sequence']}"




prev_hash = entry['hash']




return True, "Log chain verified"





Append-Only Logging




# Linux auditd configuration for immutable logs


# /etc/audit/rules.d/audit.rules


# Make audit log immutable


-e 2




# Log all administrative commands


-w /usr/bin/su -p x -k privilege_escalation


-w /usr/bin/sudo -p x -k privilege_escalation




# Log critical file access


-w /etc/passwd -p wa -k passwd_changes


-w /etc/shadow -p wa -k shadow_changes


-w /etc/ssh/sshd_config -p wa -k ssh_config




# Log system calls for process creation


-a always,exit -S execve -k process_creation




# Log network configuration changes


-w /sbin/iptables -p x -k firewall_changes


-w /sbin/ip6tables -p x -k firewall_changes




# Set log file permissions


-a exclude,never -F auid>=1000





Log Integrity Monitoring




# Forward logs to remote syslog server (prevents local tampering)


# /etc/rsyslog.d/remote-logging.conf


*.* @@logs.example.com:514 # TCP with TLS


$ActionSendStreamDriver gtls


$ActionSendStreamDriverMode 1


$ActionSendStreamDriverAuthMode x509/name


$ActionSendStreamDriverPermittedPeer *.example.com




# Use Logstash/Filebeat for shipping


filebeat.inputs:


- type: log


paths:


- /var/log/audit/*.log


- /var/log/syslog


- /var/log/auth.log


fields:


environment: production


log_type: security_audit




output.elasticsearch:


hosts: ["https://elasticsearch.example.com:9200"]


protocol: "https"


ssl.verification_mode: certificate





Centralized Logging Architecture




# Loki/Promtail centralized logging configuration


scrape_configs:


- job_name: audit-logs


static_configs:


- targets:


- localhost


labels:


job: audit-logs


environment: production


__path__: /var/log/audit/*.log




pipeline_stages:


- json:


expressions:


timestamp: timestamp


event_type: event.type


user: event.user


action: event.action


resource: event.resource


result: event.result




- labels:


event_type: ""


result: ""




- timestamp:


source: timestamp


format: RFC3339





Log Retention Policies




retention_policies:


security_audit_logs:


retention: 365_days


storage: warm_glacier


compliance: soc2_pci_hipaa




application_logs:


retention: 30_days


storage: hot


compliance: operational




debug_logs:


retention: 7_days


storage: hot


compliance: none




access_logs:


retention: 90_days


storage: warm


compliance: soc2




backup_logs:


retention: 3_years


storage: glacier


compliance: legal_hold








# Automated log rotation and archival


class LogRotationManager:


def __init__(self, log_directory, archive_bucket):


self.log_dir = Path(log_directory)


self.archive_bucket = archive_bucket




def rotate_and_archive(self, retention_days=365):


now = datetime.utcnow()




for log_file in self.log_dir.glob("audit-*.log"):


file_age = now - datetime.fromtimestamp(log_file.stat().st_mtime)




if file_age.days > retention_days:


# Compress


gzip_path = log_file.with_suffix('.log.gz')


with open(log_file, 'rb') as f_in:


with gzip.open(gzip_path, 'wb') as f_out:


shutil.copyfileobj(f_in, f_out)




# Upload to cold storage


self.archive_bucket.upload(


str(gzip_path),


f"audit_logs/{gzip_path.name}"


)




# Delete local copy


log_file.unlink()


gzip_path.unlink()





Compliance Requirements




audit_log_compliance_mapping:


pci_dss_10:


- requirement: "10.2" # Audit trails for all access


- requirement: "10.3" # Record audit trail entries


- requirement: "10.4" # Protect audit trail files


- requirement: "10.5" # Secure audit trails


- requirement: "10.7" # Retain audit history for 12 months




soc2_cc6:


- criteria: "CC6.1" # Logical and physical access controls


- criteria: "CC6.4" # Restricted access to audit trails




gdpr_article_30:


- requirement: "Records of processing activities"


- requirement: "Document data retention periods"





Conclusion

Audit logging is a critical security control that requires careful architectural planning. Implement chain-of-hash verification for tamper evidence, write to immutable WORM storage, centralize logs for unified access, enforce retention policies aligned with compliance requirements, and protect log integrity throughout the lifecycle. The most sophisticated detection capabilities are useless if logs can be silently altered or deleted.