Skip to content

Commit

Permalink
Merge pull request #18807 from geofflancaster/subnet-nacl-association
Browse files Browse the repository at this point in the history
Subnet nacl association
  • Loading branch information
ewbankkit authored Feb 8, 2022
2 parents f1fa948 + f2aee7c commit de6e94d
Show file tree
Hide file tree
Showing 19 changed files with 2,376 additions and 2,363 deletions.
3 changes: 3 additions & 0 deletions .changelog/18807.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_network_acl_association
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,7 @@ func Provider() *schema.Provider {
"aws_main_route_table_association": ec2.ResourceMainRouteTableAssociation(),
"aws_nat_gateway": ec2.ResourceNATGateway(),
"aws_network_acl": ec2.ResourceNetworkACL(),
"aws_network_acl_association": ec2.ResourceNetworkACLAssociation(),
"aws_network_acl_rule": ec2.ResourceNetworkACLRule(),
"aws_network_interface": ec2.ResourceNetworkInterface(),
"aws_network_interface_attachment": ec2.ResourceNetworkInterfaceAttachment(),
Expand Down
298 changes: 61 additions & 237 deletions internal/service/ec2/default_network_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import (
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
Expand All @@ -23,33 +21,51 @@ const (
)

func ResourceDefaultNetworkACL() *schema.Resource {
networkACLRuleSetSchema := &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: networkACLRuleResource,
Set: networkACLRuleHash,
}

return &schema.Resource{
Create: resourceDefaultNetworkACLCreate,
// We reuse aws_network_acl's read method, the operations are the same
Read: resourceNetworkACLRead,
Delete: resourceDefaultNetworkACLDelete,
Update: resourceDefaultNetworkACLUpdate,
Delete: resourceDefaultNetworkACLDelete,

Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("default_network_acl_id", d.Id())

return []*schema.ResourceData{d}, nil
},
},

// Keep in sync with aws_network_acl's schema with the following changes:
// - egress and ingress are not Computed and don't have "Attributes as Blocks" processing mode set
// - subnet_ids is not Computed
// and additions:
// - default_network_acl_id Required/ForceNew
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
"default_network_acl_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
// We want explicit management of Rules here, so we do not allow them to be
// computed. Instead, an empty config will enforce just that; removal of the
// rules
"egress": networkACLRuleSetSchema,
"ingress": networkACLRuleSetSchema,
"owner_id": {
Type: schema.TypeString,
Computed: true,
},
// We want explicit management of Subnets here, so we do not allow them to be
// computed. Instead, an empty config will enforce just that; removal of the
// any Subnets that have been assigned to the Default Network ACL. Because we
Expand All @@ -61,122 +77,9 @@ func ResourceDefaultNetworkACL() *schema.Resource {
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
// We want explicit management of Rules here, so we do not allow them to be
// computed. Instead, an empty config will enforce just that; removal of the
// rules
"ingress": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IsPortNumberOrZero,
},
"to_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IsPortNumberOrZero,
},
"rule_no": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 32766),
},
"action": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
ec2.RuleActionAllow,
ec2.RuleActionDeny,
}, true),
},
"protocol": {
Type: schema.TypeString,
Required: true,
},
"cidr_block": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsCIDR,
},
"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsCIDR,
},
"icmp_type": {
Type: schema.TypeInt,
Optional: true,
},
"icmp_code": {
Type: schema.TypeInt,
Optional: true,
},
},
},
Set: resourceNetworkACLEntryHash,
},
"egress": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"from_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IsPortNumberOrZero,
},
"to_port": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IsPortNumberOrZero,
},
"rule_no": {
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(1, 32766),
},
"action": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
ec2.RuleActionAllow,
ec2.RuleActionDeny,
}, true),
},
"protocol": {
Type: schema.TypeString,
Required: true,
},
"cidr_block": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsCIDR,
},
"ipv6_cidr_block": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsCIDR,
},
"icmp_type": {
Type: schema.TypeInt,
Optional: true,
},
"icmp_code": {
Type: schema.TypeInt,
Optional: true,
},
},
},
Set: resourceNetworkACLEntryHash,
},

"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),

"owner_id": {
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
Expand All @@ -187,144 +90,65 @@ func ResourceDefaultNetworkACL() *schema.Resource {
}

func resourceDefaultNetworkACLCreate(d *schema.ResourceData, meta interface{}) error {
d.SetId(d.Get("default_network_acl_id").(string))

// revoke all default and pre-existing rules on the default network acl.
// In the UPDATE method, we'll apply only the rules in the configuration.
log.Printf("[DEBUG] Revoking default ingress and egress rules for Default Network ACL for %s", d.Id())
err := revokeAllNetworkACLEntries(d.Id(), meta)
if err != nil {
return err
}

return resourceDefaultNetworkACLUpdate(d, meta)
}

func resourceDefaultNetworkACLUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).EC2Conn

if d.HasChange("ingress") {
err := updateNetworkAclEntries(d, "ingress", conn)
if err != nil {
return err
}
}
naclID := d.Get("default_network_acl_id").(string)
nacl, err := FindNetworkACLByID(conn, naclID)

if d.HasChange("egress") {
err := updateNetworkAclEntries(d, "egress", conn)
if err != nil {
return err
}
if err != nil {
return fmt.Errorf("error reading EC2 Network ACL (%s): %w", d.Id(), err)
}

if d.HasChange("subnet_ids") {
o, n := d.GetChange("subnet_ids")
if o == nil {
o = new(schema.Set)
}
if n == nil {
n = new(schema.Set)
}

os := o.(*schema.Set)
ns := n.(*schema.Set)
if !aws.BoolValue(nacl.IsDefault) {
return fmt.Errorf("use the `aws_network_acl` resource instead")
}

remove := os.Difference(ns).List()
add := ns.Difference(os).List()
d.SetId(naclID)

if len(remove) > 0 {
//
// NO-OP
//
// Subnets *must* belong to a Network ACL. Subnets are not "removed" from
// Network ACLs, instead their association is replaced. In a normal
// Network ACL, any removal of a Subnet is done by replacing the
// Subnet/ACL association with an association between the Subnet and the
// Default Network ACL. Because we're managing the default here, we cannot
// do that, so we simply log a NO-OP. In order to remove the Subnet here,
// it must be destroyed, or assigned to different Network ACL. Those
// operations are not handled here
log.Printf("[WARN] Cannot remove subnets from the Default Network ACL. They must be re-assigned or destroyed")
}
// Revoke all default and pre-existing rules on the default network ACL.
if err := deleteNetworkAclEntries(conn, d.Id(), nacl.Entries); err != nil {
return err
}

if len(add) > 0 {
for _, a := range add {
association, err := findNetworkAclAssociation(a.(string), conn)
if err != nil {
return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), a, err)
}
log.Printf("[DEBUG] Updating Network Association for Default Network ACL (%s) and Subnet (%s)", d.Id(), a.(string))
_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
AssociationId: association.NetworkAclAssociationId,
NetworkAclId: aws.String(d.Id()),
})
if err != nil {
return err
}
}
}
if err := modifyNetworkACLAttributesOnCreate(conn, d); err != nil {
return err
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")
// Configure tags.
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig
newTags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))).IgnoreConfig(ignoreTagsConfig)
oldTags := KeyValueTags(nacl.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

if err := UpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating EC2 Default Network ACL (%s) tags: %s", d.Id(), err)
if !oldTags.Equal(newTags) {
if err := UpdateTags(conn, d.Id(), oldTags, newTags); err != nil {
return fmt.Errorf("error updating EC2 Default Network ACL (%s) tags: %w", d.Id(), err)
}
}

// Re-use the exiting Network ACL Resources READ method
return resourceNetworkACLRead(d, meta)
}

func resourceDefaultNetworkACLDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[WARN] Cannot destroy Default Network ACL. Terraform will remove this resource from the state file, however resources may remain.")
return nil
}

// revokeAllNetworkACLEntries revoke all ingress and egress rules that the Default
// Network ACL currently has
func revokeAllNetworkACLEntries(netaclId string, meta interface{}) error {
func resourceDefaultNetworkACLUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).EC2Conn

resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
NetworkAclIds: []*string{aws.String(netaclId)},
})

if err != nil {
log.Printf("[DEBUG] Error looking up Network ACL: %s", err)
// Subnets *must* belong to a Network ACL. Subnets are not "removed" from
// Network ACLs, instead their association is replaced. In a normal
// Network ACL, any removal of a Subnet is done by replacing the
// Subnet/ACL association with an association between the Subnet and the
// Default Network ACL. Because we're managing the default here, we cannot
// do that, so we simply log a NO-OP. In order to remove the Subnet here,
// it must be destroyed, or assigned to different Network ACL. Those
// operations are not handled here.
if err := modifyNetworkACLAttributesOnUpdate(conn, d, false); err != nil {
return err
}

if resp == nil {
return fmt.Errorf("Error looking up Default Network ACL Entries: No results")
}

networkAcl := resp.NetworkAcls[0]
for _, e := range networkAcl.Entries {
// Skip the default rules added by AWS. They can be neither
// configured or deleted by users. See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl
if aws.Int64Value(e.RuleNumber) == defaultACLRuleNumberIPv4 ||
aws.Int64Value(e.RuleNumber) == defaultACLRuleNumberIPv6 {
continue
}

// track if this is an egress or ingress rule, for logging purposes
rt := "ingress"
if aws.BoolValue(e.Egress) {
rt = "egress"
}
return resourceNetworkACLRead(d, meta)
}

log.Printf("[DEBUG] Destroying Network ACL (%s) Entry number (%d)", rt, int(*e.RuleNumber))
_, err := conn.DeleteNetworkAclEntry(&ec2.DeleteNetworkAclEntryInput{
NetworkAclId: aws.String(netaclId),
RuleNumber: e.RuleNumber,
Egress: e.Egress,
})
if err != nil {
return fmt.Errorf("Error deleting entry (%s): %s", e, err)
}
}
func resourceDefaultNetworkACLDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[WARN] EC2 Default Network ACL (%s) not deleted, removing from state", d.Id())

return nil
}
Loading

0 comments on commit de6e94d

Please sign in to comment.