From de04ecb7ce0728a833924d084adbfe78dd842bf8 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 30 Jan 2018 18:14:46 -0800 Subject: [PATCH] Add data source for IP ranges --- cloudflare/data_source_ip_ranges.go | 98 ++++++++++++++++++++++++ cloudflare/data_source_ip_ranges_test.go | 74 ++++++++++++++++++ cloudflare/provider.go | 4 + website/cloudflare.erb | 29 ++++--- website/docs/d/ip_ranges.html.md | 38 +++++++++ 5 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 cloudflare/data_source_ip_ranges.go create mode 100644 cloudflare/data_source_ip_ranges_test.go create mode 100644 website/docs/d/ip_ranges.html.md diff --git a/cloudflare/data_source_ip_ranges.go b/cloudflare/data_source_ip_ranges.go new file mode 100644 index 0000000000..ed48632fae --- /dev/null +++ b/cloudflare/data_source_ip_ranges.go @@ -0,0 +1,98 @@ +package cloudflare + +import ( + "fmt" + "io/ioutil" + "log" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + urlIPV4s = "https://www.cloudflare.com/ips-v4" + urlIPV6s = "https://www.cloudflare.com/ips-v6" +) + +func dataSourceCloudflareIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudflareIPRangesRead, + + Schema: map[string]*schema.Schema{ + "cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ipv4_cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "ipv6_cidr_blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceCloudflareIPRangesRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Reading IPv4 ranges") + ipv4s, err := dataSourceCloudflareIPRangesGet(urlIPV4s) + if err != nil { + return fmt.Errorf("Error listing IPV4 ranges: %s", err) + } + sort.Strings(ipv4s) + + log.Printf("[DEBUG] Reading IPv6 ranges") + ipv6s, err := dataSourceCloudflareIPRangesGet(urlIPV6s) + if err != nil { + return fmt.Errorf("Error listing IPV6 ranges: %s", err) + } + sort.Strings(ipv6s) + + all := append([]string{}, ipv4s...) + all = append(all, ipv6s...) + + d.SetId(strconv.Itoa(hashcode.String(strings.Join(all, "|")))) + + if err := d.Set("cidr_blocks", all); err != nil { + return fmt.Errorf("Error setting all cidr blocks: %s", err) + } + + if err := d.Set("ipv4_cidr_blocks", ipv4s); err != nil { + return fmt.Errorf("Error setting ipv4 cidr blocks: %s", err) + } + + if err := d.Set("ipv6_cidr_blocks", ipv4s); err != nil { + return fmt.Errorf("Error setting ipv6 cidr blocks: %s", err) + } + + return nil +} + +// dataSourceCloudflareIPRangesGet performs an HTTP GET on the given URL and +// parses each line as an IP address. +func dataSourceCloudflareIPRangesGet(url string) ([]string, error) { + conn := cleanhttp.DefaultClient() + + res, err := conn.Get(url) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + list := strings.Split(strings.TrimSpace(string(body)), "\n") + return list, nil +} diff --git a/cloudflare/data_source_ip_ranges_test.go b/cloudflare/data_source_ip_ranges_test.go new file mode 100644 index 0000000000..fed3bf6a9a --- /dev/null +++ b/cloudflare/data_source_ip_ranges_test.go @@ -0,0 +1,74 @@ +package cloudflare + +import ( + "fmt" + "net" + "os" + "sort" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccCloudflareIPRanges(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCloudflareIPRangesConfig, + Check: resource.ComposeTestCheckFunc( + testAccCloudflareIPRanges("data.cloudflare_ip_ranges.some"), + ), + }, + }, + }) +} + +func testAccCloudflareIPRanges(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + fmt.Fprintf(os.Stderr, "%#v", a) + + 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 testAccCloudflareIPRangesConfig = ` +data "cloudflare_ip_ranges" "some" {} +` diff --git a/cloudflare/provider.go b/cloudflare/provider.go index 5dae2005b6..1e67df7879 100644 --- a/cloudflare/provider.go +++ b/cloudflare/provider.go @@ -24,6 +24,10 @@ func Provider() terraform.ResourceProvider { }, }, + DataSourcesMap: map[string]*schema.Resource{ + "cloudflare_ip_ranges": dataSourceCloudflareIPRanges(), + }, + ResourcesMap: map[string]*schema.Resource{ "cloudflare_record": resourceCloudFlareRecord(), }, diff --git a/website/cloudflare.erb b/website/cloudflare.erb index a4319e1ef1..575e827af8 100644 --- a/website/cloudflare.erb +++ b/website/cloudflare.erb @@ -3,20 +3,29 @@ diff --git a/website/docs/d/ip_ranges.html.md b/website/docs/d/ip_ranges.html.md new file mode 100644 index 0000000000..c9154e9efb --- /dev/null +++ b/website/docs/d/ip_ranges.html.md @@ -0,0 +1,38 @@ +--- +layout: "cloudflare" +page_title: "CloudFlare: cloudflare_ip_ranges" +sidebar_current: "docs-cloudflare-datasource-ip_ranges" +description: |- + Get information on CloudFlare IP ranges. +--- + +# cloudflare_ip_ranges + +Use this data source to get the [IP ranges][1] of CloudFlare edge nodes. + +## Example Usage + +```hcl +data "cloudflare_ip_ranges" "cloudflare" {} + +resource "google_compute_firewall" "allow_cloudflare_ingress" { + name = "from_cloudflare" + network = "default" + + ingress { + ports = "443" + protocol = "tcp" + source_ranges = ["${data.cloudflare_ip_ranges.cloudflare.cidr_blocks}"] + } +} +``` + +## Attributes Reference + +- `cidr_blocks` - The lexically ordered list of all CIDR blocks. + +- `ipv4_cidr_blocks` - The lexically ordered list of only the IPv4 CIDR blocks. + +- `ipv6_cidr_blocks` - The lexically ordered list of only the IPv6 CIDR blocks. + +[1]: https://www.cloudflare.com/ips/