diff --git a/CHANGELOG.md b/CHANGELOG.md index 45d6c45dc2..e21dd2dbed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ FEATURES: * **New Resource:** `aws_load_balancer_policy` [GH-7458] * **New Resource:** `aws_load_balancer_backend_server_policy` [GH-7458] * **New Resource:** `aws_load_balancer_listener_policy` [GH-7458] + * **New Data Source:** `aws_ip_ranges` [GH-7984] + * **New Data Source:** `fastly_ip_ranges` [GH-7984] IMPROVEMENTS * provider/aws: Introduce `aws_elasticsearch_domain` `elasticsearch_version` field (to specify ES version) [GH-7860] diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go new file mode 100644 index 0000000000..32e9d89883 --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -0,0 +1,151 @@ +package aws + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/schema" +) + +type dataSourceAwsIPRangesResult struct { + CreateDate string + Prefixes []dataSourceAwsIPRangesPrefix + SyncToken string +} + +type dataSourceAwsIPRangesPrefix struct { + IpPrefix string `json:"ip_prefix"` + Region string + Service string +} + +func dataSourceAwsIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsIPRangesRead, + + Schema: map[string]*schema.Schema{ + "cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "create_date": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "regions": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "services": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "sync_token": &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { + + conn := cleanhttp.DefaultClient() + + log.Printf("[DEBUG] Reading IP ranges") + + res, err := conn.Get("https://ip-ranges.amazonaws.com/ip-ranges.json") + + if err != nil { + return fmt.Errorf("Error listing IP ranges: %s", err) + } + + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + + if err != nil { + return fmt.Errorf("Error reading response body: %s", err) + } + + result := new(dataSourceAwsIPRangesResult) + + if err := json.Unmarshal(data, result); err != nil { + return fmt.Errorf("Error parsing result: %s", err) + } + + if err := d.Set("create_date", result.CreateDate); err != nil { + return fmt.Errorf("Error setting create date: %s", err) + } + + syncToken, err := strconv.Atoi(result.SyncToken) + + if err != nil { + return fmt.Errorf("Error while converting sync token: %s", err) + } + + d.SetId(result.SyncToken) + + if err := d.Set("sync_token", syncToken); err != nil { + return fmt.Errorf("Error setting sync token: %s", err) + } + + get := func(key string) *schema.Set { + + set := d.Get(key).(*schema.Set) + + for _, e := range set.List() { + + s := e.(string) + + set.Remove(s) + set.Add(strings.ToLower(s)) + + } + + return set + + } + + var ( + regions = get("regions") + services = get("services") + noRegionFilter = regions.Len() == 0 + prefixes []string + ) + + for _, e := range result.Prefixes { + + var ( + matchRegion = noRegionFilter || regions.Contains(strings.ToLower(e.Region)) + matchService = services.Contains(strings.ToLower(e.Service)) + ) + + if matchRegion && matchService { + prefixes = append(prefixes, e.IpPrefix) + } + + } + + if len(prefixes) == 0 { + return fmt.Errorf(" No IP ranges result from filters") + } + + sort.Strings(prefixes) + + if err := d.Set("cidr_blocks", prefixes); err != nil { + return fmt.Errorf("Error setting ip ranges: %s", err) + } + + return nil + +} diff --git a/builtin/providers/aws/data_source_aws_ip_ranges_test.go b/builtin/providers/aws/data_source_aws_ip_ranges_test.go new file mode 100644 index 0000000000..5e8f4b13da --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ip_ranges_test.go @@ -0,0 +1,128 @@ +package aws + +import ( + "fmt" + "net" + "regexp" + "sort" + "strconv" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIPRanges(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSIPRangesConfig, + Check: resource.ComposeTestCheckFunc( + testAccAWSIPRanges("data.aws_ip_ranges.some"), + ), + }, + }, + }) +} + +func testAccAWSIPRanges(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + var ( + cidrBlockSize int + createDate time.Time + err error + syncToken int + ) + + if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil { + return err + } + + if cidrBlockSize < 10 { + return fmt.Errorf("cidr_blocks for eu-west-1 seem suspiciously low: %d", cidrBlockSize) + } + + if createDate, err = time.Parse("2006-01-02-15-04-05", a["create_date"]); err != nil { + return err + } + + if syncToken, err = strconv.Atoi(a["sync_token"]); err != nil { + return err + } + + if syncToken != int(createDate.Unix()) { + return fmt.Errorf("sync_token %d does not match create_date %s", syncToken, createDate) + } + + var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize) + + for i := range make([]string, cidrBlockSize) { + + block := a[fmt.Sprintf("cidr_blocks.%d", i)] + + if _, _, err := net.ParseCIDR(block); err != nil { + return fmt.Errorf("malformed CIDR block %s: %s", block, err) + } + + cidrBlocks[i] = block + + } + + if !sort.IsSorted(cidrBlocks) { + return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks) + } + + var ( + regionMember = regexp.MustCompile(`regions\.\d+`) + regions, services int + serviceMember = regexp.MustCompile(`services\.\d+`) + ) + + for k, v := range a { + + if regionMember.MatchString(k) { + + if !(v == "eu-west-1" || v == "EU-central-1") { + return fmt.Errorf("unexpected region %s", v) + } + + regions = regions + 1 + + } + + if serviceMember.MatchString(k) { + + if v != "EC2" { + return fmt.Errorf("unexpected service %s", v) + } + + services = services + 1 + } + + } + + if regions != 2 { + return fmt.Errorf("unexpected number of regions: %d", regions) + } + + if services != 1 { + return fmt.Errorf("unexpected number of services: %d", services) + } + + return nil + } +} + +const testAccAWSIPRangesConfig = ` +data "aws_ip_ranges" "some" { + regions = [ "eu-west-1", "EU-central-1" ] + services = [ "EC2" ] +} +` diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 81dbc1fe62..dab42ba87c 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -114,6 +114,7 @@ func Provider() terraform.ResourceProvider { "aws_ami": dataSourceAwsAmi(), "aws_availability_zones": dataSourceAwsAvailabilityZones(), "aws_iam_policy_document": dataSourceAwsIamPolicyDocument(), + "aws_ip_ranges": dataSourceAwsIPRanges(), "aws_s3_bucket_object": dataSourceAwsS3BucketObject(), "aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(), }, diff --git a/builtin/providers/fastly/data_source_ip_ranges.go b/builtin/providers/fastly/data_source_ip_ranges.go new file mode 100644 index 0000000000..cc418465c4 --- /dev/null +++ b/builtin/providers/fastly/data_source_ip_ranges.go @@ -0,0 +1,70 @@ +package fastly + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "sort" + "strconv" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +type dataSourceFastlyIPRangesResult struct { + Addresses []string +} + +func dataSourceFastlyIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyIPRangesRead, + + Schema: map[string]*schema.Schema{ + "cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) error { + + conn := cleanhttp.DefaultClient() + + log.Printf("[DEBUG] Reading IP ranges") + + res, err := conn.Get("https://api.fastly.com/public-ip-list") + + if err != nil { + return fmt.Errorf("Error listing IP ranges: %s", err) + } + + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + + if err != nil { + return fmt.Errorf("Error reading response body: %s", err) + } + + d.SetId(strconv.Itoa(hashcode.String(string(data)))) + + result := new(dataSourceFastlyIPRangesResult) + + if err := json.Unmarshal(data, result); err != nil { + return fmt.Errorf("Error parsing result: %s", err) + } + + sort.Strings(result.Addresses) + + if err := d.Set("cidr_blocks", result.Addresses); err != nil { + return fmt.Errorf("Error setting ip ranges: %s", err) + } + + return nil + +} diff --git a/builtin/providers/fastly/data_source_ip_ranges_test.go b/builtin/providers/fastly/data_source_ip_ranges_test.go new file mode 100644 index 0000000000..26e4d8f566 --- /dev/null +++ b/builtin/providers/fastly/data_source_ip_ranges_test.go @@ -0,0 +1,73 @@ +package fastly + +import ( + "fmt" + "net" + "sort" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccFastlyIPRanges(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccFastlyIPRangesConfig, + Check: resource.ComposeTestCheckFunc( + testAccFastlyIPRanges("data.fastly_ip_ranges.some"), + ), + }, + }, + }) +} + +func testAccFastlyIPRanges(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + var ( + cidrBlockSize int + err error + ) + + if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil { + return err + } + + if cidrBlockSize < 10 { + return fmt.Errorf("cidr_blocks seem suspiciously low: %d", cidrBlockSize) + } + + var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize) + + for i := range make([]string, cidrBlockSize) { + + block := a[fmt.Sprintf("cidr_blocks.%d", i)] + + if _, _, err := net.ParseCIDR(block); err != nil { + return fmt.Errorf("malformed CIDR block %s: %s", block, err) + } + + cidrBlocks[i] = block + + } + + if !sort.IsSorted(cidrBlocks) { + return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks) + } + + return nil + } +} + +const testAccFastlyIPRangesConfig = ` +data "fastly_ip_ranges" "some" { +} +` diff --git a/builtin/providers/fastly/provider.go b/builtin/providers/fastly/provider.go index f68c6705be..eee4be8e83 100644 --- a/builtin/providers/fastly/provider.go +++ b/builtin/providers/fastly/provider.go @@ -18,6 +18,9 @@ func Provider() terraform.ResourceProvider { Description: "Fastly API Key from https://app.fastly.com/#account", }, }, + DataSourcesMap: map[string]*schema.Resource{ + "fastly_ip_ranges": dataSourceFastlyIPRanges(), + }, ResourcesMap: map[string]*schema.Resource{ "fastly_service_v1": resourceServiceV1(), }, diff --git a/website/source/docs/providers/aws/d/ip_ranges.html.markdown b/website/source/docs/providers/aws/d/ip_ranges.html.markdown new file mode 100644 index 0000000000..f7fe90a8cd --- /dev/null +++ b/website/source/docs/providers/aws/d/ip_ranges.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "aws" +page_title: "AWS: aws_ip_ranges" +sidebar_current: "docs-aws-datasource-ip_ranges" +description: |- + Get information on AWS IP ranges. +--- + +# aws\_ip_ranges + +Use this data source to get the [IP ranges][1] of various AWS products and services. + +## Example Usage + +``` +data "aws_ip_ranges" "european_ec2" { + regions = [ "eu-west-1", "eu-central-1" ] + services = [ "ec2" ] +} + +resource "aws_security_group" "from_europe" { + + name = "from_europe" + + ingress { + from_port = "443" + to_port = "443" + protocol = "tcp" + cidr_blocks = [ "${data.aws_ip_ranges.european_ec2.blocks}" ] + } + + tags { + CreateDate = "${data.aws_ip_ranges.european_ec2.create_date}" + SyncToken = "${data.aws_ip_ranges.european_ec2.sync_token}" + } + +} +``` + +## Argument Reference + +* `regions` - (Optional) Filter IP ranges by regions (or include all regions, if +omitted). Valid items are `global` (for `cloudfront`) as well as all AWS regions +(e.g. `eu-central-1`) + +* `services` - (Required) Filter IP ranges by services. Valid items are `amazon` +(for amazon.com), `cloudfront`, `ec2`, `route53` and `route53_healthchecks`. + +~> **NOTE:** If the specified combination of regions and services does not yield any +CIDR blocks, Terraform will fail. + +## Attributes Reference + +* `cidr_blocks` - The lexically ordered list of CIDR blocks. +* `create_date` - The publication time of the IP ranges (e.g. `2016-08-03-23-46-05`). +* `sync_token` - The publication time of the IP ranges, in Unix epoch time format + (e.g. `1470267965`). + +[1]: http://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html diff --git a/website/source/docs/providers/fastly/d/ip_ranges.html.markdown b/website/source/docs/providers/fastly/d/ip_ranges.html.markdown new file mode 100644 index 0000000000..75cd59b009 --- /dev/null +++ b/website/source/docs/providers/fastly/d/ip_ranges.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_ip_ranges" +sidebar_current: "docs-fastly-datasource-ip_ranges" +description: |- + Get information on Fastly IP ranges. +--- + +# fastly\_ip_ranges + +Use this data source to get the [IP ranges][1] of Fastly edge nodes. + +## Example Usage + +``` +data "fastly_ip_ranges" "fastly" { +} + +resource "aws_security_group" "from_fastly" { + + name = "from_fastly" + + ingress { + from_port = "443" + to_port = "443" + protocol = "tcp" + cidr_blocks = [ "${data.fastly_ip_ranges.fastly.cidr_blocks}" ] + } + +} +``` + +## Attributes Reference + +* `cidr_blocks` - The lexically ordered list of CIDR blocks. + +[1]: https://docs.fastly.com/guides/securing-communications/accessing-fastlys-ip-ranges diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 6682cad462..6f9fbbe72e 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -25,6 +25,9 @@