github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_default_network_acl.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/aws/aws-sdk-go/aws"
     8  	"github.com/aws/aws-sdk-go/service/ec2"
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  )
    11  
    12  // ACL Network ACLs all contain explicit deny-all rules that cannot be
    13  // destroyed or changed by users. This rules are numbered very high to be a
    14  // catch-all.
    15  // See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl
    16  const (
    17  	awsDefaultAclRuleNumberIpv4 = 32767
    18  	awsDefaultAclRuleNumberIpv6 = 32768
    19  )
    20  
    21  func resourceAwsDefaultNetworkAcl() *schema.Resource {
    22  	return &schema.Resource{
    23  		Create: resourceAwsDefaultNetworkAclCreate,
    24  		// We reuse aws_network_acl's read method, the operations are the same
    25  		Read:   resourceAwsNetworkAclRead,
    26  		Delete: resourceAwsDefaultNetworkAclDelete,
    27  		Update: resourceAwsDefaultNetworkAclUpdate,
    28  
    29  		Schema: map[string]*schema.Schema{
    30  			"vpc_id": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Computed: true,
    33  			},
    34  			"default_network_acl_id": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Required: true,
    37  				ForceNew: true,
    38  				Computed: false,
    39  			},
    40  			// We want explicit management of Subnets here, so we do not allow them to be
    41  			// computed. Instead, an empty config will enforce just that; removal of the
    42  			// any Subnets that have been assigned to the Default Network ACL. Because we
    43  			// can't actually remove them, this will be a continual plan until the
    44  			// Subnets are themselves destroyed or reassigned to a different Network
    45  			// ACL
    46  			"subnet_ids": &schema.Schema{
    47  				Type:     schema.TypeSet,
    48  				Optional: true,
    49  				Elem:     &schema.Schema{Type: schema.TypeString},
    50  				Set:      schema.HashString,
    51  			},
    52  			// We want explicit management of Rules here, so we do not allow them to be
    53  			// computed. Instead, an empty config will enforce just that; removal of the
    54  			// rules
    55  			"ingress": &schema.Schema{
    56  				Type:     schema.TypeSet,
    57  				Required: false,
    58  				Optional: true,
    59  				Elem: &schema.Resource{
    60  					Schema: map[string]*schema.Schema{
    61  						"from_port": &schema.Schema{
    62  							Type:     schema.TypeInt,
    63  							Required: true,
    64  						},
    65  						"to_port": &schema.Schema{
    66  							Type:     schema.TypeInt,
    67  							Required: true,
    68  						},
    69  						"rule_no": &schema.Schema{
    70  							Type:     schema.TypeInt,
    71  							Required: true,
    72  						},
    73  						"action": &schema.Schema{
    74  							Type:     schema.TypeString,
    75  							Required: true,
    76  						},
    77  						"protocol": &schema.Schema{
    78  							Type:     schema.TypeString,
    79  							Required: true,
    80  						},
    81  						"cidr_block": &schema.Schema{
    82  							Type:     schema.TypeString,
    83  							Optional: true,
    84  						},
    85  						"icmp_type": &schema.Schema{
    86  							Type:     schema.TypeInt,
    87  							Optional: true,
    88  						},
    89  						"icmp_code": &schema.Schema{
    90  							Type:     schema.TypeInt,
    91  							Optional: true,
    92  						},
    93  					},
    94  				},
    95  				Set: resourceAwsNetworkAclEntryHash,
    96  			},
    97  			"egress": &schema.Schema{
    98  				Type:     schema.TypeSet,
    99  				Required: false,
   100  				Optional: true,
   101  				Elem: &schema.Resource{
   102  					Schema: map[string]*schema.Schema{
   103  						"from_port": &schema.Schema{
   104  							Type:     schema.TypeInt,
   105  							Required: true,
   106  						},
   107  						"to_port": &schema.Schema{
   108  							Type:     schema.TypeInt,
   109  							Required: true,
   110  						},
   111  						"rule_no": &schema.Schema{
   112  							Type:     schema.TypeInt,
   113  							Required: true,
   114  						},
   115  						"action": &schema.Schema{
   116  							Type:     schema.TypeString,
   117  							Required: true,
   118  						},
   119  						"protocol": &schema.Schema{
   120  							Type:     schema.TypeString,
   121  							Required: true,
   122  						},
   123  						"cidr_block": &schema.Schema{
   124  							Type:     schema.TypeString,
   125  							Optional: true,
   126  						},
   127  						"icmp_type": &schema.Schema{
   128  							Type:     schema.TypeInt,
   129  							Optional: true,
   130  						},
   131  						"icmp_code": &schema.Schema{
   132  							Type:     schema.TypeInt,
   133  							Optional: true,
   134  						},
   135  					},
   136  				},
   137  				Set: resourceAwsNetworkAclEntryHash,
   138  			},
   139  
   140  			"tags": tagsSchema(),
   141  		},
   142  	}
   143  }
   144  
   145  func resourceAwsDefaultNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
   146  	d.SetId(d.Get("default_network_acl_id").(string))
   147  
   148  	// revoke all default and pre-existing rules on the default network acl.
   149  	// In the UPDATE method, we'll apply only the rules in the configuration.
   150  	log.Printf("[DEBUG] Revoking default ingress and egress rules for Default Network ACL for %s", d.Id())
   151  	err := revokeAllNetworkACLEntries(d.Id(), meta)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	return resourceAwsDefaultNetworkAclUpdate(d, meta)
   157  }
   158  
   159  func resourceAwsDefaultNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
   160  	conn := meta.(*AWSClient).ec2conn
   161  	d.Partial(true)
   162  
   163  	if d.HasChange("ingress") {
   164  		err := updateNetworkAclEntries(d, "ingress", conn)
   165  		if err != nil {
   166  			return err
   167  		}
   168  	}
   169  
   170  	if d.HasChange("egress") {
   171  		err := updateNetworkAclEntries(d, "egress", conn)
   172  		if err != nil {
   173  			return err
   174  		}
   175  	}
   176  
   177  	if d.HasChange("subnet_ids") {
   178  		o, n := d.GetChange("subnet_ids")
   179  		if o == nil {
   180  			o = new(schema.Set)
   181  		}
   182  		if n == nil {
   183  			n = new(schema.Set)
   184  		}
   185  
   186  		os := o.(*schema.Set)
   187  		ns := n.(*schema.Set)
   188  
   189  		remove := os.Difference(ns).List()
   190  		add := ns.Difference(os).List()
   191  
   192  		if len(remove) > 0 {
   193  			//
   194  			// NO-OP
   195  			//
   196  			// Subnets *must* belong to a Network ACL. Subnets are not "removed" from
   197  			// Network ACLs, instead their association is replaced. In a normal
   198  			// Network ACL, any removal of a Subnet is done by replacing the
   199  			// Subnet/ACL association with an association between the Subnet and the
   200  			// Default Network ACL. Because we're managing the default here, we cannot
   201  			// do that, so we simply log a NO-OP. In order to remove the Subnet here,
   202  			// it must be destroyed, or assigned to different Network ACL. Those
   203  			// operations are not handled here
   204  			log.Printf("[WARN] Cannot remove subnets from the Default Network ACL. They must be re-assigned or destroyed")
   205  		}
   206  
   207  		if len(add) > 0 {
   208  			for _, a := range add {
   209  				association, err := findNetworkAclAssociation(a.(string), conn)
   210  				if err != nil {
   211  					return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), a, err)
   212  				}
   213  				log.Printf("[DEBUG] Updating Network Association for Default Network ACL (%s) and Subnet (%s)", d.Id(), a.(string))
   214  				_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   215  					AssociationId: association.NetworkAclAssociationId,
   216  					NetworkAclId:  aws.String(d.Id()),
   217  				})
   218  				if err != nil {
   219  					return err
   220  				}
   221  			}
   222  		}
   223  	}
   224  
   225  	if err := setTags(conn, d); err != nil {
   226  		return err
   227  	} else {
   228  		d.SetPartial("tags")
   229  	}
   230  
   231  	d.Partial(false)
   232  	// Re-use the exiting Network ACL Resources READ method
   233  	return resourceAwsNetworkAclRead(d, meta)
   234  }
   235  
   236  func resourceAwsDefaultNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
   237  	log.Printf("[WARN] Cannot destroy Default Network ACL. Terraform will remove this resource from the state file, however resources may remain.")
   238  	d.SetId("")
   239  	return nil
   240  }
   241  
   242  // revokeAllNetworkACLEntries revoke all ingress and egress rules that the Default
   243  // Network ACL currently has
   244  func revokeAllNetworkACLEntries(netaclId string, meta interface{}) error {
   245  	conn := meta.(*AWSClient).ec2conn
   246  
   247  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   248  		NetworkAclIds: []*string{aws.String(netaclId)},
   249  	})
   250  
   251  	if err != nil {
   252  		log.Printf("[DEBUG] Error looking up Network ACL: %s", err)
   253  		return err
   254  	}
   255  
   256  	if resp == nil {
   257  		return fmt.Errorf("[ERR] Error looking up Default Network ACL Entries: No results")
   258  	}
   259  
   260  	networkAcl := resp.NetworkAcls[0]
   261  	for _, e := range networkAcl.Entries {
   262  		// Skip the default rules added by AWS. They can be neither
   263  		// configured or deleted by users. See http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_ACLs.html#default-network-acl
   264  		if *e.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
   265  			*e.RuleNumber == awsDefaultAclRuleNumberIpv6 {
   266  			continue
   267  		}
   268  
   269  		// track if this is an egress or ingress rule, for logging purposes
   270  		rt := "ingress"
   271  		if *e.Egress == true {
   272  			rt = "egress"
   273  		}
   274  
   275  		log.Printf("[DEBUG] Destroying Network ACL (%s) Entry number (%d)", rt, int(*e.RuleNumber))
   276  		_, err := conn.DeleteNetworkAclEntry(&ec2.DeleteNetworkAclEntryInput{
   277  			NetworkAclId: aws.String(netaclId),
   278  			RuleNumber:   e.RuleNumber,
   279  			Egress:       e.Egress,
   280  		})
   281  		if err != nil {
   282  			return fmt.Errorf("Error deleting entry (%s): %s", e, err)
   283  		}
   284  	}
   285  
   286  	return nil
   287  }