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

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/resource"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceAwsNetworkAcl() *schema.Resource {
    20  
    21  	return &schema.Resource{
    22  		Create: resourceAwsNetworkAclCreate,
    23  		Read:   resourceAwsNetworkAclRead,
    24  		Delete: resourceAwsNetworkAclDelete,
    25  		Update: resourceAwsNetworkAclUpdate,
    26  		Importer: &schema.ResourceImporter{
    27  			State: resourceAwsNetworkAclImportState,
    28  		},
    29  
    30  		Schema: map[string]*schema.Schema{
    31  			"vpc_id": {
    32  				Type:     schema.TypeString,
    33  				Required: true,
    34  				ForceNew: true,
    35  				Computed: false,
    36  			},
    37  			"subnet_id": {
    38  				Type:       schema.TypeString,
    39  				Optional:   true,
    40  				ForceNew:   true,
    41  				Computed:   false,
    42  				Deprecated: "Attribute subnet_id is deprecated on network_acl resources. Use subnet_ids instead",
    43  			},
    44  			"subnet_ids": {
    45  				Type:          schema.TypeSet,
    46  				Optional:      true,
    47  				Computed:      true,
    48  				ConflictsWith: []string{"subnet_id"},
    49  				Elem:          &schema.Schema{Type: schema.TypeString},
    50  				Set:           schema.HashString,
    51  			},
    52  			"ingress": {
    53  				Type:     schema.TypeSet,
    54  				Required: false,
    55  				Optional: true,
    56  				Computed: true,
    57  				Elem: &schema.Resource{
    58  					Schema: map[string]*schema.Schema{
    59  						"from_port": {
    60  							Type:     schema.TypeInt,
    61  							Required: true,
    62  						},
    63  						"to_port": {
    64  							Type:     schema.TypeInt,
    65  							Required: true,
    66  						},
    67  						"rule_no": {
    68  							Type:     schema.TypeInt,
    69  							Required: true,
    70  						},
    71  						"action": {
    72  							Type:     schema.TypeString,
    73  							Required: true,
    74  						},
    75  						"protocol": {
    76  							Type:     schema.TypeString,
    77  							Required: true,
    78  						},
    79  						"cidr_block": {
    80  							Type:     schema.TypeString,
    81  							Optional: true,
    82  						},
    83  						"ipv6_cidr_block": {
    84  							Type:     schema.TypeString,
    85  							Optional: true,
    86  						},
    87  						"icmp_type": {
    88  							Type:     schema.TypeInt,
    89  							Optional: true,
    90  						},
    91  						"icmp_code": {
    92  							Type:     schema.TypeInt,
    93  							Optional: true,
    94  						},
    95  					},
    96  				},
    97  				Set: resourceAwsNetworkAclEntryHash,
    98  			},
    99  			"egress": {
   100  				Type:     schema.TypeSet,
   101  				Required: false,
   102  				Optional: true,
   103  				Computed: true,
   104  				Elem: &schema.Resource{
   105  					Schema: map[string]*schema.Schema{
   106  						"from_port": {
   107  							Type:     schema.TypeInt,
   108  							Required: true,
   109  						},
   110  						"to_port": {
   111  							Type:     schema.TypeInt,
   112  							Required: true,
   113  						},
   114  						"rule_no": {
   115  							Type:     schema.TypeInt,
   116  							Required: true,
   117  						},
   118  						"action": {
   119  							Type:     schema.TypeString,
   120  							Required: true,
   121  						},
   122  						"protocol": {
   123  							Type:     schema.TypeString,
   124  							Required: true,
   125  						},
   126  						"cidr_block": {
   127  							Type:     schema.TypeString,
   128  							Optional: true,
   129  						},
   130  						"ipv6_cidr_block": {
   131  							Type:     schema.TypeString,
   132  							Optional: true,
   133  						},
   134  						"icmp_type": {
   135  							Type:     schema.TypeInt,
   136  							Optional: true,
   137  						},
   138  						"icmp_code": {
   139  							Type:     schema.TypeInt,
   140  							Optional: true,
   141  						},
   142  					},
   143  				},
   144  				Set: resourceAwsNetworkAclEntryHash,
   145  			},
   146  			"tags": tagsSchema(),
   147  		},
   148  	}
   149  }
   150  
   151  func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
   152  
   153  	conn := meta.(*AWSClient).ec2conn
   154  
   155  	// Create the Network Acl
   156  	createOpts := &ec2.CreateNetworkAclInput{
   157  		VpcId: aws.String(d.Get("vpc_id").(string)),
   158  	}
   159  
   160  	log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
   161  	resp, err := conn.CreateNetworkAcl(createOpts)
   162  	if err != nil {
   163  		return fmt.Errorf("Error creating network acl: %s", err)
   164  	}
   165  
   166  	// Get the ID and store it
   167  	networkAcl := resp.NetworkAcl
   168  	d.SetId(*networkAcl.NetworkAclId)
   169  	log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkAclId)
   170  
   171  	// Update rules and subnet association once acl is created
   172  	return resourceAwsNetworkAclUpdate(d, meta)
   173  }
   174  
   175  func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
   176  	conn := meta.(*AWSClient).ec2conn
   177  
   178  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   179  		NetworkAclIds: []*string{aws.String(d.Id())},
   180  	})
   181  
   182  	if err != nil {
   183  		if ec2err, ok := err.(awserr.Error); ok {
   184  			if ec2err.Code() == "InvalidNetworkAclID.NotFound" {
   185  				log.Printf("[DEBUG] Network ACL (%s) not found", d.Id())
   186  				d.SetId("")
   187  				return nil
   188  			}
   189  		}
   190  		return err
   191  	}
   192  	if resp == nil {
   193  		return nil
   194  	}
   195  
   196  	networkAcl := resp.NetworkAcls[0]
   197  	var ingressEntries []*ec2.NetworkAclEntry
   198  	var egressEntries []*ec2.NetworkAclEntry
   199  
   200  	// separate the ingress and egress rules
   201  	for _, e := range networkAcl.Entries {
   202  		// Skip the default rules added by AWS. They can be neither
   203  		// configured or deleted by users.
   204  		if *e.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
   205  			*e.RuleNumber == awsDefaultAclRuleNumberIpv6 {
   206  			continue
   207  		}
   208  
   209  		if *e.Egress == true {
   210  			egressEntries = append(egressEntries, e)
   211  		} else {
   212  			ingressEntries = append(ingressEntries, e)
   213  		}
   214  	}
   215  
   216  	d.Set("vpc_id", networkAcl.VpcId)
   217  	d.Set("tags", tagsToMap(networkAcl.Tags))
   218  
   219  	var s []string
   220  	for _, a := range networkAcl.Associations {
   221  		s = append(s, *a.SubnetId)
   222  	}
   223  	sort.Strings(s)
   224  	if err := d.Set("subnet_ids", s); err != nil {
   225  		return err
   226  	}
   227  
   228  	if err := d.Set("ingress", networkAclEntriesToMapList(ingressEntries)); err != nil {
   229  		return err
   230  	}
   231  	if err := d.Set("egress", networkAclEntriesToMapList(egressEntries)); err != nil {
   232  		return err
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
   239  	conn := meta.(*AWSClient).ec2conn
   240  	d.Partial(true)
   241  
   242  	if d.HasChange("ingress") {
   243  		err := updateNetworkAclEntries(d, "ingress", conn)
   244  		if err != nil {
   245  			return err
   246  		}
   247  	}
   248  
   249  	if d.HasChange("egress") {
   250  		err := updateNetworkAclEntries(d, "egress", conn)
   251  		if err != nil {
   252  			return err
   253  		}
   254  	}
   255  
   256  	if d.HasChange("subnet_id") {
   257  		//associate new subnet with the acl.
   258  		_, n := d.GetChange("subnet_id")
   259  		newSubnet := n.(string)
   260  		association, err := findNetworkAclAssociation(newSubnet, conn)
   261  		if err != nil {
   262  			return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
   263  		}
   264  		_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   265  			AssociationId: association.NetworkAclAssociationId,
   266  			NetworkAclId:  aws.String(d.Id()),
   267  		})
   268  		if err != nil {
   269  			return err
   270  		}
   271  	}
   272  
   273  	if d.HasChange("subnet_ids") {
   274  		o, n := d.GetChange("subnet_ids")
   275  		if o == nil {
   276  			o = new(schema.Set)
   277  		}
   278  		if n == nil {
   279  			n = new(schema.Set)
   280  		}
   281  
   282  		os := o.(*schema.Set)
   283  		ns := n.(*schema.Set)
   284  
   285  		remove := os.Difference(ns).List()
   286  		add := ns.Difference(os).List()
   287  
   288  		if len(remove) > 0 {
   289  			// A Network ACL is required for each subnet. In order to disassociate a
   290  			// subnet from this ACL, we must associate it with the default ACL.
   291  			defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn)
   292  			if err != nil {
   293  				return fmt.Errorf("Failed to find Default ACL for VPC %s", d.Get("vpc_id").(string))
   294  			}
   295  			for _, r := range remove {
   296  				association, err := findNetworkAclAssociation(r.(string), conn)
   297  				if err != nil {
   298  					return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), r, err)
   299  				}
   300  				log.Printf("DEBUG] Replacing Network Acl Association (%s) with Default Network ACL ID (%s)", *association.NetworkAclAssociationId, *defaultAcl.NetworkAclId)
   301  				_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   302  					AssociationId: association.NetworkAclAssociationId,
   303  					NetworkAclId:  defaultAcl.NetworkAclId,
   304  				})
   305  				if err != nil {
   306  					return err
   307  				}
   308  			}
   309  		}
   310  
   311  		if len(add) > 0 {
   312  			for _, a := range add {
   313  				association, err := findNetworkAclAssociation(a.(string), conn)
   314  				if err != nil {
   315  					return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), a, err)
   316  				}
   317  				_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   318  					AssociationId: association.NetworkAclAssociationId,
   319  					NetworkAclId:  aws.String(d.Id()),
   320  				})
   321  				if err != nil {
   322  					return err
   323  				}
   324  			}
   325  		}
   326  
   327  	}
   328  
   329  	if err := setTags(conn, d); err != nil {
   330  		return err
   331  	} else {
   332  		d.SetPartial("tags")
   333  	}
   334  
   335  	d.Partial(false)
   336  	return resourceAwsNetworkAclRead(d, meta)
   337  }
   338  
   339  func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2.EC2) error {
   340  	if d.HasChange(entryType) {
   341  		o, n := d.GetChange(entryType)
   342  
   343  		if o == nil {
   344  			o = new(schema.Set)
   345  		}
   346  		if n == nil {
   347  			n = new(schema.Set)
   348  		}
   349  
   350  		os := o.(*schema.Set)
   351  		ns := n.(*schema.Set)
   352  
   353  		toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType)
   354  		if err != nil {
   355  			return err
   356  		}
   357  		for _, remove := range toBeDeleted {
   358  			// AWS includes default rules with all network ACLs that can be
   359  			// neither modified nor destroyed. They have a custom rule
   360  			// number that is out of bounds for any other rule. If we
   361  			// encounter it, just continue. There's no work to be done.
   362  			if *remove.RuleNumber == awsDefaultAclRuleNumberIpv4 ||
   363  				*remove.RuleNumber == awsDefaultAclRuleNumberIpv6 {
   364  				continue
   365  			}
   366  
   367  			// Delete old Acl
   368  			log.Printf("[DEBUG] Destroying Network ACL Entry number (%d)", int(*remove.RuleNumber))
   369  			_, err := conn.DeleteNetworkAclEntry(&ec2.DeleteNetworkAclEntryInput{
   370  				NetworkAclId: aws.String(d.Id()),
   371  				RuleNumber:   remove.RuleNumber,
   372  				Egress:       remove.Egress,
   373  			})
   374  			if err != nil {
   375  				return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
   376  			}
   377  		}
   378  
   379  		toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType)
   380  		if err != nil {
   381  			return err
   382  		}
   383  		for _, add := range toBeCreated {
   384  			// Protocol -1 rules don't store ports in AWS. Thus, they'll always
   385  			// hash differently when being read out of the API. Force the user
   386  			// to set from_port and to_port to 0 for these rules, to keep the
   387  			// hashing consistent.
   388  			if *add.Protocol == "-1" {
   389  				to := *add.PortRange.To
   390  				from := *add.PortRange.From
   391  				expected := &expectedPortPair{
   392  					to_port:   0,
   393  					from_port: 0,
   394  				}
   395  				if ok := validatePorts(to, from, *expected); !ok {
   396  					return fmt.Errorf(
   397  						"to_port (%d) and from_port (%d) must both be 0 to use the the 'all' \"-1\" protocol!",
   398  						to, from)
   399  				}
   400  			}
   401  
   402  			if add.CidrBlock != nil && *add.CidrBlock != "" {
   403  				// AWS mutates the CIDR block into a network implied by the IP and
   404  				// mask provided. This results in hashing inconsistencies between
   405  				// the local config file and the state returned by the API. Error
   406  				// if the user provides a CIDR block with an inappropriate mask
   407  				if err := validateCIDRBlock(*add.CidrBlock); err != nil {
   408  					return err
   409  				}
   410  			}
   411  
   412  			createOpts := &ec2.CreateNetworkAclEntryInput{
   413  				NetworkAclId: aws.String(d.Id()),
   414  				Egress:       add.Egress,
   415  				PortRange:    add.PortRange,
   416  				Protocol:     add.Protocol,
   417  				RuleAction:   add.RuleAction,
   418  				RuleNumber:   add.RuleNumber,
   419  				IcmpTypeCode: add.IcmpTypeCode,
   420  			}
   421  
   422  			if add.CidrBlock != nil && *add.CidrBlock != "" {
   423  				createOpts.CidrBlock = add.CidrBlock
   424  			}
   425  
   426  			if add.Ipv6CidrBlock != nil && *add.Ipv6CidrBlock != "" {
   427  				createOpts.Ipv6CidrBlock = add.Ipv6CidrBlock
   428  			}
   429  
   430  			// Add new Acl entry
   431  			_, connErr := conn.CreateNetworkAclEntry(createOpts)
   432  			if connErr != nil {
   433  				return fmt.Errorf("Error creating %s entry: %s", entryType, connErr)
   434  			}
   435  		}
   436  	}
   437  	return nil
   438  }
   439  
   440  func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
   441  	conn := meta.(*AWSClient).ec2conn
   442  
   443  	log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
   444  	retryErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
   445  		_, err := conn.DeleteNetworkAcl(&ec2.DeleteNetworkAclInput{
   446  			NetworkAclId: aws.String(d.Id()),
   447  		})
   448  		if err != nil {
   449  			ec2err := err.(awserr.Error)
   450  			switch ec2err.Code() {
   451  			case "InvalidNetworkAclID.NotFound":
   452  				return nil
   453  			case "DependencyViolation":
   454  				// In case of dependency violation, we remove the association between subnet and network acl.
   455  				// This means the subnet is attached to default acl of vpc.
   456  				var associations []*ec2.NetworkAclAssociation
   457  				if v, ok := d.GetOk("subnet_id"); ok {
   458  
   459  					a, err := findNetworkAclAssociation(v.(string), conn)
   460  					if err != nil {
   461  						return resource.NonRetryableError(err)
   462  					}
   463  					associations = append(associations, a)
   464  				} else if v, ok := d.GetOk("subnet_ids"); ok {
   465  					ids := v.(*schema.Set).List()
   466  					for _, i := range ids {
   467  						a, err := findNetworkAclAssociation(i.(string), conn)
   468  						if err != nil {
   469  							return resource.NonRetryableError(err)
   470  						}
   471  						associations = append(associations, a)
   472  					}
   473  				}
   474  
   475  				log.Printf("[DEBUG] Replacing network associations for Network ACL (%s): %s", d.Id(), associations)
   476  				defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn)
   477  				if err != nil {
   478  					return resource.NonRetryableError(err)
   479  				}
   480  
   481  				for _, a := range associations {
   482  					log.Printf("DEBUG] Replacing Network Acl Association (%s) with Default Network ACL ID (%s)", *a.NetworkAclAssociationId, *defaultAcl.NetworkAclId)
   483  					_, replaceErr := conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   484  						AssociationId: a.NetworkAclAssociationId,
   485  						NetworkAclId:  defaultAcl.NetworkAclId,
   486  					})
   487  					if replaceErr != nil {
   488  						if replaceEc2err, ok := replaceErr.(awserr.Error); ok {
   489  							// It's possible that during an attempt to replace this
   490  							// association, the Subnet in question has already been moved to
   491  							// another ACL. This can happen if you're destroying a network acl
   492  							// and simultaneously re-associating it's subnet(s) with another
   493  							// ACL; Terraform may have already re-associated the subnet(s) by
   494  							// the time we attempt to destroy them, even between the time we
   495  							// list them and then try to destroy them. In this case, the
   496  							// association we're trying to replace will no longer exist and
   497  							// this call will fail. Here we trap that error and fail
   498  							// gracefully; the association we tried to replace gone, we trust
   499  							// someone else has taken ownership.
   500  							if replaceEc2err.Code() == "InvalidAssociationID.NotFound" {
   501  								log.Printf("[WARN] Network Association (%s) no longer found; Network Association likely updated or removed externally, removing from state", *a.NetworkAclAssociationId)
   502  								continue
   503  							}
   504  						}
   505  						log.Printf("[ERR] Non retry-able error in replacing associations for Network ACL (%s): %s", d.Id(), replaceErr)
   506  						return resource.NonRetryableError(replaceErr)
   507  					}
   508  				}
   509  				return resource.RetryableError(fmt.Errorf("Dependencies found and cleaned up, retrying"))
   510  			default:
   511  				// Any other error, we want to quit the retry loop immediately
   512  				return resource.NonRetryableError(err)
   513  			}
   514  		}
   515  		log.Printf("[Info] Deleted network ACL %s successfully", d.Id())
   516  		return nil
   517  	})
   518  
   519  	if retryErr != nil {
   520  		return fmt.Errorf("[ERR] Error destroying Network ACL (%s): %s", d.Id(), retryErr)
   521  	}
   522  	return nil
   523  }
   524  
   525  func resourceAwsNetworkAclEntryHash(v interface{}) int {
   526  	var buf bytes.Buffer
   527  	m := v.(map[string]interface{})
   528  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   529  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   530  	buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int)))
   531  	buf.WriteString(fmt.Sprintf("%s-", m["action"].(string)))
   532  
   533  	// The AWS network ACL API only speaks protocol numbers, and that's
   534  	// all we store. Never hash a protocol name.
   535  	protocol := m["protocol"].(string)
   536  	if _, err := strconv.Atoi(m["protocol"].(string)); err != nil {
   537  		// We're a protocol name. Look up the number.
   538  		buf.WriteString(fmt.Sprintf("%d-", protocolIntegers()[protocol]))
   539  	} else {
   540  		// We're a protocol number. Pass the value through.
   541  		buf.WriteString(fmt.Sprintf("%s-", protocol))
   542  	}
   543  
   544  	if v, ok := m["cidr_block"]; ok {
   545  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   546  	}
   547  
   548  	if v, ok := m["ipv6_cidr_block"]; ok {
   549  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   550  	}
   551  
   552  	if v, ok := m["ssl_certificate_id"]; ok {
   553  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   554  	}
   555  
   556  	if v, ok := m["icmp_type"]; ok {
   557  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   558  	}
   559  	if v, ok := m["icmp_code"]; ok {
   560  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   561  	}
   562  
   563  	return hashcode.String(buf.String())
   564  }
   565  
   566  func getDefaultNetworkAcl(vpc_id string, conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) {
   567  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   568  		Filters: []*ec2.Filter{
   569  			{
   570  				Name:   aws.String("default"),
   571  				Values: []*string{aws.String("true")},
   572  			},
   573  			{
   574  				Name:   aws.String("vpc-id"),
   575  				Values: []*string{aws.String(vpc_id)},
   576  			},
   577  		},
   578  	})
   579  
   580  	if err != nil {
   581  		return nil, err
   582  	}
   583  	return resp.NetworkAcls[0], nil
   584  }
   585  
   586  func findNetworkAclAssociation(subnetId string, conn *ec2.EC2) (networkAclAssociation *ec2.NetworkAclAssociation, err error) {
   587  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   588  		Filters: []*ec2.Filter{
   589  			{
   590  				Name:   aws.String("association.subnet-id"),
   591  				Values: []*string{aws.String(subnetId)},
   592  			},
   593  		},
   594  	})
   595  
   596  	if err != nil {
   597  		return nil, err
   598  	}
   599  	if resp.NetworkAcls != nil && len(resp.NetworkAcls) > 0 {
   600  		for _, association := range resp.NetworkAcls[0].Associations {
   601  			if *association.SubnetId == subnetId {
   602  				return association, nil
   603  			}
   604  		}
   605  	}
   606  	return nil, fmt.Errorf("could not find association for subnet: %s ", subnetId)
   607  }
   608  
   609  // networkAclEntriesToMapList turns ingress/egress rules read from AWS into a list
   610  // of maps.
   611  func networkAclEntriesToMapList(networkAcls []*ec2.NetworkAclEntry) []map[string]interface{} {
   612  	result := make([]map[string]interface{}, 0, len(networkAcls))
   613  	for _, entry := range networkAcls {
   614  		acl := make(map[string]interface{})
   615  		acl["rule_no"] = *entry.RuleNumber
   616  		acl["action"] = *entry.RuleAction
   617  		if entry.CidrBlock != nil {
   618  			acl["cidr_block"] = *entry.CidrBlock
   619  		}
   620  		if entry.Ipv6CidrBlock != nil {
   621  			acl["ipv6_cidr_block"] = *entry.Ipv6CidrBlock
   622  		}
   623  		// The AWS network ACL API only speaks protocol numbers, and
   624  		// that's all we record.
   625  		if _, err := strconv.Atoi(*entry.Protocol); err != nil {
   626  			// We're a protocol name. Look up the number.
   627  			acl["protocol"] = protocolIntegers()[*entry.Protocol]
   628  		} else {
   629  			// We're a protocol number. Pass through.
   630  			acl["protocol"] = *entry.Protocol
   631  		}
   632  
   633  		acl["protocol"] = *entry.Protocol
   634  		if entry.PortRange != nil {
   635  			acl["from_port"] = *entry.PortRange.From
   636  			acl["to_port"] = *entry.PortRange.To
   637  		}
   638  
   639  		if entry.IcmpTypeCode != nil {
   640  			acl["icmp_type"] = *entry.IcmpTypeCode.Type
   641  			acl["icmp_code"] = *entry.IcmpTypeCode.Code
   642  		}
   643  
   644  		result = append(result, acl)
   645  	}
   646  
   647  	return result
   648  }