From 5b9b15fe67bbf36468d6af6d9782316914031762 Mon Sep 17 00:00:00 2001 From: RoseSecurity Date: Mon, 27 Oct 2025 10:13:41 -0400 Subject: [PATCH 1/3] feat: add SAML options and service type for OpenSearch - Added `aws_service_type` variable to support both Elasticsearch and OpenSearch deployments, with validation and default for backward compatibility. - Introduced `elasticsearch_saml_options` variable and resource to manage SAML authentication for OpenSearch domains. - Updated outputs to be conditional on `local.enabled`. - Added `elasticsearch_log_cleanup_enabled` variable for log cleanup Lambda. - Improved password generation logic for compatibility. --- src/main.tf | 22 +++++++++++++++++++--- src/outputs.tf | 25 +++++++++++++++---------- src/variables.tf | 36 +++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/main.tf b/src/main.tf index 5a7a179..5950025 100644 --- a/src/main.tf +++ b/src/main.tf @@ -9,11 +9,10 @@ locals { elasticsearch_domain_endpoint = format(local.elasticsearch_endpoint_format, "elasticsearch_domain_endpoint") elasticsearch_kibana_endpoint = format(local.elasticsearch_endpoint_format, "elasticsearch_kibana_endpoint") elasticsearch_admin_password = format(local.elasticsearch_endpoint_format, "password") -} -locals { create_password = local.enabled && length(var.elasticsearch_password) == 0 - elasticsearch_password = local.create_password ? join("", random_password.elasticsearch_password.*.result) : var.elasticsearch_password + elasticsearch_password = local.create_password ? one(random_password.elasticsearch_password[*].result) : var.elasticsearch_password + saml_options_enabled = local.enabled && var.elasticsearch_saml_options.enabled } module "elasticsearch" { @@ -25,6 +24,7 @@ module "elasticsearch" { subnet_ids = local.vpc_private_subnet_ids zone_awareness_enabled = length(local.vpc_private_subnet_ids) > 1 ? true : false elasticsearch_version = var.elasticsearch_version + aws_service_type = var.aws_service_type instance_type = var.instance_type instance_count = length(local.vpc_private_subnet_ids) availability_zone_count = length(local.vpc_private_subnet_ids) @@ -56,6 +56,20 @@ module "elasticsearch" { context = module.this.context } +resource "aws_opensearch_domain_saml_options" "this" { + count = local.saml_options_enabled ? 1 : 0 + + domain_name = module.elasticsearch.domain_name + + saml_options { + enabled = var.elasticsearch_saml_options.enabled + idp { + entity_id = var.elasticsearch_saml_options.entity_id + metadata_content = var.elasticsearch_saml_options.metadata_content + } + } +} + resource "random_password" "elasticsearch_password" { count = local.create_password ? 1 : 0 # character length @@ -104,6 +118,8 @@ module "elasticsearch_log_cleanup" { source = "cloudposse/lambda-elasticsearch-cleanup/aws" version = "0.16.1" + enabled = var.elasticsearch_log_cleanup_enabled + es_endpoint = module.elasticsearch.domain_endpoint es_domain_arn = module.elasticsearch.domain_arn es_security_group_id = module.elasticsearch.security_group_id diff --git a/src/outputs.tf b/src/outputs.tf index 2cf5af8..cf8d6a1 100644 --- a/src/outputs.tf +++ b/src/outputs.tf @@ -1,49 +1,54 @@ output "security_group_id" { - value = module.elasticsearch.security_group_id + value = local.enabled ? module.elasticsearch.security_group_id : null description = "Security Group ID to control access to the Elasticsearch domain" } output "domain_arn" { - value = module.elasticsearch.domain_arn + value = local.enabled ? module.elasticsearch.domain_arn : null description = "ARN of the Elasticsearch domain" } output "domain_id" { - value = module.elasticsearch.domain_id + value = local.enabled ? module.elasticsearch.domain_id : null description = "Unique identifier for the Elasticsearch domain" } +output "domain_name" { + value = local.enabled ? module.elasticsearch.domain_name : null + description = "Name of the Elasticsearch domain" +} + output "domain_endpoint" { - value = module.elasticsearch.domain_endpoint + value = local.enabled ? module.elasticsearch.domain_endpoint : null description = "Domain-specific endpoint used to submit index, search, and data upload requests" } output "kibana_endpoint" { - value = module.elasticsearch.kibana_endpoint + value = local.enabled ? module.elasticsearch.kibana_endpoint : null description = "Domain-specific endpoint for Kibana without https scheme" } output "domain_hostname" { - value = module.elasticsearch.domain_hostname + value = local.enabled ? module.elasticsearch.domain_hostname : null description = "Elasticsearch domain hostname to submit index, search, and data upload requests" } output "kibana_hostname" { - value = module.elasticsearch.kibana_hostname + value = local.enabled ? module.elasticsearch.kibana_hostname : null description = "Kibana hostname" } output "elasticsearch_user_iam_role_name" { - value = module.elasticsearch.elasticsearch_user_iam_role_name + value = local.enabled ? module.elasticsearch.elasticsearch_user_iam_role_name : null description = "The name of the IAM role to allow access to Elasticsearch cluster" } output "elasticsearch_user_iam_role_arn" { - value = module.elasticsearch.elasticsearch_user_iam_role_arn + value = local.enabled ? module.elasticsearch.elasticsearch_user_iam_role_arn : null description = "The ARN of the IAM role to allow access to Elasticsearch cluster" } output "master_password_ssm_key" { - value = local.elasticsearch_admin_password + value = local.enabled ? local.elasticsearch_admin_password : null description = "SSM key of Elasticsearch master password" } diff --git a/src/variables.tf b/src/variables.tf index c47487d..9d0ed9e 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -8,9 +8,21 @@ variable "instance_type" { description = "The type of the instance" } +variable "aws_service_type" { + type = string + description = "The type of AWS service to deploy (`elasticsearch` or `opensearch`)." + # For backwards comptibility we default to elasticsearch + default = "elasticsearch" + + validation { + condition = contains(["elasticsearch", "opensearch"], var.aws_service_type) + error_message = "Value can only be one of `elasticsearch` or `opensearch`." + } +} + variable "elasticsearch_version" { type = string - description = "Version of Elasticsearch to deploy (_e.g._ `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5`" + description = "Version of Elasticsearch or Opensearch to deploy (_e.g._ `7.1`, `6.8`, `6.7`, `6.5`, `6.4`, `6.3`, `6.2`, `6.0`, `5.6`, `5.5`, `5.3`, `5.1`, `2.3`, `1.5`" } variable "encrypt_at_rest_enabled" { @@ -100,6 +112,28 @@ variable "elasticsearch_password" { } } +variable "elasticsearch_saml_options" { + type = object({ + enabled = optional(bool, false) + entity_id = optional(string) + metadata_content = optional(string) + }) + description = <<-EOT + Manages SAML authentication options for an AWS OpenSearch Domain + + enabled: Whether to enable SAML authentication for the OpenSearch Domain + entity_id: The entity ID of the IdP + metadata_content: The metadata of the IdP + EOT + default = {} +} + +variable "elasticsearch_log_cleanup_enabled" { + type = bool + description = "Whether to enable Elasticsearch log cleanup Lambda" + default = true +} + variable "dns_delegated_environment_name" { type = string description = "The name of the environment where the `dns-delegated` component is deployed" From 7ce2c1b27669d759bc1488af7ea349bc9c662a75 Mon Sep 17 00:00:00 2001 From: RoseSecurity <72598486+RoseSecurity@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:55:33 -0400 Subject: [PATCH 2/3] Update src/variables.tf Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variables.tf b/src/variables.tf index 9d0ed9e..6e9631e 100644 --- a/src/variables.tf +++ b/src/variables.tf @@ -11,7 +11,7 @@ variable "instance_type" { variable "aws_service_type" { type = string description = "The type of AWS service to deploy (`elasticsearch` or `opensearch`)." - # For backwards comptibility we default to elasticsearch + # For backwards compatibility we default to elasticsearch default = "elasticsearch" validation { From 99139a6d76e5081a073057c2cffddc98f2eb56b8 Mon Sep 17 00:00:00 2001 From: RoseSecurity Date: Mon, 27 Oct 2025 11:01:16 -0400 Subject: [PATCH 3/3] feat(terraform): add SAML options for ES and OpenSearch domains Refactored SAML options resources to support both Elasticsearch and OpenSearch domains. Added conditional logic to create the appropriate resource based on the selected AWS service type. Updated resource names and configuration to ensure compatibility and flexibility for both services. --- src/main.tf | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main.tf b/src/main.tf index 5950025..22b451f 100644 --- a/src/main.tf +++ b/src/main.tf @@ -56,11 +56,23 @@ module "elasticsearch" { context = module.this.context } -resource "aws_opensearch_domain_saml_options" "this" { - count = local.saml_options_enabled ? 1 : 0 +resource "aws_elasticsearch_domain_saml_options" "elasticsearch" { + count = local.saml_options_enabled && var.aws_service_type == "elasticsearch" ? 1 : 0 domain_name = module.elasticsearch.domain_name + saml_options { + enabled = var.elasticsearch_saml_options.enabled + idp { + entity_id = var.elasticsearch_saml_options.entity_id + metadata_content = var.elasticsearch_saml_options.metadata_content + } + } +} +resource "aws_opensearch_domain_saml_options" "opensearch" { + count = local.saml_options_enabled && var.aws_service_type == "opensearch" ? 1 : 0 + + domain_name = module.elasticsearch.domain_name saml_options { enabled = var.elasticsearch_saml_options.enabled idp {