Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cycle dependency #76

Open
nabilbendafi opened this issue Dec 3, 2024 · 1 comment
Open

Cycle dependency #76

nabilbendafi opened this issue Dec 3, 2024 · 1 comment
Labels
bug 🐛 An issue with the system

Comments

@nabilbendafi
Copy link

Describe the Bug

Trying to move from https://github.com/terraform-aws-modules/terraform-aws-security-group, a simple declaration of Security Group rules for two new Security Group in order to:

  • allow egress (outbound) traffic on one side
  • allow ingress (inbound) traffic on one the other

creates a cycle and Terraform is not able to perform a plan.

Use case:

[EC2 with SG A]--> outbound port 22
[EC2 with SG B]<-- inbound port 22

(where setting allow_all_egress to true is not an option, from a security point of view)


terraform plan
╷
│ Error: Cycle: module.b.aws_security_group.default, module.b.local.all_ingress_rules (expand), module.b.local.sg_rules_lists (expand), module.b.local.sg_exploded_rules (expand), module.b.local.all_resource_rules (expand), module.b.local.keyed_resource_rules (expand), module.b.random_id.rule_change_forces_new_security_group, module.b.local.sg_name_prefix_forced (expand), module.b.local.sg_name_prefix (expand), module.a.aws_security_group.default, module.a.local.all_ingress_rules (expand), module.a.local.sg_rules_lists (expand), module.a.local.sg_exploded_rules (expand), module.a.local.all_resource_rules (expand), module.a.local.keyed_resource_rules (expand), module.a.random_id.rule_change_forces_new_security_group, module.a.local.sg_name_prefix_forced (expand), module.a.local.sg_name_prefix (expand), module.b.local.security_group_id (expand), module.b.output.id (expand), module.a.var.rules (expand), module.a.local.rules (expand), module.a.local.norm_rules (expand), module.a.local.all_inline_rules (expand), module.a.local.all_egress_rules (expand), module.a.aws_security_group.cbd, module.a.local.created_security_group (expand), module.a.local.security_group_id (expand), module.a.output.id (expand), module.b.var.rules (expand), module.b.local.rules (expand), module.b.local.norm_rules (expand), module.b.local.all_inline_rules (expand), module.b.local.all_egress_rules (expand), module.b.aws_security_group.cbd, module.b.local.created_security_group (expand)

Expected Behavior

Terraform plan finishes without error about cycle dependency.

Steps to Reproduce

Run

terraform init
terraform plan

with following code:

locals {
  ssh-tcp = {
    key         = "ssh-tcp"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    description = "SSH"
  }
}

module "a" {
  source  = "cloudposse/security-group/aws"
  version = "~> 2.2.0"

  create_before_destroy      = true  # Enforce default behavior
  preserve_security_group_id = false # Enforce default behavior
  tags                       = {}

  vpc_id = "vpc-0000"

  allow_all_egress = false

  rules = [
    merge({ type = "egress" }, local.ssh-tcp, { source_security_group_id = module.b.id })
  ]
}

module "b" {
  source  = "cloudposse/security-group/aws"
  version = "~> 2.2.0"

  create_before_destroy      = true  # Enforce default behavior
  preserve_security_group_id = false # Enforce default behavior
  tags                       = {}

  vpc_id = "vpc-0000"

  allow_all_egress = false

  rules = [
    merge({ type = "ingress" }, local.ssh-tcp, { source_security_group_id = module.a.id })
  ]
}

Screenshots

No response

Environment

Terraform v1.7.4
on darwin_arm64

  • provider registry.terraform.io/hashicorp/aws v5.49.0
  • provider registry.terraform.io/hashicorp/null v3.2.3
  • provider registry.terraform.io/hashicorp/random v3.6.3

Additional Context

Same "code" with terraform-aws-modules/security-group/aws implementation produces no error

locals {
  ssh-tcp = {
    key         = "ssh-tcp"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    description = "SSH"
  }
}

module "a" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "~> 5.1.0"

  name   = "a"
  vpc_id = "vpc-0000"

  egress_with_source_security_group_id = [
    merge(local.ssh-tcp, { source_security_group_id = module.b.security_group_id })
  ]
}

module "b" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "~> 5.1.0"

  name   = "b"
  vpc_id = "vpc-0000"

  ingress_with_source_security_group_id = [
    merge(local.ssh-tcp, { source_security_group_id = module.b.security_group_id })
  ]
}

and plan seems consistent with expected output

terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.a.aws_security_group.this_name_prefix[0] will be created
  + resource "aws_security_group" "this_name_prefix" {
      + arn                    = (known after apply)
      + description            = "Security Group managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = (known after apply)
      + name_prefix            = "a-"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "a"
        }
      + tags_all               = {
          + "Name" = "a"
        }
      + vpc_id                 = "vpc-0000"

      + timeouts {
          + create = "10m"
          + delete = "15m"
        }
    }

  # module.a.aws_security_group_rule.egress_with_source_security_group_id[0] will be created
  + resource "aws_security_group_rule" "egress_with_source_security_group_id" {
      + description              = "SSH"
      + from_port                = 22
      + id                       = (known after apply)
      + prefix_list_ids          = []
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + security_group_rule_id   = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "egress"
    }

  # module.b.aws_security_group.this_name_prefix[0] will be created
  + resource "aws_security_group" "this_name_prefix" {
      + arn                    = (known after apply)
      + description            = "Security Group managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = (known after apply)
      + name_prefix            = "b-"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "b"
        }
      + tags_all               = {
          + "Name" = "b"
        }
      + vpc_id                 = "vpc-0000"

      + timeouts {
          + create = "10m"
          + delete = "15m"
        }
    }

  # module.b.aws_security_group_rule.ingress_with_source_security_group_id[0] will be created
  + resource "aws_security_group_rule" "ingress_with_source_security_group_id" {
      + description              = "SSH"
      + from_port                = 22
      + id                       = (known after apply)
      + prefix_list_ids          = []
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + security_group_rule_id   = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

Plan: 4 to add, 0 to change, 0 to destroy.
@nabilbendafi nabilbendafi added the bug 🐛 An issue with the system label Dec 3, 2024
@vavdoshka
Copy link

This is causing this problem

dynamic "ingress" {
for_each = local.all_ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
description = ingress.value.description
cidr_blocks = ingress.value.cidr_blocks
ipv6_cidr_blocks = ingress.value.ipv6_cidr_blocks
prefix_list_ids = ingress.value.prefix_list_ids
security_groups = ingress.value.security_groups
self = ingress.value.self
}
}
dynamic "egress" {
for_each = local.all_egress_rules
content {
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
description = egress.value.description
cidr_blocks = egress.value.cidr_blocks
ipv6_cidr_blocks = egress.value.ipv6_cidr_blocks
prefix_list_ids = egress.value.prefix_list_ids
security_groups = egress.value.security_groups
self = egress.value.self
}
}

The fact that the module can handle inline, even if inline is disabled, that does not matter. Here is simplistic example to demonstrate that:

  1. Two invocation of the same module, passing cross-reference from the output
module "sg_1" {

    source = "./module"
    rules = [module.sg_2.id]
}

module "sg_2" {

    source = "./module"
    rules = [module.sg_1.id]
}
  1. module itself
variable "rules" {
    type = list(any)
  
}

resource "aws_security_group" "default" {

    dynamic "does_not_matter_what" {
        for_each = var.rules # it can be local behind some flag, it does not matter
        content {
            does_not_matter_what = ""
        }
    }
}

output "id" {
    value = aws_security_group.default.id 
  
}
  1. result
╷
│ Error: Cycle: module.sg_2.aws_security_group.default, module.sg_2.output.id (expand), module.sg_1.var.rules (expand), module.sg_1.aws_security_group.default, module.sg_1.output.id (expand), module.sg_2.var.rules (expand)
│ 
│ 
╵

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 An issue with the system
Projects
None yet
Development

No branches or pull requests

2 participants