Управление гранулированными сетевыми списками доступа с помощью 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 добавлялось автоматически:

Другие вопросы по тегам