Problem
When applying a Terraform configuration for an aws_security_group_rule with multiple CIDR blocks, the second apply may fail with a duplicate rule error.
Example configuration:
resource "aws_security_group_rule" "example" {
description = "Allow LDAP"
from_port = 636
protocol = "tcp"
security_group_id = aws_security_group.example.id
to_port = 636
cidr_blocks = ["18.0.0.0/24", "19.0.0.0/24", "17.0.0.0/24"]
type = "ingress"
}Error output:
│ Error:
│ [WARN] A duplicate Security Group rule was found on
│ (sg-0b7c95ffefd890db1).
│ This may be│ a side effect of a now-fixed Terraform issue causing two
│ security groups with│ identical attributes but different
│ source_security_group_ids to overwrite each│ other in the state.
│ See https://github.com/hashicorp/terraform/pull/2376 for
│ more│ information and instructions for recovery. Error:
│ InvalidPermission.Duplicate:
│ the specified rule "peer:
│ 19.0.0.0/24, TCP, from port:
│ 636, to port:
│ 636, ALLOW" already exists│ status code:
│ 400, request id: 6aadf5a9-4e06-46f1-9828-a3f881b60efe
│
│ with aws_security_group_rule.example,
│ on main.tf line 25,
│ in resource "aws_security_group_rule" "example":
│ 25: resource "aws_security_group_rule" "example" {}Cause
This error typically occurs when the security group rules have been modified manually in the AWS Console, causing a drift between the real-world infrastructure and the Terraform state file.
For example, if a rule with the CIDR block 17.0.0.0/24 was manually removed, Terraform detects that a rule is missing. Because all CIDR blocks are defined within a single resource, Terraform plans to destroy the existing resource and recreate it with all the defined rules. However, when it attempts to create the rule for 19.0.0.0/24, it fails because that rule still exists in AWS, resulting in the InvalidPermission.Duplicate error.
Solutions
Here are three approaches to resolve this issue.
Solution 1: Manually Re-align AWS and Terraform State
This approach involves manually editing the security group in the AWS Console to match what Terraform expects, allowing the next apply to succeed. You can either:
- Remove the entire security group from the AWS Console and run
terraform applyto recreate it from scratch. - Remove only the specific duplicate rules mentioned in the error from the AWS Console and run
terraform apply.
Solution 2: Import Existing Rules into Terraform State
This approach corrects the state drift by importing the existing AWS security group rules into your Terraform state file.
Using the terraform import command
Run the terraform import command with the correctly formatted resource ID.
$ terraform import 'aws_security_group_rule.example' 'sg-0e90ef2bf8c599bd9_ingress_tcp_636_636_19.0.0.0/24_18.0.0.0/24'
The resource ID is constructed from the following parts, separated by underscores (_):
- Security Group ID (e.g.,
sg-0e90ef2bf8c599bd9) - Rule type (e.g.,
ingress) - Protocol (e.g.,
tcp) - From Port (e.g.,
636) - To Port (e.g.,
636) - A list of all CIDR blocks, separated by underscores (e.g.,
19.0.0.0/24_18.0.0.0/24)
Using an import block
Alternatively, you can define an import block in your configuration.
import {
to = aws_security_group_rule.example
id = "sg-0e90ef2bf8c599bd9_ingress_tcp_636_636_19.0.0.0/24_18.0.0.0/24"
}Solution 3: Refactor Configuration to Prevent Future Issues (Recommended)
To prevent this problem, you can refactor your configuration to use the for_each meta-argument. This creates a separate, independent aws_security_group_rule resource for each CIDR block, which makes the configuration more resilient to manual changes.
-
Update your resource configuration to use
for_each.resource "aws_security_group_rule" "example" { for_each = toset(["18.0.0.0/24", "19.0.0.0/24"]) description = "Allow LDAP" from_port = 636 protocol = "tcp" security_group_id = aws_security_group.example.id to_port = 636 cidr_blocks = [each.value] type = "ingress" } -
Import the existing rules into the new
for_eachresource structure.Using the
terraform importcommand:$ terraform import 'aws_security_group_rule.example["19.0.0.0/24"]' 'sg-0fa4e55b76036112d_ingress_tcp_636_636_19.0.0.0/24' $ terraform import 'aws_security_group_rule.example["18.0.0.0/24"]' 'sg-0fa4e55b76036112d_ingress_tcp_636_636_18.0.0.0/24'
Using an
importblock:import { for_each = toset(["18.0.0.0/24", "19.0.0.0/24"]) to = aws_security_group_rule.example[each.value] id = "sg-0fa4e55b76036112d_ingress_tcp_636_636_${each.value}" }
As a best practice, consider using the more specific aws_vpc_security_group_ingress_rule and aws_vpc_security_group_egress_rule resources for ingress and egress rules, respectively.
Outcome
After applying one of the solutions, you will be able to run terraform apply successfully without encountering the duplicate rule error.