Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
72f3e1c
test(postgres): require privacy-safe logging defaults
seonghobae Aug 10, 2026
0a9d873
fix(postgres): keep SQL content out of optional logs
seonghobae Aug 10, 2026
d574e17
docs(postgres): record content-safe logging boundary
seonghobae Aug 10, 2026
9014c13
test(postgres): keep query-stat text opt-in
seonghobae Aug 10, 2026
52f7879
fix(postgres): make query statistics opt-in
seonghobae Aug 10, 2026
9f94990
docs(postgres): make query-text collection explicit opt-in
seonghobae Aug 10, 2026
0984c66
test(postgres): require activity-query privacy boundary
seonghobae Aug 10, 2026
d0fb1d1
docs(postgres): disclose live activity query-text boundary
seonghobae Aug 10, 2026
721f747
docs(postgres): qualify volatile activity query text
seonghobae Aug 10, 2026
4111a9f
test(postgres): require collector for csv logging
seonghobae Aug 10, 2026
9e40e38
fix(postgres): make optional csv logging effective
seonghobae Aug 10, 2026
dc168c1
test(docs): require csv logging routing contract
seonghobae Aug 10, 2026
32cde99
docs(postgres): define csv logging routing boundary
seonghobae Aug 10, 2026
29bd3c5
test(postgres): require connection metadata minimization
seonghobae Aug 10, 2026
ce09c50
fix(postgres): minimize connection metadata logging
seonghobae Aug 10, 2026
abb83ac
docs(postgres): govern client network log metadata
seonghobae Aug 10, 2026
923f1ec
test(postgres): require timing metrics to be opt-in
seonghobae Aug 10, 2026
88133a4
fix(postgres): make timing instrumentation opt-in
seonghobae Aug 10, 2026
f3e4b45
docs(postgres): bound timing instrumentation overhead
seonghobae Aug 10, 2026
64fed07
test(postgres): require function statistics to be opt-in
seonghobae Aug 10, 2026
ee555f6
fix(postgres): make function statistics opt-in
seonghobae Aug 10, 2026
3eb1236
docs(postgres): bound function statistics overhead
seonghobae Aug 10, 2026
755487e
test(postgres): require commit timestamp tracking opt-in
seonghobae Aug 10, 2026
54476a2
fix(postgres): keep commit timestamp tracking opt-in
seonghobae Aug 10, 2026
3fc852b
docs(postgres): bound commit timestamp metadata collection
seonghobae Aug 10, 2026
5b14c0b
test(postgres): bound temp and autovacuum log volume
seonghobae Aug 11, 2026
798ce22
fix(postgres): bound temp and autovacuum log volume
seonghobae Aug 11, 2026
1771ea6
docs(postgres): bound optional log event volume
seonghobae Aug 11, 2026
e831063
test(postgres): require query-statistics preload opt-in
seonghobae Aug 11, 2026
bee5ba0
fix(postgres): make query-statistics preload opt-in
seonghobae Aug 11, 2026
41cfdc8
docs(postgres): make query-statistics preload explicitly opt-in
seonghobae Aug 11, 2026
4759169
test(postgres): bind query-stat preload doctoring contract
seonghobae Aug 11, 2026
4ad8f74
merge(main): reconcile privacy logging with exact-source governance
seonghobae Aug 11, 2026
50fd8f8
Merge protected main into fix/privacy-safe-postgres-logging
seonghobae Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 162 additions & 170 deletions docker/postgres/postgresql.conf.custom
Original file line number Diff line number Diff line change
@@ -1,183 +1,175 @@
# =============================================================================
# Custom PostgreSQL Configuration for Production Monitoring
# Optional PostgreSQL monitoring configuration
# =============================================================================
# This file extends the base PostgreSQL configuration with enhanced monitoring
# and performance tracking capabilities across ALL databases in the cluster.
# This file is shipped as an operator-applied example. The current container
# image copies it into /etc/postgresql but does NOT automatically include it in
# the running cluster configuration. Applying it is a deployment decision.
#
# Apply these settings via:
# 1. Include in main postgresql.conf: include = 'postgresql.conf.custom'
# 2. Or copy contents to main postgresql.conf
# 3. Or apply via ALTER SYSTEM commands
# Privacy boundary: pg-llm-batch can process prompts, responses, credentials,
# and identifiers. The package therefore disables persistent SQL statement/bind
# logging and pg_stat_statements query-text retention in this example. Live
# pg_stat_activity can still expose bounded current/recent query text while
# track_activities is enabled; that volatile privileged surface is documented
# separately and must be access-controlled by the deployment.
# Enabling broad SQL logging is not by itself evidence of SOC 2, ISO 27001,
# PCI-DSS, CSAP, or any other certification/compliance outcome.
#
# PostgreSQL 16 explicitly warns that logged statements can reveal sensitive
# data and plaintext passwords. See:
# https://www.postgresql.org/docs/16/runtime-config-logging.html
# =============================================================================

# =============================================================================
# SHARED LIBRARIES (requires restart)
# =============================================================================
shared_preload_libraries = 'pg_stat_statements,pg_cron'
# Query statistics are a true opt-in. PostgreSQL documents that loading
# pg_stat_statements reserves shared memory even when pg_stat_statements.track
# is none, so this general profile does not preload the module. A deployment
# that needs query-level statistics must deliberately add pg_stat_statements to
# shared_preload_libraries, restart PostgreSQL, create the extension in the
# intended database, and enable only the reviewed tracking surface.
shared_preload_libraries = 'pg_cron'

# =============================================================================
# QUERY TEXT SIZE LIMITS
# =============================================================================
# Increase from default 1024 to 32KB to capture full query text
track_activity_query_size = 32768
# pg_stat_activity remains a volatile live-query surface while track_activities
# is enabled. Bound its retained text and restrict pg_read_all_stats/superuser
# access; see docs/doctoring/postgresql-logging-privacy.md.
track_activity_query_size = 1024

# =============================================================================
# PG_STAT_STATEMENTS CONFIGURATION
# =============================================================================
# Track query statistics across ALL databases in the cluster
pg_stat_statements.max = 10000 # Track up to 10000 unique statements
pg_stat_statements.track = 'all' # Track all statements (not just top-level)
pg_stat_statements.track_utility = on # Include DDL, VACUUM, ANALYZE, etc.
pg_stat_statements.track_planning = on # Track planning time (PG 13+)
pg_stat_statements.save = on # Persist stats across restarts

# =============================================================================
# LOGGING CONFIGURATION
# =============================================================================
# Log all slow queries to PostgreSQL log files
log_min_duration_statement = 1000 # Log queries taking > 1 second
log_min_duration_sample = 500 # Sample queries 500ms-1s for performance analysis
log_statement_sample_rate = 0.01 # Additional 1% sampling for performance metrics (NOT for audit)
log_transaction_sample_rate = 0.01 # Transaction-level sampling for workload analysis

# IMPORTANT: These sample rates DO NOT affect audit logging!
# With log_statement = 'all', 100% of queries are logged regardless of sample rates
# Sample rates only control ADDITIONAL performance statistics collection

# Statement logging for comprehensive audit trail
log_statement = 'all' # Log EVERYTHING for complete audit trail
# 'none' = no logging (security risk!)
# 'ddl' = CREATE, ALTER, DROP only (insufficient!)
# 'mod' = DDL + INSERT, UPDATE, DELETE (minimum for compliance)
# 'all' = DDL + DML + SELECT (required for data security)

# Enhanced log format with full context
log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h,query_id=%Q '
log_checkpoints = on # Log checkpoint activity
log_connections = on # Log new connections
log_disconnections = on # Log disconnections
log_lock_waits = on # Log lock wait events
log_temp_files = 0 # Log all temp file usage
log_autovacuum_min_duration = 0 # Log all autovacuum runs

# =============================================================================
# PERFORMANCE TRACKING
# =============================================================================
track_io_timing = on # Track I/O timing for queries
track_wal_io_timing = on # Track WAL I/O timing (PG 14+)
track_functions = 'all' # Track function call counts and time
track_commit_timestamp = on # Track transaction commit times

# =============================================================================
# STATISTICS COLLECTION
# =============================================================================
# Query Monitoring Statistics (NOT Cost-Based Optimizer)
stats_fetch_consistency = 'cache' # Use cached stats for consistency
compute_query_id = 'on' # Generate query IDs for tracking

# =============================================================================
# COST-BASED OPTIMIZER CONFIGURATION (Separate from logging)
# =============================================================================
# These settings affect query planning, NOT audit logging
default_statistics_target = 100 # Default histogram buckets (10-10000)
# random_page_cost = 1.1 # SSD: 1.1, HDD: 4.0
# effective_cache_size = '4GB' # Total memory available for caching
# work_mem = '4MB' # Memory per sort/hash operation

# Auto-ANALYZE for CBO statistics updates
# autovacuum_analyze_threshold = 50 # Min rows before analyze
# autovacuum_analyze_scale_factor = 0.1 # 10% of table size change triggers analyze

# =============================================================================
# AUDIT AND COMPLIANCE CONFIGURATION (Enterprise-grade)
# =============================================================================
# Comprehensive audit logging for compliance (SOC2, ISO27001, PCI-DSS)
log_replication_commands = on # Log replication commands
log_rotation_age = 1h # Rotate logs hourly for high-volume systems
log_rotation_size = 1GB # Stay within PostgreSQL 16's per-file limit
log_truncate_on_rotation = off # Never truncate (audit trail preservation)
log_file_mode = 0600 # Secure file permissions
log_destination = 'csvlog' # CSV format for structured analysis

# Archive and compression strategy (requires external tools)
# archive_mode = on # Enable archiving
# archive_command = 'gzip < %p > /archive/%f.gz && aws s3 cp /archive/%f.gz s3://audit-logs/%f.gz'

# Row-level security auditing (when applicable)
row_security = on # Enable row security policies

# Additional audit trail for sensitive operations
log_error_verbosity = 'verbose' # Include SQLSTATE in logs
log_hostname = on # Log hostname for multi-server tracking
log_timezone = 'UTC' # Use UTC for consistency

# =============================================================================
# MEMORY AND RESOURCE TRACKING
# =============================================================================
log_executor_stats = off # Don't log per-query (too verbose)
log_parser_stats = off # Don't log parser stats
log_planner_stats = off # Don't log planner stats
log_statement_stats = off # Don't log statement stats

# But DO track these at system level:
track_counts = on # Track table/index access counts
track_activities = on # Track current query activity

# =============================================================================
# PG_CRON CONFIGURATION (for scheduled monitoring tasks)
# =============================================================================
cron.database_name = 'postgres' # Database where pg_cron metadata is stored
cron.use_background_workers = on # Use background workers for jobs
cron.max_running_jobs = 10 # Max concurrent cron jobs

# =============================================================================
# LOG RETENTION AND COMPLIANCE STRATEGY
# =============================================================================
# Enterprise audit log retention requirements:
# - Financial: 7 years (SOX, SEC Rule 17a-4)
# - Healthcare: 6 years (HIPAA)
# - Payment Card: 3 years (PCI-DSS)
# - EU Data: 6 years (GDPR for financial data)
# - General Business: 3-7 years depending on jurisdiction

# Recommended retention strategy:
# 1. HOT storage (local SSD): Last 7 days - immediate access
# 2. WARM storage (NAS/SAN): 7-90 days - quick retrieval
# 3. COLD storage (S3 Glacier): 90 days - 1 year - compliance retrieval
# 4. ARCHIVE (S3 Deep Archive): 1-7 years - legal hold

# Estimated storage requirements (with log_statement = 'all'):
# - 1,000 queries/second = ~86M queries/day
# - Average log entry: ~500 bytes
# - Daily raw logs: ~43GB
# - Daily compressed (gzip -9): ~4.3GB
# - Yearly compressed: ~1.5TB
# - 7-year archive: ~10.5TB compressed

# Log shipping and archival commands:
# log_directory = '/var/log/postgresql/audit' # Separate audit directory
# log_filename = 'audit_%Y%m%d_%H%M%S.csv' # Timestamp in filename

# External archival script example (run via cron):
# #!/bin/bash
# find /var/log/postgresql/audit -name "*.csv" -mmin +60 | while read file; do
# gzip -9 "$file"
# aws s3 cp "$file.gz" s3://audit-logs/$(date +%Y/%m/%d)/
# # Or use rsync for network storage:
# # rsync -av "$file.gz" backup-server:/audit-archive/$(date +%Y/%m/%d)/
# done

# =============================================================================
# NOTES FOR CLUSTER-WIDE MONITORING
# =============================================================================
# 1. pg_stat_statements is automatically available in ALL databases once loaded
# 2. Query the pg_stat_statements view from any database to see cluster-wide stats
# 3. Use dbid::regclass to identify which database each query belongs to
# 4. For true multi-database monitoring, consider:
# - Creating monitoring objects in 'postgres' database as central location
# - Using foreign data wrappers (postgres_fdw) to aggregate from all DBs
# - Setting up a dedicated monitoring database with cross-DB views
#
# CRITICAL: With log_statement = 'all', ensure adequate storage provisioning
# Monitor disk usage with: SELECT pg_size_pretty(pg_database_size('postgres'));
# Set up alerts for disk usage > 80% to prevent audit log loss
# Query-level statement collection is opt-in because pg_stat_statements stores
# representative query text. These placeholder settings remain fail-safe if an
# operator later preloads the module; collection stays disabled until explicitly
# changed under a reviewed purpose/access/retention policy.
pg_stat_statements.max = 10000
pg_stat_statements.track = 'none'
pg_stat_statements.track_utility = off
pg_stat_statements.track_planning = off
pg_stat_statements.save = off

# =============================================================================
# CONTENT-SAFE SERVER LOGGING BASELINE
# =============================================================================
# Do not persist SQL statement text or per-statement duration events merely to
# obtain operational evidence. Higher-level database/application metrics remain
# available below without forcing persistent query-content retention.
log_statement = 'none'
log_min_duration_statement = -1
log_min_duration_sample = -1
log_statement_sample_rate = 0
log_transaction_sample_rate = 0
log_duration = off

# Error messages remain available, but failing SQL text, bind values, QUERY, and
# CONTEXT payloads are excluded from this baseline. PANIC is used because
# log_min_error_statement controls statement-text inclusion, not whether the
# error itself is logged through log_min_messages.
log_min_error_statement = PANIC
log_parameter_max_length = 0
log_parameter_max_length_on_error = 0
log_error_verbosity = 'terse'

# The regular prefix intentionally omits remote-host escapes and SQL text.
# PostgreSQL CSV records nevertheless have a fixed client host:port field for
# emitted backend log entries. Treat that client network metadata, together with
# user/database/application identifiers, as access-controlled operational data.
# Connection/disconnection event logging is therefore opt-in rather than an
# unnecessary default source of additional persistent network-identity records.
log_line_prefix = '%m [%p]: user=%u,db=%d,app=%a,query_id=%Q '
log_checkpoints = on
log_connections = off
log_disconnections = off
log_lock_waits = on

# PostgreSQL documents that zero logs every temporary file name/size and every
# autovacuum action respectively. Avoid those unconditional high-volume event
# streams in the generic profile: temporary-file logging is off by default, and
# autovacuum logging stays at PostgreSQL's documented 10-minute threshold.
# Deployments may opt in to a lower threshold after defining a concrete purpose,
# storage/retention budget, and operator response path.
log_temp_files = -1
log_autovacuum_min_duration = 10min
log_replication_commands = off
log_hostname = off
log_timezone = 'UTC'

# =============================================================================
# PERFORMANCE / STATISTICS TRACKING
# =============================================================================
# PostgreSQL documents that I/O timing repeatedly reads the operating-system
# clock and can impose significant platform-dependent overhead. Keep both timing
# collectors off in the general example; deployments that need them should
# benchmark the target host with pg_test_timing and opt in deliberately.
track_io_timing = off
track_wal_io_timing = off

# Function-call timing/count collection is optional statistics work. PostgreSQL
# defaults this setting to none; keep the generic profile at that boundary so a
# deployment opts in only for a concrete diagnostic need after measuring a
# representative workload and accepting the added collection overhead.
track_functions = 'none'

# PostgreSQL records extra per-transaction commit metadata in pg_commit_ts when
# this server-start option is enabled, and the documented default is off. Keep
# it opt-in unless a deployment has a concrete commit-timestamp/replication need.
track_commit_timestamp = off
stats_fetch_consistency = 'cache'
# Keep the PostgreSQL default auto mode instead of forcing query-ID calculation
# for every statement. Adding pg_stat_statements later can request query IDs as
# documented, without imposing that work on deployments that leave it disabled.
compute_query_id = 'auto'
default_statistics_target = 100
track_counts = on
track_activities = on

# =============================================================================
# LOG FILE SAFETY / ROTATION
# =============================================================================
# PostgreSQL requires logging_collector for csvlog output. Enable it explicitly
# so an operator applying this example gets the declared structured destination
# instead of an internally inconsistent configuration. The collector changes
# log routing, not the content-retention policy above, and requires server start.
logging_collector = on

# Rotation bounds file growth; it does not define retention. Retention, export,
# deletion, legal hold, backup expiry, residency, and external log-shipping are
# host-owned data-governance decisions and must not be inferred from this file.
log_rotation_age = 1h
log_rotation_size = 1GB
log_truncate_on_rotation = off
log_file_mode = 0600
log_destination = 'csvlog'

# =============================================================================
# ROW SECURITY / PG_CRON
# =============================================================================
row_security = on
cron.database_name = 'postgres'
cron.use_background_workers = on
cron.max_running_jobs = 10

# =============================================================================
# OPERATOR NOTES
# =============================================================================
# - This example is not automatically loaded by the bundled image.
# - Keep SQL/bind-value logging and pg_stat_statements collection/preloading
# disabled unless a separately reviewed deployment has a concrete purpose,
# authorization model, minimal retention, access audit, encryption boundary,
# storage/performance budget, and incident/deletion procedure.
# - Keep all-temp-file logging disabled and do not lower the 10-minute autovacuum
# threshold without a defined diagnostic purpose and bounded log-storage plan.
# - Keep track_io_timing/track_wal_io_timing off unless the deployment has
# measured clock-read cost with pg_test_timing and accepts the runtime overhead.
# - Keep track_functions off unless function-call statistics have a reviewed
# diagnostic purpose and measured representative-workload overhead.
# - Keep track_commit_timestamp off unless commit-time metadata has a concrete
# purpose; enabling it requires server start and writes extra pg_commit_ts data.
# - CSV output has a fixed client host:port field on emitted records. Keep
# log_connections/log_disconnections off unless connection-audit events have a
# reviewed purpose, access boundary, retention policy, and storage budget.
# - track_activities remains enabled for operational diagnosis; treat the
# bounded volatile pg_stat_activity query field as sensitive live data and
# restrict privileged statistics access accordingly.
# - Prefer low-cardinality application telemetry and database operational metrics
# when query content is not needed to answer the operational question.
Loading
Loading