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