From 7ca75d29d51017334501c1f881598ca2f75e6c03 Mon Sep 17 00:00:00 2001 From: Segev Elmalech Date: Sun, 1 Feb 2026 16:57:46 +0200 Subject: [PATCH 1/4] feat: add EIP pool support for predictable IP addresses Add support for using pre-allocated Elastic IP pools instead of randomly created EIPs. This allows CI/CD runners and external systems to whitelist specific IP addresses for SSH access, improving security by avoiding 0.0.0.0/0 firewall rules. Changes: Base Modules: - Add optional eip_allocation_id parameter to sonar-base-instance, dam-base-instance, ciphertrust-manager, cte-ddc-agent, and dra-admin - When eip_allocation_id is provided, use existing EIP instead of creating new - When null (default), create and manage new EIP (backward compatible) Wrapper Modules: - Add pass-through eip_allocation_id parameter to hub, mx, agentless-gw, and agent-gw modules Deployment Example: - Add use_eip_pool and eip_pool_tag variables - Add eip_pool.tf with pool query logic and allocation ID distribution - Add validation to ensure sufficient IPs are available in pool - Add time_sleep resource to handle CipherTrust provider timing with pooled EIPs - Update all module calls to pass allocation IDs when pooled mode enabled Benefits: - Predictable IP addresses for CI/CD firewall whitelisting - IPs survive terraform destroy and can be reused - Fully backward compatible (default behavior unchanged) - Deployment-level pool management (base modules stay simple) Co-Authored-By: Claude --- examples/aws/poc/dsf_deployment/cm.tf | 23 +++- .../aws/poc/dsf_deployment/cte_ddc_agents.tf | 1 + examples/aws/poc/dsf_deployment/dam.tf | 1 + examples/aws/poc/dsf_deployment/dra.tf | 4 +- examples/aws/poc/dsf_deployment/eip_pool.tf | 108 ++++++++++++++++++ examples/aws/poc/dsf_deployment/sonar.tf | 2 + examples/aws/poc/dsf_deployment/variables.tf | 25 ++++ modules/aws/agent-gw/main.tf | 1 + modules/aws/agent-gw/variables.tf | 16 +++ modules/aws/agentless-gw/main.tf | 1 + modules/aws/agentless-gw/variables.tf | 16 +++ modules/aws/ciphertrust-manager/main.tf | 37 ++++-- modules/aws/ciphertrust-manager/variables.tf | 16 +++ modules/aws/cte-ddc-agent/main.tf | 33 +++++- modules/aws/cte-ddc-agent/variables.tf | 16 +++ modules/aws/dam-base-instance/main.tf | 33 +++++- modules/aws/dam-base-instance/variables.tf | 16 +++ modules/aws/dra-admin/main.tf | 33 +++++- modules/aws/dra-admin/variables.tf | 16 +++ modules/aws/hub/main.tf | 1 + modules/aws/hub/variables.tf | 16 +++ modules/aws/mx/main.tf | 1 + modules/aws/mx/variables.tf | 15 +++ modules/aws/sonar-base-instance/main.tf | 33 +++++- modules/aws/sonar-base-instance/variables.tf | 16 +++ 25 files changed, 450 insertions(+), 30 deletions(-) create mode 100644 examples/aws/poc/dsf_deployment/eip_pool.tf diff --git a/examples/aws/poc/dsf_deployment/cm.tf b/examples/aws/poc/dsf_deployment/cm.tf index f4425fc40..d0d6f349a 100644 --- a/examples/aws/poc/dsf_deployment/cm.tf +++ b/examples/aws/poc/dsf_deployment/cm.tf @@ -5,9 +5,9 @@ locals { } module "ciphertrust_manager" { - source = "imperva/dsf-ciphertrust-manager/aws" - version = "1.7.34" # latest release tag - count = local.ciphertrust_manager_count + source = "imperva/dsf-ciphertrust-manager/aws" + version = "1.7.34" # latest release tag + count = local.ciphertrust_manager_count ciphertrust_manager_version = var.ciphertrust_manager_version ami = var.ciphertrust_manager_ami_id == null ? null : { id = var.ciphertrust_manager_ami_id @@ -20,6 +20,7 @@ module "ciphertrust_manager" { subnet_id = local.ciphertrust_manager_subnet_id cm_password = local.password attach_persistent_public_ip = true + eip_allocation_id = length(local.ciphertrust_manager_eip_allocation_ids) > 0 ? local.ciphertrust_manager_eip_allocation_ids[count.index] : null key_pair = module.key_pair.key_pair.key_pair_name allowed_web_console_and_api_cidrs = concat(local.workstation_cidr, var.web_console_cidr) allowed_ssh_cidrs = concat(local.workstation_cidr, var.allowed_ssh_cidrs) @@ -33,7 +34,20 @@ module "ciphertrust_manager" { ] } +# When using pooled EIPs, the public IP is known immediately but the association takes time +# Add a delay to ensure the EIP is associated before the provider tries to connect +resource "time_sleep" "wait_for_ciphertrust_eip" { + count = local.ciphertrust_manager_count > 0 && var.use_eip_pool ? 1 : 0 + + depends_on = [ + module.ciphertrust_manager + ] + + create_duration = "30s" +} + provider "ciphertrust" { + # Use public IP for connectivity from local Mac/CI runners address = local.ciphertrust_manager_count > 0 ? "https://${coalesce(module.ciphertrust_manager[0].public_ip, module.ciphertrust_manager[0].private_ip)}" : null username = local.ciphertrust_manager_web_console_username password = local.password @@ -46,7 +60,8 @@ resource "ciphertrust_trial_license" "trial_license" { flag = "activate" depends_on = [ - module.ciphertrust_manager + module.ciphertrust_manager, + time_sleep.wait_for_ciphertrust_eip # Ensure EIP is associated before connecting ] } diff --git a/examples/aws/poc/dsf_deployment/cte_ddc_agents.tf b/examples/aws/poc/dsf_deployment/cte_ddc_agents.tf index 657f82aa4..8de17cce7 100644 --- a/examples/aws/poc/dsf_deployment/cte_ddc_agents.tf +++ b/examples/aws/poc/dsf_deployment/cte_ddc_agents.tf @@ -95,6 +95,7 @@ module "cte_ddc_agents" { } os_type = each.value.os_type attach_persistent_public_ip = true + eip_allocation_id = lookup(local.cte_agent_eip_allocation_ids, each.key, null) use_public_ip = true allowed_ssh_cidrs = concat(local.workstation_cidr, var.allowed_ssh_cidrs) allowed_rdp_cidrs = each.value.os_type == "Windows" ? concat(local.workstation_cidr, var.allowed_ssh_cidrs) : [] diff --git a/examples/aws/poc/dsf_deployment/dam.tf b/examples/aws/poc/dsf_deployment/dam.tf index fd0672155..6daa7c565 100644 --- a/examples/aws/poc/dsf_deployment/dam.tf +++ b/examples/aws/poc/dsf_deployment/dam.tf @@ -30,6 +30,7 @@ module "mx" { port = 8443 } : null attach_persistent_public_ip = true + eip_allocation_id = local.mx_eip_allocation_id large_scale_mode = var.large_scale_mode.mx create_server_group = length(var.simulation_db_types_for_agent) > 0 diff --git a/examples/aws/poc/dsf_deployment/dra.tf b/examples/aws/poc/dsf_deployment/dra.tf index cd6848526..4f371001d 100644 --- a/examples/aws/poc/dsf_deployment/dra.tf +++ b/examples/aws/poc/dsf_deployment/dra.tf @@ -21,6 +21,7 @@ module "dra_admin" { allowed_hub_cidrs = local.hub_cidr_list allowed_ssh_cidrs = concat(local.workstation_cidr, var.allowed_ssh_cidrs) attach_persistent_public_ip = true + eip_allocation_id = local.dra_admin_eip_allocation_id tags = local.tags depends_on = [ @@ -31,8 +32,7 @@ module "dra_admin" { module "dra_analytics" { source = "imperva/dsf-dra-analytics/aws" version = "1.7.34" # latest release tag - - count = local.dra_analytics_count + count = local.dra_analytics_count name = join("-", [local.deployment_name_salted, "dra", "analytics", count.index]) subnet_id = local.dra_analytics_subnet_id dra_version = module.globals.dra_version diff --git a/examples/aws/poc/dsf_deployment/eip_pool.tf b/examples/aws/poc/dsf_deployment/eip_pool.tf new file mode 100644 index 000000000..82a484056 --- /dev/null +++ b/examples/aws/poc/dsf_deployment/eip_pool.tf @@ -0,0 +1,108 @@ +# Query AWS for all EIPs in the pool +data "aws_eips" "pool" { + count = var.use_eip_pool ? 1 : 0 + + filter { + name = "tag:Pool" + values = [var.eip_pool_tag] + } +} + +# Create locals to distribute allocation IDs to resources +locals { + # Get list of allocation IDs from pool (only unassociated ones to avoid conflicts) + eip_pool_all_allocation_ids = var.use_eip_pool ? data.aws_eips.pool[0].allocation_ids : [] + + # Calculate how many IPs we need + eip_count_needed = ( + (var.enable_sonar ? 1 : 0) + # hub_main + (var.enable_sonar && var.hub_hadr ? 1 : 0) + # hub_dr + (var.enable_dam ? 1 : 0) + # mx + (var.enable_dra ? 1 : 0) + # dra_admin + (var.enable_ciphertrust ? var.ciphertrust_manager_count : 0) + # ciphertrust_managers + (var.enable_ciphertrust ? ( # cte/ddc agents + var.cte_ddc_agents_linux_count + + var.cte_agents_linux_count + + var.ddc_agents_linux_count + + var.cte_ddc_agents_windows_count + + var.cte_agents_windows_count + + var.ddc_agents_windows_count + ) : 0) + ) + + # Validate we have enough IPs + eip_pool_valid = !var.use_eip_pool || length(local.eip_pool_all_allocation_ids) >= local.eip_count_needed + + # Distribute allocation IDs to resources + # Use null if use_eip_pool is false (modules will create new EIPs) + + # Index counter for distributing IPs + hub_main_eip_index = 0 + hub_dr_eip_index = var.enable_sonar ? 1 : 0 + mx_eip_index = (var.enable_sonar ? 1 : 0) + (var.enable_sonar && var.hub_hadr ? 1 : 0) + dra_admin_eip_index = ( + (var.enable_sonar ? 1 : 0) + + (var.enable_sonar && var.hub_hadr ? 1 : 0) + + (var.enable_dam ? 1 : 0) + ) + ciphertrust_manager_eip_start_index = ( + (var.enable_sonar ? 1 : 0) + + (var.enable_sonar && var.hub_hadr ? 1 : 0) + + (var.enable_dam ? 1 : 0) + + (var.enable_dra ? 1 : 0) + ) + cte_agent_eip_start_index = ( + (var.enable_sonar ? 1 : 0) + + (var.enable_sonar && var.hub_hadr ? 1 : 0) + + (var.enable_dam ? 1 : 0) + + (var.enable_dra ? 1 : 0) + + (var.enable_ciphertrust ? var.ciphertrust_manager_count : 0) + ) + + # Assign specific allocation IDs to each resource + hub_main_eip_allocation_id = var.use_eip_pool && var.enable_sonar ? local.eip_pool_all_allocation_ids[local.hub_main_eip_index] : null + hub_dr_eip_allocation_id = var.use_eip_pool && var.enable_sonar && var.hub_hadr ? local.eip_pool_all_allocation_ids[local.hub_dr_eip_index] : null + mx_eip_allocation_id = var.use_eip_pool && var.enable_dam ? local.eip_pool_all_allocation_ids[local.mx_eip_index] : null + dra_admin_eip_allocation_id = var.use_eip_pool && var.enable_dra ? local.eip_pool_all_allocation_ids[local.dra_admin_eip_index] : null + + # For CipherTrust Managers, create a list of allocation IDs + ciphertrust_manager_eip_allocation_ids = var.use_eip_pool && var.enable_ciphertrust ? [ + for i in range(var.ciphertrust_manager_count) : + local.eip_pool_all_allocation_ids[local.ciphertrust_manager_eip_start_index + i] + ] : [] + + # For CTE/DDC agents, create a map of allocation IDs keyed by agent ID + # This matches the structure of local.all_agent_instances_map from cte_ddc_agents.tf + cte_agent_eip_allocation_ids = var.use_eip_pool && var.enable_ciphertrust ? { + for idx, instance_id in keys(local.all_agent_instances_map) : + instance_id => local.eip_pool_all_allocation_ids[local.cte_agent_eip_start_index + idx] + } : {} +} + +# Validation check +resource "null_resource" "eip_pool_validation" { + count = var.use_eip_pool ? 1 : 0 + + lifecycle { + precondition { + condition = local.eip_pool_valid + error_message = <" +EOF +} + +variable "eip_pool_tag" { + type = string + default = "dsf-eip-pool" + description = < 0 ? aws_eip.dsf_instance_eip[0].public_ip : null) : - aws_instance.cipthertrust_manager_instance.public_ip) - public_dns = (var.attach_persistent_public_ip ? - (length(aws_eip.dsf_instance_eip) > 0 ? aws_eip.dsf_instance_eip[0].public_dns : null) : - aws_instance.cipthertrust_manager_instance.public_dns) + # Determine public IP/DNS based on whether using existing EIP or created EIP + public_ip = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + data.aws_eip.existing[0].public_ip : # From existing EIP + (length(aws_eip.dsf_instance_eip) > 0 ? aws_eip.dsf_instance_eip[0].public_ip : null) # From created EIP + ) : aws_instance.cipthertrust_manager_instance.public_ip + + public_dns = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + data.aws_eip.existing[0].public_dns : + (length(aws_eip.dsf_instance_eip) > 0 ? aws_eip.dsf_instance_eip[0].public_dns : null) + ) : aws_instance.cipthertrust_manager_instance.public_dns + private_ip = length(aws_network_interface.eni.private_ips) > 0 ? tolist(aws_network_interface.eni.private_ips)[0] : null cm_address = coalesce(local.public_ip, local.private_ip) + + # Determine which allocation ID to use + eip_allocation_id = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + var.eip_allocation_id : # Use provided allocation ID + aws_eip.dsf_instance_eip[0].id # Use created EIP + ) : null +} + +# Data source to lookup existing EIP (when allocation ID provided) +data "aws_eip" "existing" { + count = var.attach_persistent_public_ip && var.eip_allocation_id != null ? 1 : 0 + id = var.eip_allocation_id } +# Create new EIP (only when allocation ID NOT provided) resource "aws_eip" "dsf_instance_eip" { - count = var.attach_persistent_public_ip ? 1 : 0 + count = var.attach_persistent_public_ip && var.eip_allocation_id == null ? 1 : 0 domain = "vpc" tags = merge(var.tags, { Name = var.friendly_name }) } @@ -26,7 +47,7 @@ resource "aws_eip" "dsf_instance_eip" { resource "aws_eip_association" "eip_assoc" { count = var.attach_persistent_public_ip ? 1 : 0 instance_id = aws_instance.cipthertrust_manager_instance.id - allocation_id = aws_eip.dsf_instance_eip[0].id + allocation_id = local.eip_allocation_id } resource "aws_instance" "cipthertrust_manager_instance" { diff --git a/modules/aws/ciphertrust-manager/variables.tf b/modules/aws/ciphertrust-manager/variables.tf index 747894da5..38daf53fc 100644 --- a/modules/aws/ciphertrust-manager/variables.tf +++ b/modules/aws/ciphertrust-manager/variables.tf @@ -158,6 +158,22 @@ variable "attach_persistent_public_ip" { description = "Create public elastic IP for the instance" } +variable "eip_allocation_id" { + type = string + default = null + description = < 0 ? tolist(aws_network_interface.eni.private_ips)[0] : null instance_address = var.use_public_ip ? local.public_ip : local.private_ip @@ -27,10 +38,24 @@ locals { target_platform = var.os_type == "Windows" ? "windows" : null dummy_file_path = "${path.module}/dummy.txt" + + # Determine which allocation ID to use + eip_allocation_id = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + var.eip_allocation_id : # Use provided allocation ID + aws_eip.dsf_instance_eip[0].id # Use created EIP + ) : null +} + +# Data source to lookup existing EIP (when allocation ID provided) +data "aws_eip" "existing" { + count = var.attach_persistent_public_ip && var.eip_allocation_id != null ? 1 : 0 + id = var.eip_allocation_id } +# Create new EIP (only when allocation ID NOT provided) resource "aws_eip" "dsf_instance_eip" { - count = var.attach_persistent_public_ip ? 1 : 0 + count = var.attach_persistent_public_ip && var.eip_allocation_id == null ? 1 : 0 domain = "vpc" tags = merge(var.tags, { Name = var.friendly_name }) } @@ -38,7 +63,7 @@ resource "aws_eip" "dsf_instance_eip" { resource "aws_eip_association" "eip_assoc" { count = var.attach_persistent_public_ip ? 1 : 0 instance_id = aws_instance.cte_ddc_agent.id - allocation_id = aws_eip.dsf_instance_eip[0].id + allocation_id = local.eip_allocation_id } resource "aws_network_interface" "eni" { diff --git a/modules/aws/cte-ddc-agent/variables.tf b/modules/aws/cte-ddc-agent/variables.tf index a76c6dab1..de68bd33a 100644 --- a/modules/aws/cte-ddc-agent/variables.tf +++ b/modules/aws/cte-ddc-agent/variables.tf @@ -46,6 +46,22 @@ variable "security_group_ids" { default = [] } +variable "eip_allocation_id" { + type = string + default = null + description = < 0 ? tolist(aws_network_interface.eni.private_ips)[0] : null security_group_ids = concat( @@ -23,10 +34,24 @@ locals { agent-gw = "gateway" } } + + # Determine which allocation ID to use + eip_allocation_id = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + var.eip_allocation_id : # Use provided allocation ID + aws_eip.dsf_instance_eip[0].id # Use created EIP + ) : null +} + +# Data source to lookup existing EIP (when allocation ID provided) +data "aws_eip" "existing" { + count = var.attach_persistent_public_ip && var.eip_allocation_id != null ? 1 : 0 + id = var.eip_allocation_id } +# Create new EIP (only when allocation ID NOT provided) resource "aws_eip" "dsf_instance_eip" { - count = var.attach_persistent_public_ip ? 1 : 0 + count = var.attach_persistent_public_ip && var.eip_allocation_id == null ? 1 : 0 domain = "vpc" tags = merge(var.tags, { Name = var.name }) } @@ -34,7 +59,7 @@ resource "aws_eip" "dsf_instance_eip" { resource "aws_eip_association" "eip_assoc" { count = var.attach_persistent_public_ip ? 1 : 0 instance_id = aws_instance.dsf_base_instance.id - allocation_id = aws_eip.dsf_instance_eip[0].id + allocation_id = local.eip_allocation_id } resource "aws_instance" "dsf_base_instance" { diff --git a/modules/aws/dam-base-instance/variables.tf b/modules/aws/dam-base-instance/variables.tf index 560a01d8f..48b379e5d 100644 --- a/modules/aws/dam-base-instance/variables.tf +++ b/modules/aws/dam-base-instance/variables.tf @@ -42,6 +42,22 @@ variable "attach_persistent_public_ip" { description = "Create and attach elastic public IP for the instance" } +variable "eip_allocation_id" { + type = string + default = null + description = < 0 ? tolist(aws_network_interface.eni.private_ips)[0] : null security_group_ids = concat( @@ -16,10 +27,24 @@ locals { readiness_script = templatefile("${path.module}/readiness.tftpl", { admin_server_public_ip = try(local.public_ip, local.private_ip) }) + + # Determine which allocation ID to use + eip_allocation_id = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + var.eip_allocation_id : # Use provided allocation ID + aws_eip.dsf_instance_eip[0].id # Use created EIP + ) : null +} + +# Data source to lookup existing EIP (when allocation ID provided) +data "aws_eip" "existing" { + count = var.attach_persistent_public_ip && var.eip_allocation_id != null ? 1 : 0 + id = var.eip_allocation_id } +# Create new EIP (only when allocation ID NOT provided) resource "aws_eip" "dsf_instance_eip" { - count = var.attach_persistent_public_ip ? 1 : 0 + count = var.attach_persistent_public_ip && var.eip_allocation_id == null ? 1 : 0 domain = "vpc" tags = merge(var.tags, { Name = var.name }) } @@ -27,7 +52,7 @@ resource "aws_eip" "dsf_instance_eip" { resource "aws_eip_association" "eip_assoc" { count = var.attach_persistent_public_ip ? 1 : 0 instance_id = aws_instance.dsf_base_instance.id - allocation_id = aws_eip.dsf_instance_eip[0].id + allocation_id = local.eip_allocation_id } resource "aws_instance" "dsf_base_instance" { diff --git a/modules/aws/dra-admin/variables.tf b/modules/aws/dra-admin/variables.tf index cfae03e97..dbb1bfe65 100644 --- a/modules/aws/dra-admin/variables.tf +++ b/modules/aws/dra-admin/variables.tf @@ -169,6 +169,22 @@ variable "allowed_all_cidrs" { default = [] } +variable "eip_allocation_id" { + type = string + default = null + description = < 0 ? tolist(aws_network_interface.eni.private_ips)[0] : null # root volume details @@ -22,10 +33,24 @@ locals { # If the binaries_location.s3_key is "file.zip", then the installation_s3_prefix will be null installation_s3_prefix = try(regex("^(.*)/[^/]+", var.binaries_location.s3_key)[0], null) installation_s3_bucket_and_prefix = local.installation_s3_prefix != null ? join("/", [var.binaries_location.s3_bucket, local.installation_s3_prefix]) : var.binaries_location.s3_bucket + + # Determine which allocation ID to use + eip_allocation_id = var.attach_persistent_public_ip ? ( + var.eip_allocation_id != null ? + var.eip_allocation_id : # Use provided allocation ID + aws_eip.dsf_instance_eip[0].id # Use created EIP + ) : null +} + +# Data source to lookup existing EIP (when allocation ID provided) +data "aws_eip" "existing" { + count = var.attach_persistent_public_ip && var.eip_allocation_id != null ? 1 : 0 + id = var.eip_allocation_id } +# Create new EIP (only when allocation ID NOT provided) resource "aws_eip" "dsf_instance_eip" { - count = var.attach_persistent_public_ip ? 1 : 0 + count = var.attach_persistent_public_ip && var.eip_allocation_id == null ? 1 : 0 domain = "vpc" tags = merge(var.tags, { Name = var.name }) } @@ -33,7 +58,7 @@ resource "aws_eip" "dsf_instance_eip" { resource "aws_eip_association" "eip_assoc" { count = var.attach_persistent_public_ip ? 1 : 0 instance_id = aws_instance.dsf_base_instance.id - allocation_id = aws_eip.dsf_instance_eip[0].id + allocation_id = local.eip_allocation_id } resource "aws_instance" "dsf_base_instance" { diff --git a/modules/aws/sonar-base-instance/variables.tf b/modules/aws/sonar-base-instance/variables.tf index b96da1ce2..b3a2cccaf 100644 --- a/modules/aws/sonar-base-instance/variables.tf +++ b/modules/aws/sonar-base-instance/variables.tf @@ -47,6 +47,22 @@ variable "attach_persistent_public_ip" { description = "Create and attach elastic public IP for the instance" } +variable "eip_allocation_id" { + type = string + default = null + description = < Date: Mon, 2 Feb 2026 09:44:16 +0200 Subject: [PATCH 2/4] feat: add support for Elastic IP pool in AWS deployments --- README.md | 9 +++++++++ examples/aws/poc/dsf_deployment/README.md | 2 ++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 447f89ace..f47ca4ffa 100644 --- a/README.md +++ b/README.md @@ -633,6 +633,15 @@ The following table lists the _latest_ DSF Kit releases, their release date and 2. Improvements and bug fixes. + + TBD + + 1.7.35 + + 1. Added support for Elastic IP (EIP) pool for AWS deployments. Set 'use_eip_pool' to true and specify 'eip_pool_tag' to use pre-allocated EIPs with predictable IP addresses. +
2. Improvements and bug fixes. + + diff --git a/examples/aws/poc/dsf_deployment/README.md b/examples/aws/poc/dsf_deployment/README.md index 17cf71f1e..0b786661e 100644 --- a/examples/aws/poc/dsf_deployment/README.md +++ b/examples/aws/poc/dsf_deployment/README.md @@ -108,6 +108,8 @@ Several variables in the `variables.tf` file are important for configuring the d ### Networking - `subnet_ids`: IDs of the subnets for the deployment. If not specified, a new vpc is created. +- `use_eip_pool`: Set to `true` to use pre-allocated Elastic IPs from a pool instead of creating new ones. Default: `false` +- `eip_pool_tag`: AWS tag value to identify the EIP pool (e.g., `dsf-eip-pool`). Only used when `use_eip_pool = true`. EIPs must be tagged with `Pool=` in AWS before deployment. ### Audit Sources for Simulation Purposes - `simulation_db_types_for_agentless`: Types of databases to provision and onboard to an Agentless Gateway From a008a4bfa448865d1ab7db83952196fe0d8c2b93 Mon Sep 17 00:00:00 2001 From: Segev Elmalech Date: Mon, 9 Feb 2026 14:22:37 +0200 Subject: [PATCH 3/4] fix: EIP pool stable distribution and association validation - Use fixed slot positions for singleton resources (hub=0, hub-dr=1, mx=2, dra-admin=3) so enabling/disabling modules never shifts EIPs - Add secondary data source to query unassociated pool EIPs and validate that no pool EIPs are associated to non-managed resources - Sort allocation IDs for stable ordering across API calls - Sort agent map keys for consistent distribution - Update README with fixed-slot documentation - Update .gitignore with additional patterns --- .gitignore | 10 +- examples/aws/poc/dsf_deployment/README.md | 7 + examples/aws/poc/dsf_deployment/eip_pool.tf | 193 +++++++++++++------- 3 files changed, 147 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index 8fffac4ae..e9851b0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,12 @@ myip-default __pycache__/ upgrade_status.json .coverage - +.vscode/* +*.log +*.txt +*.bin +*.exe +*.rpm +examples/azure/poc/dsf_deployment/y/* +*.msi +*.tfstate* \ No newline at end of file diff --git a/examples/aws/poc/dsf_deployment/README.md b/examples/aws/poc/dsf_deployment/README.md index 0b786661e..5f84ee0a9 100644 --- a/examples/aws/poc/dsf_deployment/README.md +++ b/examples/aws/poc/dsf_deployment/README.md @@ -111,6 +111,13 @@ Several variables in the `variables.tf` file are important for configuring the d - `use_eip_pool`: Set to `true` to use pre-allocated Elastic IPs from a pool instead of creating new ones. Default: `false` - `eip_pool_tag`: AWS tag value to identify the EIP pool (e.g., `dsf-eip-pool`). Only used when `use_eip_pool = true`. EIPs must be tagged with `Pool=` in AWS before deployment. +> **EIP Pool — Fixed Slot Distribution**: The pool uses fixed slot positions to ensure IP stability: +> - Slot 0: Hub Main, Slot 1: Hub DR, Slot 2: MX, Slot 3: DRA Admin, Slots 4+: CipherTrust Managers, then CTE/DDC Agents. +> - Enabling/disabling modules (sonar, dam, dra) does **not** shift EIPs for other resources. +> - The pool must contain enough EIPs to cover through the highest used slot (some lower slots may be unused). +> - Changing `ciphertrust_manager_count` will shift agent EIP assignments; for full stability, supply specific `eip_allocation_id` values directly to each module. +> - All pool EIPs must be unassociated before the first deployment. A validation check will warn if any pool EIPs are already associated to non-managed resources. + ### Audit Sources for Simulation Purposes - `simulation_db_types_for_agentless`: Types of databases to provision and onboard to an Agentless Gateway - `simulation_db_types_for_agent`: Types of databases to provision for Agent Gateways diff --git a/examples/aws/poc/dsf_deployment/eip_pool.tf b/examples/aws/poc/dsf_deployment/eip_pool.tf index 82a484056..4895e6aa4 100644 --- a/examples/aws/poc/dsf_deployment/eip_pool.tf +++ b/examples/aws/poc/dsf_deployment/eip_pool.tf @@ -1,4 +1,7 @@ -# Query AWS for all EIPs in the pool +# Query AWS for all EIPs in the pool (associated and unassociated) +# We need ALL pool EIPs for distribution since on subsequent applies our own +# associations make them "associated". The aws_eip_association resource is +# idempotent - associating an EIP to the same instance it's already on is a no-op. data "aws_eips" "pool" { count = var.use_eip_pool ? 1 : 0 @@ -8,62 +11,104 @@ data "aws_eips" "pool" { } } +# Query only UNASSOCIATED pool EIPs for validation purposes. +# This catches user errors like tagging an EIP that's already in use by +# a non-managed resource. On first deploy all pool EIPs should be unassociated. +# On subsequent applies, pool EIPs associated to our instances are expected. +data "aws_eips" "pool_available" { + count = var.use_eip_pool ? 1 : 0 + + filter { + name = "tag:Pool" + values = [var.eip_pool_tag] + } + + filter { + name = "association-id" + values = [""] # Empty association-id means unassociated + } +} + # Create locals to distribute allocation IDs to resources locals { - # Get list of allocation IDs from pool (only unassociated ones to avoid conflicts) - eip_pool_all_allocation_ids = var.use_eip_pool ? data.aws_eips.pool[0].allocation_ids : [] - - # Calculate how many IPs we need - eip_count_needed = ( - (var.enable_sonar ? 1 : 0) + # hub_main - (var.enable_sonar && var.hub_hadr ? 1 : 0) + # hub_dr - (var.enable_dam ? 1 : 0) + # mx - (var.enable_dra ? 1 : 0) + # dra_admin - (var.enable_ciphertrust ? var.ciphertrust_manager_count : 0) + # ciphertrust_managers - (var.enable_ciphertrust ? ( # cte/ddc agents - var.cte_ddc_agents_linux_count + - var.cte_agents_linux_count + - var.ddc_agents_linux_count + - var.cte_ddc_agents_windows_count + - var.cte_agents_windows_count + - var.ddc_agents_windows_count - ) : 0) - ) + # Get sorted list of ALL allocation IDs from pool. + # Sort ensures stable ordering across API calls - same EIP always gets same index. + eip_pool_all_allocation_ids = var.use_eip_pool ? sort(data.aws_eips.pool[0].allocation_ids) : [] - # Validate we have enough IPs - eip_pool_valid = !var.use_eip_pool || length(local.eip_pool_all_allocation_ids) >= local.eip_count_needed - - # Distribute allocation IDs to resources - # Use null if use_eip_pool is false (modules will create new EIPs) - - # Index counter for distributing IPs - hub_main_eip_index = 0 - hub_dr_eip_index = var.enable_sonar ? 1 : 0 - mx_eip_index = (var.enable_sonar ? 1 : 0) + (var.enable_sonar && var.hub_hadr ? 1 : 0) - dra_admin_eip_index = ( - (var.enable_sonar ? 1 : 0) + - (var.enable_sonar && var.hub_hadr ? 1 : 0) + - (var.enable_dam ? 1 : 0) - ) - ciphertrust_manager_eip_start_index = ( - (var.enable_sonar ? 1 : 0) + - (var.enable_sonar && var.hub_hadr ? 1 : 0) + - (var.enable_dam ? 1 : 0) + - (var.enable_dra ? 1 : 0) + # Count of unassociated EIPs in the pool (for validation) + eip_pool_available_count = var.use_eip_pool ? length(data.aws_eips.pool_available[0].allocation_ids) : 0 + + # Count of already-associated EIPs in the pool + eip_pool_associated_count = var.use_eip_pool ? ( + length(local.eip_pool_all_allocation_ids) - local.eip_pool_available_count + ) : 0 + + # Total pool EIPs available + eip_pool_total_count = length(local.eip_pool_all_allocation_ids) + + # ============================================================================ + # Fixed slot positions for singleton resources + # These positions NEVER change regardless of which modules are enabled/disabled. + # This ensures that enabling/disabling sonar, dam, or dra does not shift the + # EIP assigned to other resources. + # + # Slot layout: + # 0: hub_main + # 1: hub_dr + # 2: mx + # 3: dra_admin + # 4+: ciphertrust_managers (up to ciphertrust_manager_count) + # 4+cm_count+: cte/ddc agents + # + # Trade-off: Some pool slots may be unused if a module is disabled, but + # positions are guaranteed stable across configuration changes. + # + # Note: Changing ciphertrust_manager_count will shift agent positions. + # This is acceptable as CM count changes are rare in practice. + # ============================================================================ + hub_main_eip_index = 0 + hub_dr_eip_index = 1 + mx_eip_index = 2 + dra_admin_eip_index = 3 + ciphertrust_manager_eip_start_index = 4 + cte_agent_eip_start_index = 4 + (var.enable_ciphertrust ? var.ciphertrust_manager_count : 0) + + # Total agents count + total_agent_count = ( + var.cte_ddc_agents_linux_count + + var.cte_agents_linux_count + + var.ddc_agents_linux_count + + var.cte_ddc_agents_windows_count + + var.cte_agents_windows_count + + var.ddc_agents_windows_count ) - cte_agent_eip_start_index = ( - (var.enable_sonar ? 1 : 0) + - (var.enable_sonar && var.hub_hadr ? 1 : 0) + - (var.enable_dam ? 1 : 0) + - (var.enable_dra ? 1 : 0) + - (var.enable_ciphertrust ? var.ciphertrust_manager_count : 0) + + # Calculate the highest slot index needed (for pool size validation) + # We use max() to find the highest slot that's actually in use + eip_pool_highest_slot = max( + var.enable_sonar ? local.hub_main_eip_index : -1, + var.enable_sonar && var.hub_hadr ? local.hub_dr_eip_index : -1, + var.enable_dam ? local.mx_eip_index : -1, + var.enable_dra ? local.dra_admin_eip_index : -1, + var.enable_ciphertrust && var.ciphertrust_manager_count > 0 ? ( + local.ciphertrust_manager_eip_start_index + var.ciphertrust_manager_count - 1 + ) : -1, + var.enable_ciphertrust && local.total_agent_count > 0 ? ( + local.cte_agent_eip_start_index + local.total_agent_count - 1 + ) : -1, ) - # Assign specific allocation IDs to each resource - hub_main_eip_allocation_id = var.use_eip_pool && var.enable_sonar ? local.eip_pool_all_allocation_ids[local.hub_main_eip_index] : null - hub_dr_eip_allocation_id = var.use_eip_pool && var.enable_sonar && var.hub_hadr ? local.eip_pool_all_allocation_ids[local.hub_dr_eip_index] : null - mx_eip_allocation_id = var.use_eip_pool && var.enable_dam ? local.eip_pool_all_allocation_ids[local.mx_eip_index] : null - dra_admin_eip_allocation_id = var.use_eip_pool && var.enable_dra ? local.eip_pool_all_allocation_ids[local.dra_admin_eip_index] : null + # Pool needs enough EIPs to cover through the highest used slot + eip_count_needed = local.eip_pool_highest_slot + 1 + + # Validate we have enough IPs + eip_pool_valid = !var.use_eip_pool || local.eip_pool_total_count >= local.eip_count_needed + + # Assign specific allocation IDs to each resource using fixed slot positions + hub_main_eip_allocation_id = var.use_eip_pool && var.enable_sonar ? local.eip_pool_all_allocation_ids[local.hub_main_eip_index] : null + hub_dr_eip_allocation_id = var.use_eip_pool && var.enable_sonar && var.hub_hadr ? local.eip_pool_all_allocation_ids[local.hub_dr_eip_index] : null + mx_eip_allocation_id = var.use_eip_pool && var.enable_dam ? local.eip_pool_all_allocation_ids[local.mx_eip_index] : null + dra_admin_eip_allocation_id = var.use_eip_pool && var.enable_dra ? local.eip_pool_all_allocation_ids[local.dra_admin_eip_index] : null # For CipherTrust Managers, create a list of allocation IDs ciphertrust_manager_eip_allocation_ids = var.use_eip_pool && var.enable_ciphertrust ? [ @@ -73,35 +118,59 @@ locals { # For CTE/DDC agents, create a map of allocation IDs keyed by agent ID # This matches the structure of local.all_agent_instances_map from cte_ddc_agents.tf + # Sort keys to ensure stable ordering - same agent always gets same pool EIP cte_agent_eip_allocation_ids = var.use_eip_pool && var.enable_ciphertrust ? { - for idx, instance_id in keys(local.all_agent_instances_map) : + for idx, instance_id in sort(keys(local.all_agent_instances_map)) : instance_id => local.eip_pool_all_allocation_ids[local.cte_agent_eip_start_index + idx] } : {} } -# Validation check +# Validation checks resource "null_resource" "eip_pool_validation" { count = var.use_eip_pool ? 1 : 0 lifecycle { + # Validate pool has enough EIPs for the fixed slot layout precondition { condition = local.eip_pool_valid error_message = < Date: Thu, 12 Feb 2026 11:49:19 +0200 Subject: [PATCH 4/4] refactor: update EIP pool validation logic and improve security group naming convention --- examples/aws/poc/dsf_deployment/eip_pool.tf | 21 +++------------------ modules/aws/core/globals/main.tf | 9 --------- modules/aws/cte-ddc-agent/sg.tf | 8 ++++++-- 3 files changed, 9 insertions(+), 29 deletions(-) diff --git a/examples/aws/poc/dsf_deployment/eip_pool.tf b/examples/aws/poc/dsf_deployment/eip_pool.tf index 4895e6aa4..914699fe5 100644 --- a/examples/aws/poc/dsf_deployment/eip_pool.tf +++ b/examples/aws/poc/dsf_deployment/eip_pool.tf @@ -155,23 +155,8 @@ Please allocate more EIPs with tag Pool=${var.eip_pool_tag}: EOF } - # Warn if there are associated EIPs in the pool that exceed what we manage. - # This catches the case where a user accidentally tagged an EIP that's - # already in use by a non-managed resource. - precondition { - condition = local.eip_pool_associated_count <= local.eip_count_needed - error_message = < config } - name = join("-", [var.friendly_name, join("-", each.value.name)]) + name_prefix = join("-", [var.friendly_name, join("-", each.value.name), ""]) vpc_id = data.aws_subnet.subnet.vpc_id description = format("%s - %s ingress access", var.friendly_name, join(" ", each.value.name)) @@ -72,4 +72,8 @@ resource "aws_security_group" "dsf_agent_sg" { } tags = merge(var.tags, { Name = join("-", [var.friendly_name, join("-", each.value.name)]) }) -} \ No newline at end of file + + lifecycle { + create_before_destroy = true + } +}