diff --git a/aws/config.go b/aws/config.go index 4a3a56a7075..ace10ecba6c 100644 --- a/aws/config.go +++ b/aws/config.go @@ -390,7 +390,7 @@ func (c *Config) Client() (interface{}, error) { fsxconn: fsx.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["fsx"])})), gameliftconn: gamelift.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["gamelift"])})), glacierconn: glacier.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["glacier"])})), - globalacceleratorconn: globalaccelerator.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["globalaccelerator"])})), + globalacceleratorconn: globalaccelerator.New(sess.Copy(&aws.Config{Region: aws.String("us-west-2"), Endpoint: aws.String(c.Endpoints["globalaccelerator"])})), glueconn: glue.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["glue"])})), guarddutyconn: guardduty.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["guardduty"])})), iamconn: iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["iam"])})), @@ -439,7 +439,7 @@ func (c *Config) Client() (interface{}, error) { serverlessapplicationrepositoryconn: serverlessapplicationrepository.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["serverlessrepo"])})), sesConn: ses.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["ses"])})), sfnconn: sfn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["stepfunctions"])})), - shieldconn: shield.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["shield"])})), + shieldconn: shield.New(sess.Copy(&aws.Config{Region: aws.String("us-east-1"), Endpoint: aws.String(c.Endpoints["shield"])})), simpledbconn: simpledb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["sdb"])})), snsconn: sns.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["sns"])})), sqsconn: sqs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints["sqs"])})), diff --git a/aws/provider.go b/aws/provider.go index b8dbe2e4c53..fa667e19954 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -664,6 +664,7 @@ func Provider() terraform.ResourceProvider { "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), "aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(), "aws_service_discovery_service": resourceAwsServiceDiscoveryService(), + "aws_shield_protection": resourceAwsShieldProtection(), "aws_simpledb_domain": resourceAwsSimpleDBDomain(), "aws_ssm_activation": resourceAwsSsmActivation(), "aws_ssm_association": resourceAwsSsmAssociation(), diff --git a/aws/resource_aws_shield_protection.go b/aws/resource_aws_shield_protection.go new file mode 100644 index 00000000000..5548a22b7c9 --- /dev/null +++ b/aws/resource_aws_shield_protection.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsShieldProtection() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsShieldProtectionCreate, + Read: resourceAwsShieldProtectionRead, + Delete: resourceAwsShieldProtectionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "resource_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + } +} + +func resourceAwsShieldProtectionCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).shieldconn + + input := &shield.CreateProtectionInput{ + Name: aws.String(d.Get("name").(string)), + ResourceArn: aws.String(d.Get("resource_arn").(string)), + } + + resp, err := conn.CreateProtection(input) + if err != nil { + return fmt.Errorf("error creating Shield Protection: %s", err) + } + d.SetId(*resp.ProtectionId) + return resourceAwsShieldProtectionRead(d, meta) +} + +func resourceAwsShieldProtectionRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).shieldconn + + input := &shield.DescribeProtectionInput{ + ProtectionId: aws.String(d.Id()), + } + + resp, err := conn.DescribeProtection(input) + if err != nil { + return fmt.Errorf("error reading Shield Protection (%s): %s", d.Id(), err) + } + d.Set("name", resp.Protection.Name) + d.Set("resource_arn", resp.Protection.ResourceArn) + return nil +} + +func resourceAwsShieldProtectionDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).shieldconn + + input := &shield.DeleteProtectionInput{ + ProtectionId: aws.String(d.Id()), + } + + _, err := conn.DeleteProtection(input) + + if isAWSErr(err, shield.ErrCodeResourceNotFoundException, "") { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Shield Protection (%s): %s", d.Id(), err) + } + return nil +} diff --git a/aws/resource_aws_shield_protection_test.go b/aws/resource_aws_shield_protection_test.go new file mode 100644 index 00000000000..ce755e75647 --- /dev/null +++ b/aws/resource_aws_shield_protection_test.go @@ -0,0 +1,526 @@ +package aws + +import ( + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSShieldProtection_GlobalAccelerator(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionGlobalAcceleratorConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSShieldProtection_ElasticIPAddress(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionElasticIPAddressConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSShieldProtection_Alb(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionAlbConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSShieldProtection_Elb(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionElbConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSShieldProtection_Cloudfront(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionCloudfrontConfig(rName, testAccShieldProtectionCloudfrontRetainConfig()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSShieldProtection_Route53(t *testing.T) { + resourceName := "aws_shield_protection.acctest" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSShield(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSShieldProtectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccShieldProtectionRoute53HostedZoneConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSShieldProtectionExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSShieldProtectionDestroy(s *terraform.State) error { + shieldconn := testAccProvider.Meta().(*AWSClient).shieldconn + + for _, rs := range s.RootModule().Resources { + + if rs.Type != "aws_shield_protection" { + continue + } + + input := &shield.DescribeProtectionInput{ + ProtectionId: aws.String(rs.Primary.ID), + } + + resp, err := shieldconn.DescribeProtection(input) + + if isAWSErr(err, shield.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + if resp != nil && resp.Protection != nil && aws.StringValue(resp.Protection.Id) == rs.Primary.ID { + return fmt.Errorf("The Shield protection with ID %v still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckAWSShieldProtectionExists(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) + } + + conn := testAccProvider.Meta().(*AWSClient).shieldconn + + input := &shield.DescribeProtectionInput{ + ProtectionId: aws.String(rs.Primary.ID), + } + + _, err := conn.DescribeProtection(input) + + if err != nil { + return err + } + + return nil + } +} + +func testAccPreCheckAWSShield(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).shieldconn + + input := &shield.ListProtectionsInput{} + + _, err := conn.ListProtections(input) + + if testAccPreCheckSkipError(err) || isAWSErr(err, shield.ErrCodeResourceNotFoundException, "subscription does not exist") { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +// Set the environment variable TF_TEST_CLOUDFRONT_RETAIN +// when doing manual tests so that the test is not waiting for +// the distribution to be removed completely. +func testAccShieldProtectionCloudfrontRetainConfig() string { + if _, ok := os.LookupEnv("TF_TEST_CLOUDFRONT_RETAIN"); ok { + return "retain_on_delete = true" + } + return "" +} + +func testAccShieldProtectionRoute53HostedZoneConfig(rName string) string { + return fmt.Sprintf(` +variable "name" { + default = "%s" +} + +resource "aws_route53_zone" "acctest" { + name = "${var.name}.com." + comment = "Terraform Acceptance Tests" + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "arn:aws:route53:::hostedzone/${aws_route53_zone.acctest.zone_id}" +} +`, rName) +} + +func testAccShieldProtectionElbConfig(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +variable "subnets" { + default = ["10.0.1.0/24", "10.0.2.0/24"] + type = "list" +} + +variable "name" { + default = "%s" +} + +resource "aws_vpc" "acctest" { + cidr_block = "10.0.0.0/16" + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_subnet" "acctest" { + count = 2 + vpc_id = "${aws_vpc.acctest.id}" + cidr_block = "${element(var.subnets, count.index)}" + map_public_ip_on_launch = true + availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}" + + tags = { + foo = "bar" + Name = "${var.name}" + } + +} +resource "aws_elb" "acctest" { + name = "${var.name}" + #availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] + subnets = ["${aws_subnet.acctest.*.id[0]}", "${aws_subnet.acctest.*.id[1]}"] + internal = true + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + tags = { + foo = "bar" + Name = "${var.name}" + } + + cross_zone_load_balancing = true +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "${aws_elb.acctest.arn}" +} +`, rName) +} + +func testAccShieldProtectionAlbConfig(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" {} + +variable "subnets" { + default = ["10.0.1.0/24", "10.0.2.0/24"] + type = "list" +} + +variable "name" { + default = "%s" +} + +resource "aws_lb" "acctest" { + name = "${var.name}" + internal = true + security_groups = ["${aws_security_group.acctest.id}"] + subnets = ["${aws_subnet.acctest.*.id[0]}", "${aws_subnet.acctest.*.id[1]}"] + + idle_timeout = 30 + enable_deletion_protection = false + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_vpc" "acctest" { + cidr_block = "10.0.0.0/16" + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_subnet" "acctest" { + count = 2 + vpc_id = "${aws_vpc.acctest.id}" + cidr_block = "${element(var.subnets, count.index)}" + map_public_ip_on_launch = true + availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}" + + tags = { + foo = "bar" + Name = "${var.name}" + } + +} + +resource "aws_security_group" "acctest" { + name = "${var.name}" + description = "acctest" + vpc_id = "${aws_vpc.acctest.id}" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "${aws_lb.acctest.arn}" +} +`, rName) +} + +func testAccShieldProtectionCloudfrontConfig(rName, retainOnDelete string) string { + return fmt.Sprintf(` +variable "name" { + default = "%s" +} + +resource "aws_cloudfront_distribution" "acctest" { + origin { + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "https-only" + + origin_ssl_protocols = [ + "TLSv1", + "TLSv1.1", + "TLSv1.2", + ] + } + + # This is a fake origin and it's set to this name to indicate that. + domain_name = "${var.name}.com" + origin_id = "acctest" + } + + enabled = false + wait_for_deployment = false + + default_cache_behavior { + + allowed_methods = ["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "acctest" + + forwarded_values { + query_string = false + headers = ["*"] + + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 0 + max_ttl = 0 + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + tags = { + foo = "bar" + Name = "${var.name}" + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + %s +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "${aws_cloudfront_distribution.acctest.arn}" +} +`, rName, retainOnDelete) +} + +func testAccShieldProtectionElasticIPAddressConfig(rName string) string { + return fmt.Sprintf(` +variable "name" { + default = "%s" +} + +data "aws_availability_zones" "available" {} +data "aws_region" "current" {} +data "aws_caller_identity" "current" {} + +resource "aws_eip" "acctest" { + vpc = true + + tags = { + foo = "bar" + Name = "${var.name}" + } +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:eip-allocation/${aws_eip.acctest.id}" +} +`, rName) +} + +func testAccShieldProtectionGlobalAcceleratorConfig(rName string) string { + return fmt.Sprintf(` +variable "name" { + default = "%s" +} + +resource "aws_shield_protection" "acctest" { + name = "${var.name}" + resource_arn = "${aws_globalaccelerator_accelerator.acctest.id}" +} + +resource "aws_globalaccelerator_accelerator" "acctest" { + # provider = "aws.us-west-2" + name = "${var.name}" + ip_address_type = "IPV4" + enabled = true +} +`, rName) +} diff --git a/website/aws.erb b/website/aws.erb index 8475198510c..8c6652365c9 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2412,6 +2412,29 @@ +
  • + Shield Resources + +
  • + + > + Shield Resources + + + +
  • SimpleDB Resources