locals { # Addresses actually used, one per instance keycloak_ip = slice(var.keycloak_ip, 0, var.keycloak_data.vm_count) } # # Traffic between the cluster nodes (Infinispan/JGroups discovery and # replication). The addresses are static, so no rule depends on the instances # resource "openstack_networking_secgroup_v2" "keycloak_cluster_traffic" { name = "keycloak_cluster_traffic" delete_default_rules = "true" description = "Traffic between the nodes of the keycloak cluster" } resource "openstack_networking_secgroup_rule_v2" "cluster_tcp" { for_each = toset(local.keycloak_ip) security_group_id = openstack_networking_secgroup_v2.keycloak_cluster_traffic.id description = "TCP traffic from the keycloak node ${each.value}" direction = "ingress" ethertype = "IPv4" protocol = "tcp" remote_ip_prefix = "${each.value}/32" } resource "openstack_networking_secgroup_rule_v2" "cluster_udp" { for_each = toset(local.keycloak_ip) security_group_id = openstack_networking_secgroup_v2.keycloak_cluster_traffic.id description = "UDP traffic from the keycloak node ${each.value}" direction = "ingress" ethertype = "IPv4" protocol = "udp" remote_ip_prefix = "${each.value}/32" } resource "openstack_networking_secgroup_rule_v2" "cluster_egress" { for_each = toset(local.keycloak_ip) security_group_id = openstack_networking_secgroup_v2.keycloak_cluster_traffic.id description = "Traffic to the keycloak node ${each.value}" direction = "egress" ethertype = "IPv4" remote_ip_prefix = "${each.value}/32" } # JGroups can use multicast for the discovery resource "openstack_networking_secgroup_rule_v2" "cluster_igmp_ingress" { security_group_id = openstack_networking_secgroup_v2.keycloak_cluster_traffic.id description = "Ingress IGMP traffic between the keycloak nodes" direction = "ingress" ethertype = "IPv4" protocol = "igmp" remote_ip_prefix = "0.0.0.0/0" } resource "openstack_networking_secgroup_rule_v2" "cluster_igmp_egress" { security_group_id = openstack_networking_secgroup_v2.keycloak_cluster_traffic.id description = "Egress IGMP traffic between the keycloak nodes" direction = "egress" ethertype = "IPv4" protocol = "igmp" remote_ip_prefix = "0.0.0.0/0" } # # Traffic from the main L7 load balancers and from Prometheus # resource "openstack_networking_secgroup_v2" "traffic_to_keycloak" { name = "traffic_to_keycloak_from_the_main_load_balancers" delete_default_rules = "true" description = "Traffic from the main L7 HAPROXY load balancers to keycloak" } resource "openstack_networking_secgroup_rule_v2" "haproxy_to_keycloak_https" { for_each = toset(var.haproxy_l7_ip) security_group_id = openstack_networking_secgroup_v2.traffic_to_keycloak.id description = "HTTPS traffic from the HAPROXY L7 ${each.value}" direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = var.keycloak_data.https_port port_range_max = var.keycloak_data.https_port remote_ip_prefix = "${each.value}/32" } resource "openstack_networking_secgroup_rule_v2" "haproxy_to_keycloak_management" { for_each = toset(var.haproxy_l7_ip) security_group_id = openstack_networking_secgroup_v2.traffic_to_keycloak.id description = "Traffic from the HAPROXY L7 ${each.value} to the management port" direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = var.keycloak_data.management_port port_range_max = var.keycloak_data.management_port remote_ip_prefix = "${each.value}/32" } resource "openstack_networking_secgroup_rule_v2" "prometheus_to_keycloak_metrics" { security_group_id = openstack_networking_secgroup_v2.traffic_to_keycloak.id description = "Requests from Prometheus to the management port, that serves the metrics" direction = "ingress" ethertype = "IPv4" protocol = "tcp" port_range_min = var.keycloak_data.management_port port_range_max = var.keycloak_data.management_port remote_ip_prefix = var.prometheus_cidr } # # Hard anti affinity: the two instances never share a hypervisor # resource "openstack_compute_servergroup_v2" "keycloak" { name = var.keycloak_data.srv_name policies = [var.keycloak_data.affinity_policy] } # # Ports. One on the main private network, one on the dedicated network of the # database: keycloak reaches PostgreSQL only through the latter # resource "openstack_networking_port_v2" "keycloak_main_port" { count = var.keycloak_data.vm_count name = format("%s-%02d-main-port", var.keycloak_data.srv_name, count.index + 1) description = "Port of the keycloak instance on the main private network" admin_state_up = true network_id = var.main_private_network_id security_group_ids = [ var.default_security_group_id, openstack_networking_secgroup_v2.keycloak_cluster_traffic.id, openstack_networking_secgroup_v2.traffic_to_keycloak.id, ] fixed_ip { subnet_id = var.main_private_subnet_id ip_address = local.keycloak_ip[count.index] } } resource "openstack_networking_port_v2" "keycloak_postgresql_port" { count = var.keycloak_data.vm_count name = format("%s-%02d-postgresql-port", var.keycloak_data.srv_name, count.index + 1) description = "Port of the keycloak instance on the dedicated network of the database" admin_state_up = true network_id = var.postgresql_network_id security_group_ids = [var.postgresql_client_security_group_id] # The address comes from the allocation pool of the dedicated subnet fixed_ip { subnet_id = var.postgresql_subnet_id } } # # Instances # resource "openstack_compute_instance_v2" "keycloak" { count = var.keycloak_data.vm_count name = format("%s-%02d", var.keycloak_data.srv_name, count.index + 1) availability_zone_hints = var.availability_zone flavor_name = var.keycloak_data.flavor key_pair = var.ssh_key_name scheduler_hints { group = openstack_compute_servergroup_v2.keycloak.id } block_device { uuid = var.image.uuid source_type = "image" volume_size = var.keycloak_data.boot_vol_size boot_index = 0 destination_type = "volume" delete_on_termination = false } network { port = openstack_networking_port_v2.keycloak_main_port[count.index].id } network { port = openstack_networking_port_v2.keycloak_postgresql_port[count.index].id } user_data = file(var.image.user_data_file) # Do not replace the instance when the ssh key or the user data change lifecycle { ignore_changes = [ key_pair, user_data, network ] } } # # Optional CNAMEs, pointing to the load balancer that publishes the service # resource "openstack_dns_recordset_v2" "keycloak_recordset" { for_each = var.keycloak_recordsets zone_id = var.dns_zone_id name = each.value.name description = each.value.description ttl = 8600 type = "CNAME" records = [var.keycloak_cname_target] }