Skip to main content
emnode / learn
Compliance

Enable database audit and log exports

One capability across RDS, Aurora, Redshift and OpenSearch: make every managed data store ship its audit and engine logs off the instance to CloudWatch or S3, so you keep a durable record of who connected and what ran.

14 min·10 sections·AWS

Last reviewed

Remediates AWS Security Hub: Opensearch.4Opensearch.5RDS.9RDS.45Redshift.4

Database logging: the basics

What does "logging enabled" actually mean across managed data stores?

Every managed AWS data store writes logs, but by default most of them keep those logs on the instance where they are useless to you: they rotate away, they die when the instance is replaced, and they cannot be queried, retained, or alerted on. This capability is about pushing those logs off the instance to a durable destination. Amazon RDS exports its engine logs (PostgreSQL's postgresql.log, MySQL's error and slow logs, Oracle's alert log, SQL Server's error log) to CloudWatch Logs. Aurora MySQL ships its built-in Advanced Auditing stream. Amazon Redshift publishes connection, user, and user-activity logs to S3 or CloudWatch. Amazon OpenSearch Service publishes error and audit logs to CloudWatch.

AWS Security Hub turns each of these into its own control, which is why a single estate can fail several database-logging checks at once. RDS.9 fails any RDS instance whose EnabledCloudwatchLogsExports is empty; RDS.45 fails any Aurora MySQL cluster not shipping the audit log; Redshift.4 fails any Redshift cluster without audit logging; Opensearch.4 fails domains not publishing error logs and Opensearch.5 fails domains not publishing audit logs. They look like separate problems on the report, but they are one capability: make sure each data store keeps a durable, off-instance record of what happened to it.

The good news is that the database side of this is almost always free or near-free. Enabling an audit plugin or a log export adds negligible overhead to the engine itself; the only real cost is downstream storage in CloudWatch Logs or S3, which is a few dollars a month for most stores and is controllable with a retention policy and event-type filtering. The job is to find every data store that is not shipping logs, decide which log types each one actually needs, turn the exports on, and cap retention so the bill stays proportionate.

In this lesson you will learn how RDS, Aurora, Redshift and OpenSearch each express logging, how to find every data store that is not shipping its logs off the instance, and how to turn the exports on without breaking anything or blowing up the CloudWatch bill. The Controls this lesson covers section lists every Security Hub control in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The logs that died with the instance

RDS engine logs live on the instance's local disk by default and rotate away on a size or time schedule. A 2022 post-mortem from a fintech team describes a SEV1 where a payments worker was corrupting rows; by the time they went to pull the database log to confirm the cause, the read replica had been recycled overnight by unrelated automation and every relevant log line was gone. No export to CloudWatch had been configured. They spent four more days reconstructing the timeline from application logs. The first time you need a database log is always after it no longer exists, which is exactly why shipping it off the instance is the whole point of this capability.

Finding data stores that ship no logs

Marco is on the platform team at a healthcare-adjacent SaaS company preparing for a SOC 2 review. Security Hub shows database-logging failures spread across an RDS PostgreSQL instance, an Aurora MySQL cluster, a Redshift warehouse and two OpenSearch domains, most of them stood up before the team standardised its provisioning module.

Rather than work the findings one by one, he starts with the highest-value store first and confirms what each is actually exporting today, so the changes are deliberate rather than blind toggles.

Start with the RDS instances that ship nothing off the box. An empty EnabledCloudwatchLogsExports array is the RDS.9 failure.

$ aws rds describe-db-instances --query 'DBInstances[?length(EnabledCloudwatchLogsExports || `[]`) == `0`].[DBInstanceIdentifier,Engine]' --output table
-------------------------------------------
| prod-orders-pg | postgres |
| billing-mysql | mysql |
-------------------------------------------
# Two databases keeping every engine log local-only. Both will die with the instance.

Stores holding sensitive data come first. Fix the ones whose access you would most need to reconstruct.

How each data store ships its logsdeep dive

Each service exposes logging differently, but they resolve to two mechanisms. The first is a CloudWatch Logs export: RDS runs a sidecar that tails the engine's log files and pushes lines to a log group named /aws/rds/instance//; Aurora MySQL ships its audit stream to /aws/rds/cluster//audit once audit is in EnabledCloudwatchLogsExports; OpenSearch publishes ES_APPLICATION_LOGS (error) and AUDIT_LOGS to CloudWatch log groups. The second is an S3 destination: Redshift's log-delivery service writes gzipped connection, user, and user-activity files under a prefix, and can also target CloudWatch. Security Hub passes the relevant control as long as the right log type is being shipped, not which sink you chose.

Two prerequisites trip people up. OpenSearch audit logs require fine-grained access control to be enabled first, because the security plugin that emits them is only active when advanced security options are on; without it the enable call is rejected. And both OpenSearch and Redshift's S3 destination need the service principal granted permission to write to the destination (a CloudWatch Logs resource policy for es.amazonaws.com, or the Redshift log-delivery bucket policy on the S3 bucket). Skip the permission and the enable call can succeed while no logs ever arrive, which is a silent failure worth verifying.

Security Hub evaluates these through AWS Config, typically on a change-triggered or periodic cycle, so a fix does not flip the finding to PASSED instantly. The control plane change itself is non-disruptive on modern engines (no reboot, no query interruption for RDS log exports; a brief Processing state for OpenSearch; no restart for Redshift), but the report catches up on the next evaluation. The detail teams most often miss is that the audit stream you want may need a separate parameter: Redshift query text needs enable_user_activity_logging = true, and Aurora MySQL audit content is governed by server_audit_events.

What is the impact of running data stores without logging?

The direct impact is investigative blindness. These stores frequently hold the most sensitive, most aggregated data an organisation has, and without their audit and engine logs there is no independent record of who connected, from where, or what they ran. If a credential is compromised or data is exfiltrated, the questions an incident responder must answer (scope, timeline, blast radius) have no source data behind them, and the honest answer becomes worst-case.

The second-order impact is operational. Engine error and slow logs are also how you diagnose a degraded database or a struggling search domain. With logs off the instance and in CloudWatch, you can query them, correlate them, and alarm on patterns; with logging off, the on-call engineer responding at 2am opens CloudWatch and finds nothing, and a fifteen-minute incident stretches into an hour of guesswork.

On the compliance side, every modern framework (SOC 2, ISO 27001, HIPAA, PCI DSS) expects access to sensitive data stores to be logged and retained for a defined period. PCI DSS requirement 10.2.1 and the NIST 800-53 audit-and-accountability family (AU-2, AU-3, AU-12) are explicit about this. A passing set of database-logging controls is among the cheapest and most defensible artefacts you can hand an auditor; a failing one is a concrete, citable gap with a remediation deadline.

How do you enable database logging safely?

Work the capability as one loop rather than chasing individual findings. The order matters: confirm what each store needs before you flip switches, and set retention before logs start piling up.

1. Inventory every data store and what it ships today

Across RDS, Aurora, Redshift and OpenSearch, list each store and check its current log configuration: EnabledCloudwatchLogsExports for RDS and Aurora, LoggingProperties for Redshift, and LogPublishingOptions for OpenSearch. Treat this inventory as the source of truth, not the Security Hub finding count, and rank it by data sensitivity so the most valuable stores are remediated first.

2. Pick log types deliberately, not by default

Enable what the auditor and your on-call actually need, not everything. For RDS, error and slow logs are almost always worth it; for Aurora MySQL, CONNECT plus QUERY_DDL plus QUERY_DCL is the standard compliance filter at a fraction of full-query volume; for Redshift, connection and user logs are on with logging and query text needs the extra parameter; for OpenSearch, error logs satisfy Opensearch.4 and audit logs satisfy Opensearch.5 (which needs fine-grained access control first). Avoid bare full-query logging unless security or legal specifically ask for it.

3. Prepare the destination and grant the permission

Enabling the export is one API call per store, and it is non-disruptive on modern engines. The work is in the destination: a CloudWatch Logs resource policy granting the service principal write access (OpenSearch), the Redshift log-delivery bucket policy on the target S3 bucket, and the fine-grained-access-control prerequisite for OpenSearch audit logs. Skip the permission and the enable call can succeed while no logs arrive, so verify that events are actually landing.

4. Cap retention and prevent recurrence

Set a retention policy on every CloudWatch log group and an S3 lifecycle on every log bucket; the CloudWatch default is never-expire, which is the worst possible setting. Match the window to the strictest compliance obligation in scope (90 days, a year, or six for HIPAA, with aged logs tiered to Glacier). Then bake logging and retention into the provisioning template and back it with AWS Config rules (rds-logging-enabled, redshift-cluster-audit-logging-enabled, opensearch-audit-logging-enabled) so new stores arrive compliant by default.

# Enable CloudWatch log export on a flagged RDS instance, then cap retention.
aws rds modify-db-instance \
  --db-instance-identifier prod-orders-pg \
  --cloudwatch-logs-exports-configuration 'EnableLogTypes=["postgresql","upgrade"]' \
  --apply-immediately

aws logs put-retention-policy \
  --log-group-name /aws/rds/instance/prod-orders-pg/postgresql \
  --retention-in-days 90

# Enable audit logging on a Redshift cluster to a policy-attached S3 bucket.
aws redshift enable-logging \
  --cluster-identifier analytics-prod \
  --bucket-name redshift-audit-logs-acct123 \
  --s3-key-prefix analytics-prod/

Quick quiz

Question 1 of 5

Security Hub shows database-logging failures across RDS.9, RDS.45, Redshift.4 and Opensearch.5. What is the most efficient way to think about them?

You can now treat database logging as one capability rather than a scatter of findings: inventory what each data store ships today, pick the log types the auditor and your on-call actually need, enable the exports with the right permissions in place, and cap retention so the storage cost stays bounded. The Controls this lesson covers section below links every control in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many AWS Security Hub controls. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.