Управление гранулированными сетевыми списками доступа с помощью terraform
Я делаю пробную работу с terraform, чтобы перенести в нее наш инфраструктурный код. Это мой второй день, и я чувствую, что делаю что-то крайне неправильное или пропускаю некоторые моменты, пытаясь настроить сетевые списки контроля доступа, потому что код очень быстро стал очень сложным и даже не решил все повторения.
Я попытался создать модуль network-acl-rule, который мог бы повторно использовать во всех средах. В настоящее время это выглядит так;
# modules/acl/main.tf
resource "aws_network_acl_rule" "acl-rule-example" {
network_acl_id = "${var.network_acl_id}"
count = "${length(var.cidrs) * length(var.rules)}"
rule_number = "${var.rule_number + count.index}"
from_port = "${element(var.all_acl_rules[var.rules[floor(count.index / length(var.cidrs))]], 0)}"
to_port = "${element(var.all_acl_rules[var.rules[floor(count.index / length(var.cidrs))]], 1)}"
egress = "${element(var.all_acl_rules[var.rules[floor(count.index / length(var.cidrs))]], 2)}"
protocol = "${element(var.all_acl_rules[var.rules[floor(count.index / length(var.cidrs))]], 3)}"
rule_action = "${element(var.all_acl_rules[var.rules[floor(count.index / length(var.cidrs))]], 4)}"
cidr_block = "${element(var.cidrs, count.index)}"
}
Я использую это со следующими переменными и объявлением модуля, чтобы вам было легче понять.
# variables.tf
variable "all_acl_rules" {
type = "map"
# [from_port, to_port, egress, protocol, action, description]
default = {
# ephemeral outbound
ephemeral_outbound = [1024, 65535, true, "tcp", "allow", "ephemeral-outbound"]
# basic inbounds
http_inbound = [80, 80, false, "tcp", "allow", "http-inbound"]
https_inbound = [443, 443, false, "tcp", "allow", "https-inbound"]
ssh_inbound = [22, 22, false, "tcp", "allow", "https-inbound"]
# :::
}
}
variable "cidr_blocks" {
type = "map"
default = {
"all" = ["0.0.0.0/0"],
"vpc" = ["10.0.0.0/8"],
"clients" = ["x.x.x.x/32", "x.x.x.x/32", "x.x.x.x/30"],
# :::
}
}
и ниже, как я называю модуль
# main.tf
module "clients-acl-rule" {
source = "modules/acl"
network_acl_id = "${aws_network_acl.public-acl.id}"
all_acl_rules = "${var.acl_rules}"
cidrs = "${var.cidr_blocks["clients"]}"
rules = ["http_inbound", "https_inbound", "ephemeral_outbound"]
rule_number = 20
}
У меня все в порядке с раздутой реализацией модуля, потому что она будет записана один раз и никогда больше не оглядывается на что-то вроде сетевого acl. Эта реализация хороша для группировки правил по некоторым блокам cidr. Но у него есть недостаток, заключающийся в том, что мне нужно повторять вызов модуля несколько раз для каждого отдельного блока cidr. Мне нужно правило, которое приведет к многократному дублированию.
В конце концов, я бы хотел добиться наличия модуля, который я могу сказать http_inbound для этих блоков cidr, ssh входящий для этих блоков cidr и эфемерный исходящий для всех видов гибкости vpc.
Я мог бы побороться за раздувание кода модуля немного больше, но мне показалось, что это неправильный способ сделать ACL. Возможно, более умные определения переменных с большим количеством дубликатов, а не дублированием при вызове модуля. Как люди решают подобные проблемы с терраформой?
1 ответ
До поддержки count
в модулях реализовано, у вас не так много вариантов. В прошлом я генерировал файлы.tf во время выполнения, используя другие инструменты сценариев (bash/python), чтобы обойти эту проблему DRY.
Terraform 0.12 поддерживает динамические вложенные блоки.
Например, вы используете это так:
resource "aws_network_acl" "public_tier" {
vpc_id = aws_vpc.my_vpc.id
subnet_ids = [for s in aws_subnet.public : s.id]
tags = {
Name = "my-nacl"
}
dynamic "egress" {
for_each = [for rule_obj in local.nacl_rules : {
port = rule_obj.port
rule_no = rule_obj.rule_num
cidr_block = rule_obj.cidr
}]
content {
protocol = "tcp"
rule_no = egress.value["rule_no"]
action = "allow"
cidr_block = egress.value["cidr_block"]
from_port = egress.value["port"]
to_port = egress.value["port"]
}
}
dynamic "ingress" {
for_each = [for rule_obj in local.nacl_rules : {
port = rule_obj.port
rule_no = rule_obj.rule_num
cidr_block = rule_obj.cidr
}]
content {
protocol = "tcp"
rule_no = ingress.value["rule_no"]
action = "allow"
cidr_block = ingress.value["cidr_block"]
from_port = ingress.value["port"]
to_port = ingress.value["port"]
}
}
}
locals {
nacl_rules = [
{ port : 22, rule_num : 100, cidr : "0.0.0.0/0" },
{ port : 80, rule_num : 110, cidr : "0.0.0.0/0" },
{ port : 443, rule_num : 120, cidr : "0.0.0.0/0" }
]
}
Обратите внимание, что вам может потребоваться добавить
egress
блок ниже:
egress{
protocol = "tcp"
rule_no = 300
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
Как упоминалось здесь:
Чтобы разрешить соединение со службой, работающей на экземпляре, связанный сетевой ACL должен разрешать как входящий трафик на порт, который прослушивает служба, так и исходящий трафик с эфемерных портов. Когда клиент подключается к серверу, случайный порт из временного диапазона портов (1024-65535) становится исходным портом клиента.
Мы можем в консоли, чтобы правило (*)DENY ALL добавлялось автоматически: