diff --git a/aws/provider.go b/aws/provider.go index d2ea8a71fb3..fe9153fa5ec 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -489,6 +489,10 @@ func Provider() terraform.ResourceProvider { "aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(), "aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(), "aws_ec2_fleet": resourceAwsEc2Fleet(), + "aws_ec2_traffic_mirror_filter": resourceAwsEc2TrafficMirrorFilter(), + "aws_ec2_traffic_mirror_filter_rule": resourceAwsEc2TrafficMirrorFilterRule(), + "aws_ec2_traffic_mirror_target": resourceAwsEc2TrafficMirrorTarget(), + "aws_ec2_traffic_mirror_session": resourceAwsEc2TrafficMirrorSession(), "aws_ec2_transit_gateway": resourceAwsEc2TransitGateway(), "aws_ec2_transit_gateway_route": resourceAwsEc2TransitGatewayRoute(), "aws_ec2_transit_gateway_route_table": resourceAwsEc2TransitGatewayRouteTable(), diff --git a/aws/resource_aws_ec2_traffic_mirror_filter.go b/aws/resource_aws_ec2_traffic_mirror_filter.go new file mode 100644 index 00000000000..f64163cc566 --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_filter.go @@ -0,0 +1,131 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceAwsEc2TrafficMirrorFilter() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TrafficMirrorinFilterCreate, + Read: resourceAwsEc2TrafficMirrorFilterRead, + Update: resourceAwsEc2TrafficMirrorFilterUpdate, + Delete: resourceAwsEc2TrafficMirrorFilterDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "network_services": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "amazon-dns", + }, false), + }, + }, + }, + } +} + +func resourceAwsEc2TrafficMirrorinFilterCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.CreateTrafficMirrorFilterInput{} + + if description, ok := d.GetOk("description"); ok { + input.Description = aws.String(description.(string)) + } + + out, err := conn.CreateTrafficMirrorFilter(input) + if err != nil { + return fmt.Errorf("Error while creating traffic filter %s", err) + } + + d.SetId(*out.TrafficMirrorFilter.TrafficMirrorFilterId) + + return resourceAwsEc2TrafficMirrorFilterUpdate(d, meta) +} + +func resourceAwsEc2TrafficMirrorFilterUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + if d.HasChange("network_services") { + input := &ec2.ModifyTrafficMirrorFilterNetworkServicesInput{ + TrafficMirrorFilterId: aws.String(d.Id()), + } + + o, n := d.GetChange("network_services") + newServices := n.(*schema.Set).Difference(o.(*schema.Set)).List() + if len(newServices) > 0 { + input.AddNetworkServices = expandStringList(newServices) + } + + removeServices := o.(*schema.Set).Difference(n.(*schema.Set)).List() + if len(removeServices) > 0 { + input.RemoveNetworkServices = expandStringList(removeServices) + } + + _, err := conn.ModifyTrafficMirrorFilterNetworkServices(input) + if err != nil { + return fmt.Errorf("error modifying EC2 Traffic Mirror Filter (%s) network services: %w", d.Id(), err) + } + } + + return resourceAwsEc2TrafficMirrorFilterRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorFilterRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DescribeTrafficMirrorFiltersInput{ + TrafficMirrorFilterIds: aws.StringSlice([]string{d.Id()}), + } + + out, err := conn.DescribeTrafficMirrorFilters(input) + if err != nil { + return fmt.Errorf("Error describing traffic mirror filter %v: %v", d.Id(), err) + } + + if len(out.TrafficMirrorFilters) == 0 { + log.Printf("[WARN] EC2 Traffic Mirror Filter (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.SetId(*out.TrafficMirrorFilters[0].TrafficMirrorFilterId) + d.Set("description", out.TrafficMirrorFilters[0].Description) + + if err := d.Set("network_services", aws.StringValueSlice(out.TrafficMirrorFilters[0].NetworkServices)); err != nil { + return fmt.Errorf("error setting network_services for filter %v: %s", d.Id(), err) + } + + return nil +} + +func resourceAwsEc2TrafficMirrorFilterDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.DeleteTrafficMirrorFilterInput{ + TrafficMirrorFilterId: aws.String(d.Id()), + } + + _, err := conn.DeleteTrafficMirrorFilter(input) + if err != nil { + return fmt.Errorf("Error deleting traffic mirror filter %v: %v", d.Id(), err) + } + + return nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_filter_rule.go b/aws/resource_aws_ec2_traffic_mirror_filter_rule.go new file mode 100644 index 00000000000..792d054b307 --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_filter_rule.go @@ -0,0 +1,356 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceAwsEc2TrafficMirrorFilterRule() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TrafficMirrorFilterRuleCreate, + Read: resourceAwsEc2TrafficMirrorFilterRuleRead, + Update: resourceAwsEc2TrafficMirrorFilterRuleUpdate, + Delete: resourceAwsEc2TrafficMirrorFilterRuleDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsEc2TrafficMirrorFilterRuleImport, + }, + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "traffic_mirror_filter_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "destination_cidr_block": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateCIDRNetworkAddress, + }, + "destination_port_range": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Optional: true, + }, + "to_port": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "protocol": { + Type: schema.TypeInt, + Optional: true, + }, + "rule_action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.TrafficMirrorRuleActionAccept, + ec2.TrafficMirrorRuleActionReject, + }, false), + }, + "rule_number": { + Type: schema.TypeInt, + Required: true, + }, + "source_cidr_block": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateCIDRNetworkAddress, + }, + "source_port_range": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": { + Type: schema.TypeInt, + Optional: true, + }, + "to_port": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "traffic_direction": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + ec2.TrafficDirectionIngress, + ec2.TrafficDirectionEgress, + }, false), + }, + }, + } +} + +func resourceAwsEc2TrafficMirrorFilterRuleCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + filterId := d.Get("traffic_mirror_filter_id") + + input := &ec2.CreateTrafficMirrorFilterRuleInput{ + TrafficMirrorFilterId: aws.String(filterId.(string)), + DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), + SourceCidrBlock: aws.String(d.Get("source_cidr_block").(string)), + RuleAction: aws.String(d.Get("rule_action").(string)), + RuleNumber: aws.Int64(int64(d.Get("rule_number").(int))), + TrafficDirection: aws.String(d.Get("traffic_direction").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("protocol"); ok { + input.Protocol = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("destination_port_range"); ok { + input.DestinationPortRange = buildTrafficMirrorPortRangeRequest(v.([]interface{})) + } + + if v, ok := d.GetOk("source_port_range"); ok { + input.SourcePortRange = buildTrafficMirrorPortRangeRequest(v.([]interface{})) + } + + out, err := conn.CreateTrafficMirrorFilterRule(input) + if err != nil { + return fmt.Errorf("error creating EC2 Traffic Mirror Filter Rule (%s): %w", filterId, err) + } + + d.SetId(*out.TrafficMirrorFilterRule.TrafficMirrorFilterRuleId) + return resourceAwsEc2TrafficMirrorFilterRuleRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorFilterRuleRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + ruleId := d.Id() + + var rule *ec2.TrafficMirrorFilterRule + filterId, filterIdSet := d.GetOk("traffic_mirror_filter_id") + input := &ec2.DescribeTrafficMirrorFiltersInput{} + if filterIdSet { + input.TrafficMirrorFilterIds = aws.StringSlice([]string{filterId.(string)}) + } + + err := conn.DescribeTrafficMirrorFiltersPages(input, func(page *ec2.DescribeTrafficMirrorFiltersOutput, lastPage bool) bool { + rule = findEc2TrafficMirrorFilterRule(ruleId, page.TrafficMirrorFilters) + return nil == rule + }) + + if err != nil { + return fmt.Errorf("Error while describing filters: %v", err) + } + + if nil == rule { + log.Printf("[WARN] EC2 Traffic Mirror Filter (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.SetId(*rule.TrafficMirrorFilterRuleId) + d.Set("traffic_mirror_filter_id", rule.TrafficMirrorFilterId) + d.Set("destination_cidr_block", rule.DestinationCidrBlock) + d.Set("source_cidr_block", rule.SourceCidrBlock) + d.Set("rule_action", rule.RuleAction) + d.Set("rule_number", rule.RuleNumber) + d.Set("traffic_direction", rule.TrafficDirection) + d.Set("description", rule.Description) + d.Set("protocol", rule.Protocol) + + if err := d.Set("destination_port_range", buildTrafficMirrorFilterRulePortRangeSchema(rule.DestinationPortRange)); err != nil { + return fmt.Errorf("error setting destination_port_range: %s", err) + } + + if err := d.Set("source_port_range", buildTrafficMirrorFilterRulePortRangeSchema(rule.SourcePortRange)); err != nil { + return fmt.Errorf("error setting source_port_range: %s", err) + } + + return nil +} + +func findEc2TrafficMirrorFilterRule(ruleId string, filters []*ec2.TrafficMirrorFilter) (rule *ec2.TrafficMirrorFilterRule) { + log.Printf("[DEBUG] searching %s in %d filters", ruleId, len(filters)) + for _, v := range filters { + log.Printf("[DEBUG]: searching filter %s, ingress rule count = %d, egress rule count = %d", *v.TrafficMirrorFilterId, len(v.IngressFilterRules), len(v.EgressFilterRules)) + for _, r := range v.IngressFilterRules { + if *r.TrafficMirrorFilterRuleId == ruleId { + rule = r + break + } + } + for _, r := range v.EgressFilterRules { + if *r.TrafficMirrorFilterRuleId == ruleId { + rule = r + break + } + } + } + + if nil != rule { + log.Printf("[DEBUG]: Found %s in %s", ruleId, *rule.TrafficDirection) + } + + return rule +} + +func resourceAwsEc2TrafficMirrorFilterRuleUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + ruleId := d.Id() + + input := &ec2.ModifyTrafficMirrorFilterRuleInput{ + TrafficMirrorFilterRuleId: &ruleId, + } + + var removeFields []*string + if d.HasChange("protocol") { + n := d.Get("protocol") + if n == "0" { + removeFields = append(removeFields, aws.String(ec2.TrafficMirrorFilterRuleFieldProtocol)) + } else { + input.Protocol = aws.Int64(int64(d.Get("protocol").(int))) + } + } + + if d.HasChange("description") { + n := d.Get("description") + if n != "" { + input.Description = aws.String(n.(string)) + } else { + removeFields = append(removeFields, aws.String(ec2.TrafficMirrorFilterRuleFieldDescription)) + } + } + + if d.HasChange("destination_cidr_block") { + input.DestinationCidrBlock = aws.String(d.Get("destination_cidr_block").(string)) + } + + if d.HasChange("source_cidr_block") { + input.SourceCidrBlock = aws.String(d.Get("source_cidr_block").(string)) + } + + if d.HasChange("destination_port_range") { + v := d.Get("destination_port_range") + n := v.([]interface{}) + if 0 == len(n) { + removeFields = append(removeFields, aws.String(ec2.TrafficMirrorFilterRuleFieldDestinationPortRange)) + } else { + //Modify request that adds port range seems to fail if protocol is not set in the request + input.Protocol = aws.Int64(int64(d.Get("protocol").(int))) + input.DestinationPortRange = buildTrafficMirrorPortRangeRequest(n) + } + } + + if d.HasChange("source_port_range") { + v := d.Get("source_port_range") + n := v.([]interface{}) + if 0 == len(n) { + removeFields = append(removeFields, aws.String(ec2.TrafficMirrorFilterRuleFieldSourcePortRange)) + } else { + //Modify request that adds port range seems to fail if protocol is not set in the request + input.Protocol = aws.Int64(int64(d.Get("protocol").(int))) + input.SourcePortRange = buildTrafficMirrorPortRangeRequest(n) + } + } + + if d.HasChange("rule_action") { + input.RuleAction = aws.String(d.Get("rule_action").(string)) + } + + if d.HasChange("rule_number") { + input.RuleNumber = aws.Int64(int64(d.Get("rule_action").(int))) + } + + if d.HasChange("traffic_direction") { + input.TrafficDirection = aws.String(d.Get("traffic_direction").(string)) + } + + if len(removeFields) > 0 { + input.SetRemoveFields(removeFields) + } + + _, err := conn.ModifyTrafficMirrorFilterRule(input) + if err != nil { + return fmt.Errorf("error modifying EC2 Traffic Mirror Filter Rule (%s): %w", ruleId, err) + } + + return resourceAwsEc2TrafficMirrorFilterRuleRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorFilterRuleDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + ruleId := d.Id() + input := &ec2.DeleteTrafficMirrorFilterRuleInput{ + TrafficMirrorFilterRuleId: &ruleId, + } + + _, err := conn.DeleteTrafficMirrorFilterRule(input) + if err != nil { + return fmt.Errorf("error deleting EC2 Traffic Mirror Filter Rule (%s): %w", ruleId, err) + } + + return nil +} + +func buildTrafficMirrorPortRangeRequest(p []interface{}) (out *ec2.TrafficMirrorPortRangeRequest) { + portSchema := p[0].(map[string]interface{}) + + portRange := ec2.TrafficMirrorPortRangeRequest{} + if v, ok := portSchema["from_port"]; ok { + portRange.FromPort = aws.Int64(int64(v.(int))) + out = &portRange + } + + if v, ok := portSchema["to_port"]; ok { + portRange.ToPort = aws.Int64(int64(v.(int))) + out = &portRange + } + + return out +} + +func buildTrafficMirrorFilterRulePortRangeSchema(portRange *ec2.TrafficMirrorPortRange) interface{} { + if nil == portRange { + return nil + } + + var out [1]interface{} + elem := make(map[string]interface{}) + elem["from_port"] = portRange.FromPort + elem["to_port"] = portRange.ToPort + out[0] = elem + + return out +} + +func resourceAwsEc2TrafficMirrorFilterRuleImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.SplitN(d.Id(), ":", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return nil, fmt.Errorf("unexpected format (%q), expected :", d.Id()) + } + + d.Set("traffic_mirror_filter_id", parts[0]) + d.SetId(parts[1]) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_filter_rule_test.go b/aws/resource_aws_ec2_traffic_mirror_filter_rule_test.go new file mode 100644 index 00000000000..cf5c6789f53 --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_filter_rule_test.go @@ -0,0 +1,261 @@ +package aws + +import ( + "fmt" + "regexp" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2TrafficMirrorFilterRule_basic(t *testing.T) { + resourceName := "aws_ec2_traffic_mirror_filter_rule.rule" + dstCidr := "10.0.0.0/8" + srcCidr := "0.0.0.0/0" + ruleNum := 1 + action := "accept" + direction := "ingress" + description := "test rule" + protocol := 6 + srcPortFrom := 32000 + srcPortTo := 64000 + dstPortFrom := 10000 + dstPortTo := 10001 + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TrafficMirrorFilterRule(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2TrafficMirrorFilterRuleDestroy, + Steps: []resource.TestStep{ + //create + { + Config: testAccEc2TrafficMirrorFilterRuleConfig(dstCidr, srcCidr, action, direction, ruleNum), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterRuleExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "traffic_mirror_filter_id", regexp.MustCompile("tmf-.*")), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", dstCidr), + resource.TestCheckResourceAttr(resourceName, "rule_action", action), + resource.TestCheckResourceAttr(resourceName, "rule_number", strconv.Itoa(ruleNum)), + resource.TestCheckResourceAttr(resourceName, "source_cidr_block", srcCidr), + resource.TestCheckResourceAttr(resourceName, "traffic_direction", direction), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckNoResourceAttr(resourceName, "destination_port_range"), + resource.TestCheckResourceAttr(resourceName, "protocol", "0"), + resource.TestCheckNoResourceAttr(resourceName, "source_port_range"), + ), + }, + // Add all optionals + { + Config: testAccEc2TrafficMirrorFilterRuleConfigFull(dstCidr, srcCidr, action, direction, description, ruleNum, srcPortFrom, srcPortTo, dstPortFrom, dstPortTo, protocol), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterRuleExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "traffic_mirror_filter_id", regexp.MustCompile("tmf-.*")), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", dstCidr), + resource.TestCheckResourceAttr(resourceName, "rule_action", action), + resource.TestCheckResourceAttr(resourceName, "rule_number", strconv.Itoa(ruleNum)), + resource.TestCheckResourceAttr(resourceName, "source_cidr_block", srcCidr), + resource.TestCheckResourceAttr(resourceName, "traffic_direction", direction), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "destination_port_range.#", "1"), + resource.TestCheckResourceAttr(resourceName, "destination_port_range.0.from_port", strconv.Itoa(dstPortFrom)), + resource.TestCheckResourceAttr(resourceName, "destination_port_range.0.to_port", strconv.Itoa(dstPortTo)), + resource.TestCheckResourceAttr(resourceName, "source_port_range.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source_port_range.0.from_port", strconv.Itoa(srcPortFrom)), + resource.TestCheckResourceAttr(resourceName, "source_port_range.0.to_port", strconv.Itoa(srcPortTo)), + resource.TestCheckResourceAttr(resourceName, "protocol", strconv.Itoa(protocol)), + ), + }, + // remove optionals + { + Config: testAccEc2TrafficMirrorFilterRuleConfig(dstCidr, srcCidr, action, direction, ruleNum), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterRuleExists(resourceName), + resource.TestMatchResourceAttr(resourceName, "traffic_mirror_filter_id", regexp.MustCompile("tmf-.*")), + resource.TestCheckResourceAttr(resourceName, "destination_cidr_block", dstCidr), + resource.TestCheckResourceAttr(resourceName, "rule_action", action), + resource.TestCheckResourceAttr(resourceName, "rule_number", strconv.Itoa(ruleNum)), + resource.TestCheckResourceAttr(resourceName, "source_cidr_block", srcCidr), + resource.TestCheckResourceAttr(resourceName, "traffic_direction", direction), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "destination_port_range.#", "0"), + resource.TestCheckResourceAttr(resourceName, "protocol", "0"), + resource.TestCheckResourceAttr(resourceName, "source_port_range.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSEc2TrafficMirrorFilterRuleImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSEc2TrafficMirrorFilterRuleExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set for %s", name) + } + + ruleId := rs.Primary.ID + filterId := rs.Primary.Attributes["traffic_mirror_filter_id"] + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + out, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{ + TrafficMirrorFilterIds: []*string{ + aws.String(filterId), + }, + }) + + if err != nil { + return err + } + + if 0 == len(out.TrafficMirrorFilters) { + return fmt.Errorf("Traffic mirror filter %s not found", rs.Primary.ID) + } + + filter := out.TrafficMirrorFilters[0] + var ruleList []*ec2.TrafficMirrorFilterRule + ruleList = append(ruleList, filter.IngressFilterRules...) + ruleList = append(ruleList, filter.EgressFilterRules...) + + var exists bool + for _, rule := range ruleList { + if *rule.TrafficMirrorFilterRuleId == ruleId { + exists = true + break + } + } + + if !exists { + return fmt.Errorf("Rule %s not found inside filter %s", ruleId, filterId) + } + + return nil + } +} + +func testAccEc2TrafficMirrorFilterRuleConfig(dstCidr, srcCidr, action, dir string, num int) string { + return fmt.Sprintf(` +resource "aws_ec2_traffic_mirror_filter" "filter" { +} + +resource "aws_ec2_traffic_mirror_filter_rule" "rule" { + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + destination_cidr_block = "%s" + rule_action = "%s" + rule_number = %d + source_cidr_block = "%s" + traffic_direction = "%s" +} +`, dstCidr, action, num, srcCidr, dir) +} + +func testAccEc2TrafficMirrorFilterRuleConfigFull(dstCidr, srcCidr, action, dir, description string, ruleNum, srcPortFrom, srcPortTo, dstPortFrom, dstPortTo, protocol int) string { + return fmt.Sprintf(` +resource "aws_ec2_traffic_mirror_filter" "filter" { +} + +resource "aws_ec2_traffic_mirror_filter_rule" "rule" { + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + destination_cidr_block = "%s" + rule_action = "%s" + rule_number = %d + source_cidr_block = "%s" + traffic_direction = "%s" + description = "%s" + protocol = %d + source_port_range { + from_port = %d + to_port = %d + } + destination_port_range { + from_port = %d + to_port = %d + } +} +`, dstCidr, action, ruleNum, srcCidr, dir, description, protocol, srcPortFrom, srcPortTo, dstPortFrom, dstPortTo) +} + +func testAccPreCheckAWSEc2TrafficMirrorFilterRule(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{}) + + if testAccPreCheckSkipError(err) { + t.Skip("skipping traffic mirror filter rule acceprance test: ", err) + } + + if err != nil { + t.Fatal("Unexpected PreCheck error: ", err) + } +} + +func testAccCheckAWSEc2TrafficMirrorFilterRuleDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_traffic_mirror_filter_rule" { + continue + } + + ruleId := rs.Primary.ID + filterId := rs.Primary.Attributes["traffic_mirror_filter_id"] + + out, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{ + TrafficMirrorFilterIds: []*string{ + aws.String(filterId), + }, + }) + + if isAWSErr(err, "InvalidTrafficMirrorFilterId.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if 0 == len(out.TrafficMirrorFilters) { + return nil + } + + filter := out.TrafficMirrorFilters[0] + var ruleList []*ec2.TrafficMirrorFilterRule + ruleList = append(ruleList, filter.IngressFilterRules...) + ruleList = append(ruleList, filter.EgressFilterRules...) + + for _, rule := range ruleList { + if *rule.TrafficMirrorFilterRuleId == ruleId { + return fmt.Errorf("Rule %s still exists in filter %s", ruleId, filterId) + } + } + } + + return nil +} + +func testAccAWSEc2TrafficMirrorFilterRuleImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return fmt.Sprintf("%s:%s", rs.Primary.Attributes["traffic_mirror_filter_id"], rs.Primary.ID), nil + } +} diff --git a/aws/resource_aws_ec2_traffic_mirror_filter_test.go b/aws/resource_aws_ec2_traffic_mirror_filter_test.go new file mode 100644 index 00000000000..bf7604ced6d --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_filter_test.go @@ -0,0 +1,150 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2TrafficMirrorFilter_basic(t *testing.T) { + resourceName := "aws_ec2_traffic_mirror_filter.filter" + description := "test filter" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TrafficMirrorFilter(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2TrafficMirrorFilterDestroy, + Steps: []resource.TestStep{ + //create + { + Config: testAccTrafficMirrorFilterConfig(description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "network_services.#", "1"), + ), + }, + // Test Disable DNS service + { + Config: testAccTrafficMirrorFilterConfigWithoutDNS(description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterExists(resourceName), + resource.TestCheckNoResourceAttr(resourceName, "network_services"), + ), + }, + // Test Enable DNS service + { + Config: testAccTrafficMirrorFilterConfig(description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorFilterExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "network_services.#", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSEc2TrafficMirrorFilterExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set for %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + out, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{ + TrafficMirrorFilterIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if err != nil { + return err + } + + if 0 == len(out.TrafficMirrorFilters) { + return fmt.Errorf("Traffic mirror filter %s not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccTrafficMirrorFilterConfig(description string) string { + return fmt.Sprintf(` +resource "aws_ec2_traffic_mirror_filter" "filter" { + description = "%s" + + network_services = ["amazon-dns"] +} +`, description) +} + +func testAccTrafficMirrorFilterConfigWithoutDNS(description string) string { + return fmt.Sprintf(` +resource "aws_ec2_traffic_mirror_filter" "filter" { + description = "%s" +} +`, description) +} + +func testAccPreCheckAWSEc2TrafficMirrorFilter(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{}) + + if testAccPreCheckSkipError(err) { + t.Skip("skipping traffic mirror filter acceprance test: ", err) + } + + if err != nil { + t.Fatal("Unexpected PreCheck error: ", err) + } +} + +func testAccCheckAWSEc2TrafficMirrorFilterDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_traffic_mirror_filter" { + continue + } + + out, err := conn.DescribeTrafficMirrorFilters(&ec2.DescribeTrafficMirrorFiltersInput{ + TrafficMirrorFilterIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if isAWSErr(err, "InvalidTrafficMirrorFilterId.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if len(out.TrafficMirrorFilters) != 0 { + return fmt.Errorf("Traffic mirror filter %s still not destroyed", rs.Primary.ID) + } + } + + return nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_session.go b/aws/resource_aws_ec2_traffic_mirror_session.go new file mode 100644 index 00000000000..c9d69f9f233 --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_session.go @@ -0,0 +1,206 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceAwsEc2TrafficMirrorSession() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TrafficMirrorSessionCreate, + Update: resourceAwsEc2TrafficMirrorSessionUpdate, + Read: resourceAwsEc2TrafficMirrorSessionRead, + Delete: resourceAwsEc2TrafficMirrorSessionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "packet_length": { + Type: schema.TypeInt, + Optional: true, + }, + "session_number": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(1, 32766), + }, + "traffic_mirror_filter_id": { + Type: schema.TypeString, + Required: true, + }, + "traffic_mirror_target_id": { + Type: schema.TypeString, + Required: true, + }, + "virtual_network_id": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ValidateFunc: validation.IntBetween(1, 16777216), + }, + }, + } +} + +func resourceAwsEc2TrafficMirrorSessionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.CreateTrafficMirrorSessionInput{ + NetworkInterfaceId: aws.String(d.Get("network_interface_id").(string)), + TrafficMirrorFilterId: aws.String(d.Get("traffic_mirror_filter_id").(string)), + TrafficMirrorTargetId: aws.String(d.Get("traffic_mirror_target_id").(string)), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("packet_length"); ok { + input.PacketLength = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("session_number"); ok { + input.SessionNumber = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("virtual_network_id"); ok { + input.VirtualNetworkId = aws.Int64(int64(v.(int))) + } + + out, err := conn.CreateTrafficMirrorSession(input) + if nil != err { + return fmt.Errorf("Error creating traffic mirror session %v", err) + } + + d.SetId(*out.TrafficMirrorSession.TrafficMirrorSessionId) + return resourceAwsEc2TrafficMirrorSessionRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorSessionUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + sessionId := d.Id() + input := &ec2.ModifyTrafficMirrorSessionInput{ + TrafficMirrorSessionId: &sessionId, + } + + if d.HasChange("session_number") { + n := d.Get("session_number") + input.SessionNumber = aws.Int64(int64(n.(int))) + } + + if d.HasChange("traffic_mirror_filter_id") { + n := d.Get("traffic_mirror_filter_id") + input.TrafficMirrorFilterId = aws.String(n.(string)) + } + + if d.HasChange("traffic_mirror_target_id") { + n := d.Get("traffic_mirror_target_id") + input.TrafficMirrorTargetId = aws.String(n.(string)) + } + + var removeFields []*string + if d.HasChange("description") { + n := d.Get("description") + if "" != n { + input.Description = aws.String(n.(string)) + } else { + removeFields = append(removeFields, aws.String("description")) + } + } + + if d.HasChange("packet_length") { + n := d.Get("packet_length") + if nil != n && n.(int) > 0 { + input.PacketLength = aws.Int64(int64(n.(int))) + } else { + removeFields = append(removeFields, aws.String("packet-length")) + } + } + log.Printf("[DEBUG] removeFields %v", removeFields) + + if d.HasChange("virtual_network_id") { + n := d.Get("virtual_network_id") + log.Printf("[DEBUG] VNI has change %v", n) + if nil != n && n.(int) > 0 { + input.VirtualNetworkId = aws.Int64(int64(n.(int))) + } else { + removeFields = append(removeFields, aws.String("virtual-network-id")) + } + } + + log.Printf("[DEBUG] removeFields %v", removeFields) + if len(removeFields) > 0 { + input.SetRemoveFields(removeFields) + } + + _, err := conn.ModifyTrafficMirrorSession(input) + if nil != err { + return fmt.Errorf("Error updating traffic mirror session %v", err) + } + + return resourceAwsEc2TrafficMirrorSessionRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorSessionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + sessionId := d.Id() + input := &ec2.DescribeTrafficMirrorSessionsInput{ + TrafficMirrorSessionIds: []*string{ + &sessionId, + }, + } + + out, err := conn.DescribeTrafficMirrorSessions(input) + if nil != err { + return fmt.Errorf("error describing EC2 Traffic Mirror Session (%s): %w", sessionId, err) + } + + if 0 == len(out.TrafficMirrorSessions) { + log.Printf("[WARN] EC2 Traffic Mirror Session (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + session := out.TrafficMirrorSessions[0] + d.Set("network_interface_id", session.NetworkInterfaceId) + d.Set("session_number", session.SessionNumber) + d.Set("traffic_mirror_filter_id", session.TrafficMirrorFilterId) + d.Set("traffic_mirror_target_id", session.TrafficMirrorTargetId) + d.Set("description", session.Description) + d.Set("packet_length", session.PacketLength) + d.Set("virtual_network_id", session.VirtualNetworkId) + + return nil +} + +func resourceAwsEc2TrafficMirrorSessionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + sessionId := d.Id() + input := &ec2.DeleteTrafficMirrorSessionInput{ + TrafficMirrorSessionId: &sessionId, + } + + _, err := conn.DeleteTrafficMirrorSession(input) + if nil != err { + return fmt.Errorf("error deleting EC2 Traffic Mirror Session (%s): %w", sessionId, err) + } + + return nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_session_test.go b/aws/resource_aws_ec2_traffic_mirror_session_test.go new file mode 100644 index 00000000000..65468c44c73 --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_session_test.go @@ -0,0 +1,244 @@ +package aws + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2TrafficMirrorSession_basic(t *testing.T) { + resourceName := "aws_ec2_traffic_mirror_session.session" + description := "test session" + session := acctest.RandIntRange(1, 32766) + lbName := acctest.RandString(31) + pLen := acctest.RandIntRange(1, 255) + vni := acctest.RandIntRange(1, 16777216) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TrafficMirrorSession(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2TrafficMirrorSessionDestroy, + Steps: []resource.TestStep{ + //create + { + Config: testAccTrafficMirrorSessionConfig(lbName, session), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorSessionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "packet_length", "0"), + resource.TestCheckResourceAttr(resourceName, "session_number", strconv.Itoa(session)), + resource.TestCheckNoResourceAttr(resourceName, "virtual_network_id"), + ), + }, + // update of description, packet length and VNI + { + Config: testAccTrafficMirrorSessionConfigWithOptionals(description, lbName, session, pLen, vni), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorSessionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestCheckResourceAttr(resourceName, "packet_length", strconv.Itoa(pLen)), + resource.TestCheckResourceAttr(resourceName, "session_number", strconv.Itoa(session)), + resource.TestCheckResourceAttr(resourceName, "virtual_network_id", strconv.Itoa(vni)), + ), + }, + // removal of description, packet length and VNI + { + Config: testAccTrafficMirrorSessionConfig(lbName, session), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorSessionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "packet_length", "0"), + resource.TestCheckResourceAttr(resourceName, "session_number", strconv.Itoa(session)), + resource.TestCheckResourceAttr(resourceName, "virtual_network_id", "0"), + ), + }, + // import test without VNI + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"virtual_network_id"}, + }, + }, + }) +} + +func testAccCheckAWSEc2TrafficMirrorSessionExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set for %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + out, err := conn.DescribeTrafficMirrorSessions(&ec2.DescribeTrafficMirrorSessionsInput{ + TrafficMirrorSessionIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if err != nil { + return err + } + + if 0 == len(out.TrafficMirrorSessions) { + return fmt.Errorf("Traffic mirror session %s not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccTrafficMirrorSessionConfigBase(lbName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "azs" { + state = "available" +} + +data "aws_ami" "amzn-linux" { + most_recent = true + + filter { + name = "name" + values = ["amzn2-ami-hvm-2.0*"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } + + owners = ["137112412989"] +} + +resource "aws_vpc" "vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "sub1" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.0.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[0]}" +} + +resource "aws_subnet" "sub2" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[1]}" +} + +resource "aws_instance" "src" { + ami = "${data.aws_ami.amzn-linux.id}" + instance_type = "m5.large" # m5.large required because only Nitro instances support mirroring + subnet_id = "${aws_subnet.sub1.id}" +} + +resource "aws_lb" "lb" { + name = "%s" + internal = true + load_balancer_type = "network" + subnets = ["${aws_subnet.sub1.id}", "${aws_subnet.sub2.id}"] + + enable_deletion_protection = false + + tags = { + Environment = "production" + } +} + +resource "aws_ec2_traffic_mirror_filter" "filter" { +} + +resource "aws_ec2_traffic_mirror_target" "target" { + network_load_balancer_arn = "${aws_lb.lb.arn}" +} + +`, lbName) +} + +func testAccTrafficMirrorSessionConfig(lbName string, session int) string { + return fmt.Sprintf(` +%s + +resource "aws_ec2_traffic_mirror_session" "session" { + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + traffic_mirror_target_id = "${aws_ec2_traffic_mirror_target.target.id}" + network_interface_id = "${aws_instance.src.primary_network_interface_id}" + session_number = %d +} +`, testAccTrafficMirrorSessionConfigBase(lbName), session) +} + +func testAccTrafficMirrorSessionConfigWithOptionals(description string, lbName string, session, pLen, vni int) string { + return fmt.Sprintf(` +%s + +resource "aws_ec2_traffic_mirror_session" "session" { + description = "%s" + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + traffic_mirror_target_id = "${aws_ec2_traffic_mirror_target.target.id}" + network_interface_id = "${aws_instance.src.primary_network_interface_id}" + session_number = %d + packet_length = %d + virtual_network_id = %d +} +`, testAccTrafficMirrorSessionConfigBase(lbName), description, session, pLen, vni) +} + +func testAccPreCheckAWSEc2TrafficMirrorSession(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := conn.DescribeTrafficMirrorSessions(&ec2.DescribeTrafficMirrorSessionsInput{}) + + if testAccPreCheckSkipError(err) { + t.Skip("skipping traffic mirror sessions acceprance test: ", err) + } + + if err != nil { + t.Fatal("Unexpected PreCheck error: ", err) + } +} + +func testAccCheckAWSEc2TrafficMirrorSessionDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_traffic_mirror_session" { + continue + } + + out, err := conn.DescribeTrafficMirrorSessions(&ec2.DescribeTrafficMirrorSessionsInput{ + TrafficMirrorSessionIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if isAWSErr(err, "InvalidTrafficMirrorSessionId.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if len(out.TrafficMirrorSessions) != 0 { + return fmt.Errorf("Traffic mirror session %s still not destroyed", rs.Primary.ID) + } + } + + return nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_target.go b/aws/resource_aws_ec2_traffic_mirror_target.go new file mode 100644 index 00000000000..3d44f95490b --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_target.go @@ -0,0 +1,121 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceAwsEc2TrafficMirrorTarget() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2TrafficMirrorTargetCreate, + Read: resourceAwsEc2TrafficMirrorTargetRead, + Delete: resourceAwsEc2TrafficMirrorTargetDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "network_interface_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{ + "network_interface_id", + "network_load_balancer_arn", + }, + }, + "network_load_balancer_arn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{ + "network_interface_id", + "network_load_balancer_arn", + }, + }, + }, + } +} + +func resourceAwsEc2TrafficMirrorTargetCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := &ec2.CreateTrafficMirrorTargetInput{} + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("network_interface_id"); ok { + input.NetworkInterfaceId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("network_load_balancer_arn"); ok { + input.NetworkLoadBalancerArn = aws.String(v.(string)) + } + + out, err := conn.CreateTrafficMirrorTarget(input) + if err != nil { + return fmt.Errorf("Error creating traffic mirror target %v", err) + } + + d.SetId(*out.TrafficMirrorTarget.TrafficMirrorTargetId) + + return resourceAwsEc2TrafficMirrorTargetRead(d, meta) +} + +func resourceAwsEc2TrafficMirrorTargetRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + targetId := d.Id() + input := &ec2.DescribeTrafficMirrorTargetsInput{ + TrafficMirrorTargetIds: []*string{&targetId}, + } + + out, err := conn.DescribeTrafficMirrorTargets(input) + if isAWSErr(err, "InvalidTrafficMirrorTargetId.NotFound", "") { + log.Printf("[WARN] EC2 Traffic Mirror Target (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error describing EC2 Traffic Mirror Target (%s): %w", targetId, err) + } + + if nil == out || 0 == len(out.TrafficMirrorTargets) { + log.Printf("[WARN] EC2 Traffic Mirror Target (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + target := out.TrafficMirrorTargets[0] + d.Set("description", target.Description) + d.Set("network_interface_id", target.NetworkInterfaceId) + d.Set("network_load_balancer_arn", target.NetworkLoadBalancerArn) + + return nil +} + +func resourceAwsEc2TrafficMirrorTargetDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + targetId := d.Id() + input := &ec2.DeleteTrafficMirrorTargetInput{ + TrafficMirrorTargetId: &targetId, + } + + _, err := conn.DeleteTrafficMirrorTarget(input) + if nil != err { + return fmt.Errorf("error deleting EC2 Traffic Mirror Target (%s): %w", targetId, err) + } + + return nil +} diff --git a/aws/resource_aws_ec2_traffic_mirror_target_test.go b/aws/resource_aws_ec2_traffic_mirror_target_test.go new file mode 100644 index 00000000000..6fad7456d0c --- /dev/null +++ b/aws/resource_aws_ec2_traffic_mirror_target_test.go @@ -0,0 +1,242 @@ +package aws + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccAWSEc2TrafficMirrorTarget_nlb(t *testing.T) { + resourceName := "aws_ec2_traffic_mirror_target.target" + description := "test nlb target" + lbName := acctest.RandString(32) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TrafficMirrorTarget(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2TrafficMirrorTargetDestroy, + Steps: []resource.TestStep{ + //create + { + Config: testAccTrafficMirrorTargetConfigNlb(description, lbName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorTargetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestMatchResourceAttr(resourceName, "network_load_balancer_arn", regexp.MustCompile("arn:aws:elasticloadbalancing:.*")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSEc2TrafficMirrorTarget_eni(t *testing.T) { + resourceName := "aws_ec2_traffic_mirror_target.target" + description := "test eni target" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAWSEc2TrafficMirrorTarget(t) + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEc2TrafficMirrorTargetDestroy, + Steps: []resource.TestStep{ + //create + { + Config: testAccTrafficMirrorTargetConfigEni(description), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEc2TrafficMirrorTargetExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestMatchResourceAttr(resourceName, "network_interface_id", regexp.MustCompile("eni-.*")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSEc2TrafficMirrorTargetExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set for %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + out, err := conn.DescribeTrafficMirrorTargets(&ec2.DescribeTrafficMirrorTargetsInput{ + TrafficMirrorTargetIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if err != nil { + return err + } + + if 0 == len(out.TrafficMirrorTargets) { + return fmt.Errorf("Traffic mirror target %s not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccTrafficMirrorTargetConfigNlb(description string, lbName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "azs" { + state = "available" +} + +resource "aws_vpc" "vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "sub1" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.0.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[0]}" +} + +resource "aws_subnet" "sub2" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[1]}" +} + +resource "aws_lb" "lb" { + name = "%s" + internal = true + load_balancer_type = "network" + subnets = ["${aws_subnet.sub1.id}", "${aws_subnet.sub2.id}"] + + enable_deletion_protection = false + + tags = { + Environment = "production" + } +} + +resource "aws_ec2_traffic_mirror_target" "target" { + description = "%s" + network_load_balancer_arn = "${aws_lb.lb.arn}" +} +`, lbName, description) +} + +func testAccTrafficMirrorTargetConfigEni(description string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "azs" { + state = "available" +} + +data "aws_ami" "amzn-linux" { + most_recent = true + + filter { + name = "name" + values = ["amzn2-ami-hvm-2.0*"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } + + owners = ["137112412989"] +} + +resource "aws_vpc" "vpc" { + cidr_block = "10.0.0.0/16" +} + +resource "aws_subnet" "sub1" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.0.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[0]}" +} + +resource "aws_subnet" "sub2" { + vpc_id = "${aws_vpc.vpc.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "${data.aws_availability_zones.azs.names[1]}" +} + +resource "aws_instance" "src" { + ami = "${data.aws_ami.amzn-linux.id}" + instance_type = "t2.micro" + subnet_id = "${aws_subnet.sub1.id}" +} + +resource "aws_ec2_traffic_mirror_target" "target" { + description = "%s" + network_interface_id = "${aws_instance.src.primary_network_interface_id}" +} +`, description) +} + +func testAccPreCheckAWSEc2TrafficMirrorTarget(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + _, err := conn.DescribeTrafficMirrorTargets(&ec2.DescribeTrafficMirrorTargetsInput{}) + + if testAccPreCheckSkipError(err) { + t.Skip("skipping traffic mirror target acceprance test: ", err) + } + + if err != nil { + t.Fatal("Unexpected PreCheck error: ", err) + } +} + +func testAccCheckAWSEc2TrafficMirrorTargetDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_traffic_mirror_target" { + continue + } + + out, err := conn.DescribeTrafficMirrorTargets(&ec2.DescribeTrafficMirrorTargetsInput{ + TrafficMirrorTargetIds: []*string{ + aws.String(rs.Primary.ID), + }, + }) + + if isAWSErr(err, "InvalidTrafficMirrorTargetId.NotFound", "") { + continue + } + + if err != nil { + return err + } + + if len(out.TrafficMirrorTargets) != 0 { + return fmt.Errorf("Traffic mirror target %s still not destroyed", rs.Primary.ID) + } + } + + return nil +} diff --git a/website/aws.erb b/website/aws.erb index 359187c75d0..fe61a98e368 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1035,6 +1035,21 @@
  • aws_ebs_volume
  • +
  • + aws_ec2_traffic_mirror_filter +
  • + +
  • + aws_ec2_traffic_mirror_filter_rule +
  • + +
  • + aws_ec2_traffic_mirror_target +
  • + +
  • + aws_ec2_traffic_mirror_session +
  • aws_ec2_transit_gateway
  • diff --git a/website/docs/r/ec2_traffic_mirror_filter.html.markdown b/website/docs/r/ec2_traffic_mirror_filter.html.markdown new file mode 100644 index 00000000000..93dcf6433ac --- /dev/null +++ b/website/docs/r/ec2_traffic_mirror_filter.html.markdown @@ -0,0 +1,44 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_traffic_mirror_filter" +description: |- + Provides an Traffic mirror filter +--- + +# Resource: aws_ec2_traffic_mirror_filter + +Provides an Traffic mirror filter. +Read [limits and considerations](https://docs.aws.amazon.com/vpc/latest/mirroring/traffic-mirroring-considerations.html) for traffic mirroring + +## Example Usage + +To create a basic traffic mirror filter + +```hcl +resource "aws_ec2_traffic_mirror_filter" "foo" { + description = "traffic mirror filter - terraform example" + network_services = ["amazon-dns"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional, Forces new resource) A description of the filter. +* `network_services` - (Optional) List of amazon network services that should be mirrored. Valid values: amazon-dns + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the filter. + +## Import + +Traffic mirror filter can be imported using the `id`, e.g. + +``` +$ terraform import aws_ec2_traffic_mirror_filter.foo tmf-0fbb93ddf38198f64 +``` diff --git a/website/docs/r/ec2_traffic_mirror_filter_rule.html.markdown b/website/docs/r/ec2_traffic_mirror_filter_rule.html.markdown new file mode 100644 index 00000000000..759fdf8888e --- /dev/null +++ b/website/docs/r/ec2_traffic_mirror_filter_rule.html.markdown @@ -0,0 +1,88 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_traffic_mirror_filter_rule" +description: |- + Provides an Traffic mirror filter rule +--- + +# Resource: aws_ec2_traffic_mirror_filter_rule + +Provides an Traffic mirror filter rule. +Read [limits and considerations](https://docs.aws.amazon.com/vpc/latest/mirroring/traffic-mirroring-considerations.html) for traffic mirroring + +## Example Usage + +To create a basic traffic mirror session + +```hcl +resource "aws_ec2_traffic_mirror_filter" "filter" { + description = "traffic mirror filter - terraform example" + network_services = ["amazon-dns"] +} + +resource "aws_ec2_traffic_mirror_filter_rule" "ruleout" { + description = "test rule" + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + destination_cidr_block = "10.0.0.0/8" + source_cidr_block = "10.0.0.0/8" + rule_number = 1 + rule_action = "accept" + traffic_direction = "egress" +} + +resource "aws_ec2_traffic_mirror_filter_rule" "rulein" { + description = "test rule" + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + destination_cidr_block = "10.0.0.0/8" + source_cidr_block = "10.0.0.0/8" + rule_number = 1 + rule_action = "accept" + traffic_direction = "ingress" + protocol = 6 + + destination_port_range { + from_port = 22 + to_port = 53 + } + + source_port_range { + from_port = 0 + to_port = 10 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional) A description of the traffic mirror filter rule. +* `traffic_mirror_filter_id` - (Required) ID of the traffic mirror filter to which this rule should be added +* `destination_cidr_block` - (Required) The destination CIDR block to assign to the Traffic Mirror rule. +* `destination_port_range` - (Optional) The destination port range. Supported only when the protocol is set to TCP(6) or UDP(17). See Traffic mirror port range documented below +* `protocol` - (Optional) The protocol number, for example 17 (UDP), to assign to the Traffic Mirror rule. For information about the protocol value, see [Protocol Numbers](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) on the Internet Assigned Numbers Authority (IANA) website. +* `rule_action` - (Required) The action to take (accept | reject) on the filtered traffic. Valid values are `accept` and `reject` +* `rule_number` - (Required) The number of the Traffic Mirror rule. This number must be unique for each Traffic Mirror rule in a given direction. The rules are processed in ascending order by rule number. +* `source_cidr_block` - (Required) The source CIDR block to assign to the Traffic Mirror rule. +* `source_port_range` - (Optional) The source port range. Supported only when the protocol is set to TCP(6) or UDP(17). See Traffic mirror port range documented below +* `traffic_direction` - (Required) The direction of traffic to be captured. Valid values are `ingress` and `egress` + +Traffic mirror port range support following attributes: + +* `from_port` - (Optional) Starting port of the range +* `to_port` - (Optional) Ending port of the range + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the traffic mirror filter rule. + +## Import + +Traffic mirror rules can be imported using the `traffic_mirror_filter_id` and `id` separated by `:` e.g. + +``` +$ terraform import aws_ec2_traffic_mirror_filter_rule.rule tmf-0fbb93ddf38198f64:tmfr-05a458f06445d0aee +``` diff --git a/website/docs/r/ec2_traffic_mirror_session.html.markdown b/website/docs/r/ec2_traffic_mirror_session.html.markdown new file mode 100644 index 00000000000..d5e6f12b9f9 --- /dev/null +++ b/website/docs/r/ec2_traffic_mirror_session.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_traffic_mirror_session" +description: |- + Provides an Traffic mirror session +--- + +# Resource: aws_ec2_traffic_mirror_session + +Provides an Traffic mirror session. +Read [limits and considerations](https://docs.aws.amazon.com/vpc/latest/mirroring/traffic-mirroring-considerations.html) for traffic mirroring + +## Example Usage + +To create a basic traffic mirror session + +```hcl +resource "aws_ec2_traffic_mirror_filter" "filter" { + description = "traffic mirror filter - terraform example" + network_services = ["amazon-dns"] +} + +resource "aws_ec2_traffic_mirror_target" "target" { + network_load_balancer_arn = "${aws_lb.lb.arn}" +} + +resource "aws_ec2_traffic_mirror_session" "session" { + description = "traffic mirror session - terraform example" + network_interface_id = "${aws_instance.test.primary_network_interface_id}" + traffic_mirror_filter_id = "${aws_ec2_traffic_mirror_filter.filter.id}" + traffic_mirror_target_id = "${aws_ec2_traffic_mirror_target.target.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional) A description of the traffic mirror session. +* `network_interface_id` - (Required, Forces new) ID of the source network interface. Not all network interfaces are eligible as mirror sources. On EC2 instances only nitro based instances support mirroring. +* `traffic_mirror_filter_id` - (Required) ID of the traffic mirror filter to be used +* `traffic_mirror_target_id` - (Required) ID of the traffic mirror target to be used +* `packet_length` - (Optional) The number of bytes in each packet to mirror. These are bytes after the VXLAN header. Do not specify this parameter when you want to mirror the entire packet. To mirror a subset of the packet, set this to the length (in bytes) that you want to mirror. +* `session_number` - (Required) - The session number determines the order in which sessions are evaluated when an interface is used by multiple sessions. The first session with a matching filter is the one that mirrors the packets. +* `virtual_network_id` - (Optional) - The VXLAN ID for the Traffic Mirror session. For more information about the VXLAN protocol, see RFC 7348. If you do not specify a VirtualNetworkId, an account-wide unique id is chosen at random. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the session. + +## Import + +Traffic mirror sessions can be imported using the `id`, e.g. + +``` +$ terraform import aws_ec2_traffic_mirror_session.session tms-0d8aa3ca35897b82e +``` diff --git a/website/docs/r/ec2_traffic_mirror_target.html.markdown b/website/docs/r/ec2_traffic_mirror_target.html.markdown new file mode 100644 index 00000000000..5d052e17da1 --- /dev/null +++ b/website/docs/r/ec2_traffic_mirror_target.html.markdown @@ -0,0 +1,53 @@ +--- +subcategory: "EC2" +layout: "aws" +page_title: "AWS: aws_ec2_traffic_mirror_target" +description: |- + Provides an Traffic mirror target +--- + +# Resource: aws_ec2_traffic_mirror_target + +Provides an Traffic mirror target. +Read [limits and considerations](https://docs.aws.amazon.com/vpc/latest/mirroring/traffic-mirroring-considerations.html) for traffic mirroring + +## Example Usage + +To create a basic traffic mirror session + +```hcl +resource "aws_ec2_traffic_mirror_target" "nlb" { + description = "NLB target" + network_load_balancer_arn = "${aws_lb.lb.arn}" +} + +resource "aws_ec2_traffic_mirror_target" "eni" { + description = "ENI target" + network_interface_id = "${aws_instance.test.primary_network_interface_id}" +} + +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional, Forces new) A description of the traffic mirror session. +* `network_interface_id` - (Optional, Forces new) The network interface ID that is associated with the target. +* `network_load_balancer_arn` - (Optional, Forces new) The Amazon Resource Name (ARN) of the Network Load Balancer that is associated with the target. + +**NOTE:** Either `network_interface_id` or `network_load_balancer_arn` should be specified and both should not be specified together + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the traffic mirror target. + +## Import + +Traffic mirror targets can be imported using the `id`, e.g. + +``` +$ terraform import aws_ec2_traffic_mirror_target.target tmt-0c13a005422b86606 +```