diff --git a/examples/azure/poc/dsf_deployment/dam.tf b/examples/azure/poc/dsf_deployment/dam.tf new file mode 100644 index 000000000..2aa7b4479 --- /dev/null +++ b/examples/azure/poc/dsf_deployment/dam.tf @@ -0,0 +1,86 @@ +locals { +# agent_gw_count = var.enable_dam ? var.agent_gw_count : 0 +# gateway_group_name = "temporaryGatewayGroup" +# create_agent_gw_cluster = local.agent_gw_count >= 2 ? 1 : 0 +} + +# TODO sivan - fix all relative modules paths +module "mx" { + source = "../../../../modules/azurerm/mx" + count = var.enable_dam ? 1 : 0 + + friendly_name = join("-", [local.deployment_name_salted, "mx"]) + resource_group = local.resource_group + dam_version = var.dam_version + subnet_id = module.network[0].vnet_subnets[0] +# license = var.dam_license + ssh_key = { + ssh_public_key = tls_private_key.ssh_key.public_key_openssh + ssh_private_key_file_path = local_sensitive_file.ssh_key.filename + } +# secure_password = local.password +# mx_password = local.password + allowed_web_console_and_api_cidrs = var.web_console_cidr + allowed_agent_gw_cidrs = module.network[0].vnet_address_space + allowed_ssh_cidrs = local.workstation_cidr + allowed_hub_cidrs = module.network[0].vnet_address_space + +# hub_details = var.enable_sonar ? { +# address = coalesce(module.hub_main[0].public_dns, module.hub_main[0].private_dns) +# access_token = module.hub_main[0].access_tokens["archiver"].token +# port = 8443 +# } : null + attach_persistent_public_ip = true +# large_scale_mode = var.large_scale_mode.mx + +# create_server_group = length(var.simulation_db_types_for_agent) > 0 + tags = local.tags + # TODO sivan - remove and test +# send_usage_statistics = false + depends_on = [ + module.network + ] +} + +#module "agent_gw" { +# source = "../../../../modules/aws/agent-gw" +# count = local.agent_gw_count +# +# friendly_name = join("-", [local.deployment_name_salted, "agent", "gw", count.index]) +# dam_version = var.dam_version +# ebs = var.agent_gw_ebs_details +# subnet_id = local.agent_gw_subnet_id +# key_pair = module.key_pair.key_pair.key_pair_name +# secure_password = local.password +# mx_password = local.password +# allowed_agent_cidrs = [data.aws_subnet.agent_gw.cidr_block] +# allowed_mx_cidrs = [data.aws_subnet.mx.cidr_block] +# allowed_ssh_cidrs = [data.aws_subnet.mx.cidr_block] +# allowed_gw_clusters_cidrs = [data.aws_subnet.agent_gw.cidr_block] +# management_server_host_for_registration = module.mx[0].private_ip +# management_server_host_for_api_access = module.mx[0].public_ip +# large_scale_mode = var.large_scale_mode.agent_gw +# gateway_group_name = local.gateway_group_name +# tags = local.tags +# depends_on = [ +# module.vpc +# ] +#} +# +#module "agent_gw_cluster_setup" { +# source = "../../../../modules/null/agent-gw-cluster-setup" +# count = local.create_agent_gw_cluster +# +# cluster_name = join("-", [local.deployment_name_salted, "agent", "gw", "cluster"]) +# gateway_group_name = local.gateway_group_name +# mx_details = { +# address = module.mx[0].public_ip +# port = 8083 +# user = module.mx[0].web_console_user +# password = local.password +# } +# depends_on = [ +# module.agent_gw, +# module.mx +# ] +#} diff --git a/examples/azure/poc/dsf_deployment/outputs.tf b/examples/azure/poc/dsf_deployment/outputs.tf index 76d1ee26a..04a9ff81f 100644 --- a/examples/azure/poc/dsf_deployment/outputs.tf +++ b/examples/azure/poc/dsf_deployment/outputs.tf @@ -60,6 +60,24 @@ output "sonar" { } : null } +output "dam" { + value = var.enable_dam ? { + mx = { + public_ip = try(module.mx[0].public_ip, null) + private_ip = try(module.mx[0].private_ip, null) + display_name = try(module.mx[0].display_name, null) + principal_id = try(module.mx[0].principal_id, null) + ssh_command = try("ssh -i ${local.private_key_file_path} ${module.mx[0].ssh_user}@${module.mx[0].public_ip}", null) + public_url = try(join("", ["https://", module.mx[0].public_ip, ":8083/"]), null) + private_url = try(join("", ["https://", module.mx[0].private_ip, ":8083/"]), null) + password = nonsensitive(local.password) + user = module.mx[0].web_console_user +# large_scale_mode = module.mx[0].large_scale_mode + } + # TODO sivan add GWs + } : null +} + output "web_console_dsf_hub" { value = try({ user = module.hub_main[0].web_console_user @@ -68,3 +86,8 @@ output "web_console_dsf_hub" { private_url = join("", ["https://", module.hub_main[0].private_ip, ":8443/"]) }, null) } + +# TODO sivan - remove +output "dam_vm_image" { + value = module.mx[0].vm_image +} \ No newline at end of file diff --git a/examples/azure/poc/dsf_deployment/variables.tf b/examples/azure/poc/dsf_deployment/variables.tf index 1f3ce8d36..6cbeb6408 100644 --- a/examples/azure/poc/dsf_deployment/variables.tf +++ b/examples/azure/poc/dsf_deployment/variables.tf @@ -6,13 +6,13 @@ variable "tags" { variable "resource_group" { type = string - description = "Azure exisiting resource group. Keep empty if you wish to create a new resource group" + description = "Azure existing resource group. Keep empty if you wish to create a new resource group" default = null } variable "resource_group_location" { type = string - description = "In case var.resource_group is not provided and a new resource group is created. It will be created in this location (e.g 'East US')" + description = "In case var.resource_group is not provided and a new resource group is created, the new resource group will be created in this location (e.g 'East US'). The resource group location can be different from the Blob location defined via 'tarball_location' variable)" default = null } @@ -28,12 +28,24 @@ variable "enable_sonar" { description = "Provision DSF Hub and Agentless Gateways (formerly Sonar). To provision only a DSF Hub, set agentless_gw_count to 0." } +variable "enable_dam" { + type = bool + default = true + description = "Provision DAM MX and Agent Gateways" +} + variable "agentless_gw_count" { type = number default = 1 description = "Number of Agentless Gateways. Provisioning Agentless Gateways requires the enable_sonar variable to be set to 'true'." } +variable "agent_gw_count" { + type = number + default = 2 # Minimum count for a cluster + description = "Number of Agent Gateways. Provisioning Agent Gateways requires the enable_dam variable to be set to 'true'." +} + variable "password" { sensitive = true type = string @@ -81,6 +93,46 @@ variable "subnet_ids" { } } + +############################## +#### DAM variables #### +############################## + +variable "dam_version" { + type = string + description = "The DAM version to install" + default = "14.13.1.10" + validation { + condition = can(regex("^(\\d{1,2}\\.){3}\\d{1,2}$", var.dam_version)) + error_message = "Version must be in the format dd.dd.dd.dd where each dd is a number between 1-99 (e.g 14.10.1.10)" + } +} + +variable "dam_license" { + description = < config if length(config.cidrs) > 0 } + content { + name = join("-", [var.name, "tcp", join("-", security_rule.value.name)]) + priority = 100 + security_rule.key + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_ranges = security_rule.value.tcp + # Azure doesn't allow overlapping cidr blocks in a single rule. that's what the code below fixes + source_address_prefixes = [for k, v in { for v in security_rule.value.cidrs : v => { + cidr = v, + min_ip_int = (tonumber(split(".", cidrhost(v, 0))[0]) * pow(256, 3)) + (tonumber(split(".", cidrhost(v, 0))[1]) * pow(256, 2)) + (tonumber(split(".", cidrhost(v, 0))[2]) * pow(256, 1)) + tonumber(split(".", cidrhost(v, 0))[3]) + max_ip_int = (tonumber(split(".", cidrhost(v, -1))[0]) * pow(256, 3)) + (tonumber(split(".", cidrhost(v, -1))[1]) * pow(256, 2)) + (tonumber(split(".", cidrhost(v, -1))[2]) * pow(256, 1)) + tonumber(split(".", cidrhost(v, -1))[3]) + } } : v.cidr if !anytrue([for i in { for v in security_rule.value.cidrs : v => { + cidr = v, + min_ip_int = (tonumber(split(".", cidrhost(v, 0))[0]) * pow(256, 3)) + (tonumber(split(".", cidrhost(v, 0))[1]) * pow(256, 2)) + (tonumber(split(".", cidrhost(v, 0))[2]) * pow(256, 1)) + tonumber(split(".", cidrhost(v, 0))[3]) + max_ip_int = (tonumber(split(".", cidrhost(v, -1))[0]) * pow(256, 3)) + (tonumber(split(".", cidrhost(v, -1))[1]) * pow(256, 2)) + (tonumber(split(".", cidrhost(v, -1))[2]) * pow(256, 1)) + tonumber(split(".", cidrhost(v, -1))[3]) + } } : v.max_ip_int <= i.max_ip_int && v.min_ip_int >= i.min_ip_int if v.cidr != i.cidr])] + destination_address_prefix = "*" + } + } + tags = var.tags +} \ No newline at end of file diff --git a/modules/azurerm/dam-base-instance/userdata.tf b/modules/azurerm/dam-base-instance/userdata.tf new file mode 100644 index 000000000..7bfa31e10 --- /dev/null +++ b/modules/azurerm/dam-base-instance/userdata.tf @@ -0,0 +1,62 @@ +locals { + display_name = var.name + +# userdata = <<-EOF +# ${var.user_data_commands} +# EOF +} +# +#module "statistics" { +# source = "../../../modules/aws/statistics" +# count = var.send_usage_statistics ? 1 : 0 +# +# deployment_name = var.name +# product = "DAM" +# resource_type = var.resource_type +# artifact = join("@", compact(["ami://${sha256(data.aws_ami.selected-ami.image_id)}", var.ami != null ? null : var.dam_version])) +#} +# +#resource "null_resource" "readiness" { +# count = var.instance_readiness_params.enable == true ? 1 : 0 +# provisioner "local-exec" { +# interpreter = ["bash", "-c"] +# command = <<-EOF +# TIMEOUT=${var.instance_readiness_params.timeout} +# START=$(date +%s) +# +# operation() { +# ${var.instance_readiness_params.commands} +# } +# +# # Perform the operation in a loop until the timeout is reached +# while true; do +# # Check if the timeout has been reached +# NOW=$(date +%s) +# ELAPSED=$((NOW-START)) +# if [ $ELAPSED -gt $TIMEOUT ]; then +# echo "Timeout reached. To obtain additional information, refer to the /var/log/ec2_auto_ftl.log file located on the remote server." +# exit 1 +# fi +# +# operation +# +# sleep 60 +# done +# EOF +# } +# +# triggers = { +# instance_id = aws_instance.dsf_base_instance.id +# commands = var.instance_readiness_params.commands +# } +# depends_on = [module.statistics] +#} +# +#module "statistics_success" { +# source = "../../../modules/aws/statistics" +# count = var.send_usage_statistics ? 1 : 0 +# +# id = module.statistics[0].id +# status = "success" +# depends_on = [null_resource.readiness] +#} diff --git a/modules/azurerm/dam-base-instance/variables.tf b/modules/azurerm/dam-base-instance/variables.tf new file mode 100644 index 000000000..d5f757352 --- /dev/null +++ b/modules/azurerm/dam-base-instance/variables.tf @@ -0,0 +1,111 @@ +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) +} + +variable "resource_group" { + type = object({ + name = string + location = string + }) + description = "Resource group details" +} + +variable "name" { + type = string +} + +variable "subnet_id" { + type = string + description = "Subnet id for the DSF base instance" + validation { + condition = can(regex(".*Microsoft.Network/virtualNetworks/.*/subnets/.*", var.subnet_id)) + error_message = "The variable must match the pattern 'Microsoft.Network/virtualNetworks//subnets/'" + } +} + +variable "security_groups_config" { + description = "Security groups config" + type = list(object({ + name = list(string) + internet_access = bool + udp = list(number) + tcp = list(number) + cidrs = list(string) + })) +} + +variable "security_group_ids" { + type = list(string) + description = "security group ids to attach to the instance. If provided, no security groups are created and all allowed_*_cidrs variables are ignored." + validation { + condition = length(var.security_group_ids) == 0 || length(var.security_group_ids) == 1 + error_message = "Can't contain more than a single element" + } + default = [] +} + +variable "attach_persistent_public_ip" { + type = bool + description = "Create and attach elastic public IP for the instance" +} + +variable "public_ssh_key" { + type = string + description = "Key for the DSF base instance" +} + +variable "vm_user" { + type = string + description = "VM user to use for SSH." +} + +variable "vm_image" { + type = object({ + publisher = string + offer = string + sku = string + version = string + }) + description = "This variable is used for selecting an Azure DAM machine image. If set to null, the image will be determine according to dam_version variable." + default = null +} + +variable "dam_version" { + type = string + description = "The DAM version to install" + default = "14.13.1.10" + nullable = false + validation { + condition = can(regex("^(\\d{1,2}\\.){3}\\d{1,2}$", var.dam_version)) + error_message = "Version must be in the format dd.dd.dd.dd where each dd is a number between 1-99 (e.g 14.10.1.10)" + } +} + +variable "resource_type" { + type = string + validation { + condition = contains(["mx", "agent-gw"], var.resource_type) + error_message = "Allowed values for DSF node type: \"mx\", \"agent-gw\"" + } + nullable = false +} + +variable "dam_model" { + type = string + description = "Enter the Agent Gateway/MX Model. More info in https://www.imperva.com/resources/datasheets/Imperva_VirtualAppliances_V2.3_20220518.pdf" + validation { + condition = contains(["AV2500", "AV6500", "AVM150"], var.dam_model) + error_message = <= 3 + error_message = "Must be at least 3 characters long" + } + validation { + condition = can(regex("^\\p{L}.*", var.friendly_name)) + error_message = "Must start with a letter" + } +} + +variable "subnet_id" { + type = string + description = "Subnet id for the DSF base instance" + validation { + condition = can(regex(".*Microsoft.Network/virtualNetworks/.*/subnets/.*", var.subnet_id)) + error_message = "The variable must match the pattern 'Microsoft.Network/virtualNetworks//subnets/'" + } +} + +#variable "instance_profile_name" { +# type = string +# default = null +# description = "Instance profile to assign to the instance. Keep empty if you wish to create a new IAM role and profile" +#} +# +variable "security_group_ids" { + type = list(string) + description = "Security group ids to attach to the instance. If provided, no security groups are created and all allowed_*_cidrs variables are ignored." + validation { + condition = length(var.security_group_ids) == 0 || length(var.security_group_ids) == 1 + error_message = "Can't contain more than a single element" + } + validation { + condition = alltrue([for item in var.security_group_ids : can(regex(".*Microsoft.Network/networkSecurityGroups/.*", item))]) + error_message = "One or more of the security group ids list is invalid. Each item should match the pattern '.*Microsoft.Network/networkSecurityGroups/" + } + default = [] +} + +variable "allowed_agent_gw_cidrs" { + type = list(string) + description = "List of ingress CIDR patterns allowing Agent Gateways to access the DSF MX instance" + validation { + condition = alltrue([for item in var.allowed_agent_gw_cidrs : can(cidrnetmask(item))]) + error_message = "Each item of this list must be in a valid CIDR block format. For example: [\"10.106.108.0/25\"]" + } + default = [] +} + +variable "allowed_ssh_cidrs" { + type = list(string) + description = "List of ingress CIDR patterns allowing ssh access" + validation { + condition = alltrue([for item in var.allowed_ssh_cidrs : can(cidrnetmask(item))]) + error_message = "Each item of this list must be in a valid CIDR block format. For example: [\"10.106.108.0/25\"]" + } + default = [] +} + +variable "allowed_web_console_and_api_cidrs" { + type = list(string) + description = "List of ingress CIDR patterns allowing web console access" + validation { + condition = alltrue([for item in var.allowed_web_console_and_api_cidrs : can(cidrnetmask(item))]) + error_message = "Each item of this list must be in a valid CIDR block format. For example: [\"10.106.108.0/25\"]" + } + default = [] +} + +variable "allowed_hub_cidrs" { + type = list(string) + description = "List of ingress CIDR patterns allowing DSF Hub access" + validation { + condition = alltrue([for item in var.allowed_hub_cidrs : can(cidrnetmask(item))]) + error_message = "Each item of this list must be in a valid CIDR block format. For example: [\"10.106.108.0/25\"]" + } + default = [] +} + +variable "allowed_all_cidrs" { + type = list(string) + description = "List of ingress CIDR patterns allowing access to all relevant protocols (E.g vpc cidr range)" + validation { + condition = alltrue([for item in var.allowed_all_cidrs : can(cidrnetmask(item))]) + error_message = "Each item of this list must be in a valid CIDR block format. For example: [\"10.106.108.0/25\"]" + } + default = [] +} + +variable "ssh_key" { + type = object({ + ssh_public_key = string + ssh_private_key_file_path = string + }) + description = "SSH materials to access machine" + + nullable = false +} + +#variable "mx_password" { +# type = string +# description = "MX password" +# sensitive = true +# validation { +# condition = length(var.mx_password) >= 7 && length(var.mx_password) <= 14 +# error_message = "Password must be 7-14 characters long" +# } +# +# validation { +# condition = can(regex("[A-Z]", var.mx_password)) +# error_message = "Password must contain at least one uppercase letter" +# } +# +# validation { +# condition = can(regex("[a-z]", var.mx_password)) +# error_message = "Password must contain at least one lowercase letter" +# } +# +# validation { +# condition = can(regex("\\d", var.mx_password)) +# error_message = "Password must contain at least one digit" +# } +# +# validation { +# condition = can(regex("[*+=#%^:/~.,\\[\\]_]", var.mx_password)) +# error_message = "Password must contain at least one of the following special characters: *+=#%^:/~.,[]_" +# } +#} +# +#variable "secure_password" { +# type = string +# description = "The password used for communication between the Management Server and the Agent Gateway" +# sensitive = true +# validation { +# condition = length(var.secure_password) >= 7 && length(var.secure_password) <= 14 +# error_message = "Password must be 7-14 characters long" +# } +# +# validation { +# condition = can(regex("[A-Z]", var.secure_password)) +# error_message = "Password must contain at least one uppercase letter" +# } +# +# validation { +# condition = can(regex("[a-z]", var.secure_password)) +# error_message = "Password must contain at least one lowercase letter" +# } +# +# validation { +# condition = can(regex("\\d", var.secure_password)) +# error_message = "Password must contain at least one digit" +# } +# +# validation { +# condition = can(regex("[*+#%^:/~.,\\[\\]_]", var.secure_password)) +# error_message = "Password must contain at least one of the following special characters: *+=#%^:/~.,[]_" +# } +#} +# +#variable "timezone" { +# type = string +# default = "UTC" +#} +# + +variable "dam_version" { + type = string + description = "The DAM version to install" + validation { + condition = can(regex("^(\\d{1,2}\\.){3}\\d{1,2}$", var.dam_version)) + error_message = "Version must be in the format dd.dd.dd.dd where each dd is a number between 1-99 (e.g 14.10.1.10)." + } + validation { + condition = split(".", var.dam_version)[0] == "14" + error_message = "DAM version not supported." + } +} + +variable "vm_user" { + type = string + default = "adminuser" + description = "VM user to use for SSH. Keep empty to use the default user." +} + +variable "vm_image" { + type = object({ + publisher = string + offer = string + sku = string + version = string + }) + description = "This variable is used for selecting an Azure DAM machine image. If set to null, the image will be determine according to dam_version variable." + default = null +} + +variable "large_scale_mode" { + type = bool + description = "Large scale mode" + default = false +} + +#variable "license" { +# description = <