OpenSearch domains should have logging to CloudWatch Logs enabled
OpenSearch domains process search queries and index operations that can reveal performance bottlenecks, unauthorized access patterns, and configuration errors. Without CloudWatch Logs integration, these signals vanish after the domain's internal buffer rotates, leaving you blind during incident investigation.
Centralizing OpenSearch logs in CloudWatch also enables metric filters, alarms, and cross-account log aggregation. If an attacker modifies cluster settings or exfiltrates data through unusual query patterns, CloudWatch log data provides the forensic trail needed to scope the breach and satisfy regulatory reporting timelines.
Retrofit consideration
Existing domains require a CloudWatch log group and a resource policy granting OpenSearch permission to write to it. If your account already has the maximum number of CloudWatch Logs resource policies (10), you must consolidate policies before enabling log publishing.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "opensearch" {
source = "soc2.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "pcidss.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "nydfs23.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "acscessentialeight.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
module "opensearch" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
If you use terraform-aws-modules/opensearch/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "opensearch" {
source = "terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
Use AWS provider resources directly. See docs for the resources involved: aws_opensearch_domain.
resource "aws_opensearch_domain" "this" {
advanced_security_options {
enabled = true
internal_user_database_enabled = true
master_user_options {
master_user_name = "admin"
master_user_password = "ChangeMe123!"
}
}
auto_tune_options {
desired_state = "DISABLED"
}
cluster_config {
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
cognito_options {
enabled = true
identity_pool_id = "us-east-1:12345678-1234-1234-1234-123456789012"
role_arn = "arn:aws:iam::123456789012:role/example-role"
user_pool_id = "us-east-1_AbCdEfGhI"
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "pofix-abc123"
ebs_options {
ebs_enabled = true
volume_size = 10
volume_type = "gp3"
}
encrypt_at_rest {
enabled = true
}
engine_version = "OpenSearch_2.11"
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "AUDIT_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "ES_APPLICATION_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "SEARCH_SLOW_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "INDEX_SLOW_LOGS"
}
node_to_node_encryption {
enabled = true
}
vpc_options {
security_group_ids = ["sg-12345678"]
subnet_ids = ["subnet-12345678"]
}
}
What this control checks
The control checks that the aws_opensearch_domain resource includes one or more log_publishing_options blocks. Each block requires a log_type set to one of INDEX_SLOW_LOGS, SEARCH_SLOW_LOGS, ES_APPLICATION_LOGS, or AUDIT_LOGS, and a cloudwatch_log_group_arn pointing to a valid aws_cloudwatch_log_group. The enabled argument must either be omitted (defaults to true) or explicitly set to true. A domain with no log_publishing_options blocks fails, as does one where all blocks have enabled = false.
An aws_cloudwatch_log_resource_policy must also exist, granting the OpenSearch service principal (es.amazonaws.com) permission to call logs:PutLogEvents and logs:CreateLogStream on the target log group. Without it, the domain accepts the configuration but silently fails to deliver logs.
Common pitfalls
CloudWatch Logs resource policy missing or too restrictive
Terraform will successfully apply
log_publishing_optionson theaws_opensearch_domain, but log delivery silently fails without a matchingaws_cloudwatch_log_resource_policygrantinglogs:PutLogEventsandlogs:CreateLogStreamtoes.amazonaws.com. The control may pass at the configuration level while no logs actually arrive.10-policy limit on CloudWatch Logs resource policies
AWS enforces a hard limit of 10 resource policies per region on CloudWatch Logs. With multiple OpenSearch domains or other services needing log delivery, you will need to consolidate policies before you hit the ceiling. Crossing it causes
LimitExceededExceptiononaws_cloudwatch_log_resource_policycreation, and there is no in-band warning before you get there.Audit logs require fine-grained access control
Setting
log_type = "AUDIT_LOGS"inlog_publishing_optionsonly works ifadvanced_security_optionswithenabled = trueandinternal_user_database_enabledare already configured on the domain. Without fine-grained access control enabled, OpenSearch rejects the audit log configuration at apply time.Legacy elasticsearch_domain resource
Older Terraform configurations may still use the deprecated
aws_elasticsearch_domainresource, which supportslog_publishing_optionswith the same block structure. Migration toaws_opensearch_domainis required for engine versions after Elasticsearch 7.10. The control evaluates the live AWS resource regardless of which Terraform resource type created it, so a passing plan check does not guarantee the resource type is current.
Audit evidence
Config rule evaluation results showing the domain as COMPLIANT confirm that log publishing options are configured. Console screenshots of the domain's "Logs" tab in the OpenSearch Service dashboard, showing enabled log types and their target CloudWatch log group ARNs, confirm the active configuration directly.
For deeper verification, CloudWatch Logs Insights queries showing log events actually flowing confirm active delivery, not just configuration. CloudTrail events for UpdateDomainConfig API calls with LogPublishingOptions in the request parameters establish when logging was enabled and by whom. Where audit logs are enabled, fine-grained access control log entries in CloudWatch complete the picture.
Framework-specific interpretation
SOC 2: CC7.2 asks for monitoring to detect anomalies in system operation. Shipping OpenSearch logs to CloudWatch gives you the continuous visibility and alerting that satisfies CC7.2, and the documented response capability that CC7.3 expects.
PCI DSS v4.0: Requirement 10 covers audit logging across all in-scope system components, including the ability to detect and investigate suspicious activity. For OpenSearch domains touching cardholder data, CloudWatch Logs integration addresses the log review requirements in 10.4.x and the retention requirement in 10.5.1.1.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
opensearch_domain_logs_to_cloudwatchAWS Config Managed Rules:
ELASTICSEARCH_LOGS_TO_CLOUDWATCH,OPENSEARCH_LOGS_TO_CLOUDWATCHCheckov Checks:
CKV_AWS_317,CKV_AWS_84Powerpipe Controls:
aws_compliance.control.es_domain_logs_to_cloudwatch,aws_compliance.control.opensearch_domain_logs_to_cloudwatchProwler Checks:
opensearch_service_domains_audit_logging_enabled,opensearch_service_domains_cloudwatch_logging_enabledAWS Security Hub Controls:
Opensearch.4,Opensearch.5Trivy Check:
AWS-0042
Last reviewed: 2026-03-09