From 1e565ead289b6a418a56d7842afc0345e6ae3217 Mon Sep 17 00:00:00 2001 From: Benjamin Knofe Date: Mon, 1 Jul 2024 13:00:52 +0200 Subject: [PATCH] add GetStaticIPs function and test case (#121) * add GetStaticIPs function and test case * add fixtures * move to *net.IPNet * add comment * use netip package * remove line --------- Co-authored-by: Daniel Paulus --- checkly.go | 69 +++++++++++++++++++++++++++++++++++++++ checkly_test.go | 57 ++++++++++++++++++++++++++++++++ fixtures/StaticIPs.json | 1 + fixtures/StaticIPv6s.json | 1 + types.go | 11 +++++++ 5 files changed, 139 insertions(+) create mode 100644 fixtures/StaticIPs.json create mode 100644 fixtures/StaticIPv6s.json diff --git a/checkly.go b/checkly.go index 9b4ff5b..2bf51b1 100644 --- a/checkly.go +++ b/checkly.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net/http" "net/http/httputil" + "net/netip" "net/url" "os" "strconv" @@ -1366,6 +1367,74 @@ func (c *client) GetRuntime( return &result, nil } +// Get static IP lists +func (c *client) GetStaticIPs( + ctx context.Context, +) ([]StaticIP, error) { + var IPs []StaticIP + + // getting IPv6 first + status, res, err := c.apiCall( + ctx, + http.MethodGet, + "static-ipv6s-by-region", + nil, + ) + if err != nil { + return nil, err + } + if status != http.StatusOK { + return nil, fmt.Errorf("unexpected response status %d: %q", status, res) + } + + var datav6 map[string]string + err = json.Unmarshal([]byte(res), &datav6) + if err != nil { + return nil, fmt.Errorf("decoding error for data %s: %v", res, err) + } + + for region, ip := range datav6 { + addr, err := netip.ParsePrefix(ip) + if err != nil { + return nil, fmt.Errorf("could not parse CIDR from %s: %v", ip, err) + } + + IPs = append(IPs, StaticIP{Region: region, Address: addr}) + } + + // and then IPv4 + status, res, err = c.apiCall( + ctx, + http.MethodGet, + "static-ips-by-region", + nil, + ) + if err != nil { + return nil, err + } + if status != http.StatusOK { + return nil, fmt.Errorf("unexpected response status %d: %q", status, res) + } + + var datav4 map[string][]string + err = json.Unmarshal([]byte(res), &datav4) + if err != nil { + return nil, fmt.Errorf("decoding error for data %s: %v", res, err) + } + + for region, ips := range datav4 { + for _, ip := range ips { + addr, err := netip.ParseAddr(ip) + if err != nil { + return nil, fmt.Errorf("could not parse CIDR from %s: %v", ip, err) + } + IPs = append(IPs, StaticIP{Region: region, Address: netip.PrefixFrom(addr, 32)}) + } + } + + return IPs, nil +} + // dumpResponse writes the raw response data to the debug output, if set, or // standard error otherwise. func (c *client) dumpResponse(resp *http.Response) { diff --git a/checkly_test.go b/checkly_test.go index efb945e..e0cdcb4 100644 --- a/checkly_test.go +++ b/checkly_test.go @@ -8,8 +8,11 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "net/netip" "os" "path" + "path/filepath" + "reflect" "strconv" "strings" "testing" @@ -1516,3 +1519,57 @@ func TestGetPrivateLocation(t *testing.T) { t.Error(cmp.Diff(testPrivateLocation, *pl, ignorePrivateLocationFields)) } } + +func TestGetStaticIPs(t *testing.T) { + t.Parallel() + + fixtureMap := map[string]string{ + "/v1/static-ips-by-region": "StaticIPs.json", + "/v1/static-ipv6s-by-region": "StaticIPv6s.json", + } + + // we can't use cannedResponseServer here since we need a response on more than one URL + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fileName, ok := fixtureMap[r.URL.Path] + if !ok { + http.Error(w, "URL not found", http.StatusNotFound) + return + } + + filePath := filepath.Join("fixtures", fileName) + fileData, err := ioutil.ReadFile(filePath) + if err != nil { + http.Error(w, "File not found", http.StatusNotFound) + return + } + w.Write(fileData) + })) + defer ts.Close() + + client := checkly.NewClient(ts.URL, "dummy-key", ts.Client(), nil) + gotStaticIPs, err := client.GetStaticIPs(context.Background()) + if err != nil { + t.Error(err) + } + + exampleIPv4 := netip.MustParsePrefix("54.151.146.209/32") + exampleIPv6 := netip.MustParsePrefix("2600:1f18:12ca:3000::/56") + + expected := []checkly.StaticIP{ + {Region: "ap-southeast-1", Address: exampleIPv4}, + {Region: "us-east-1", Address: exampleIPv6}, + } + + for _, exp := range expected { + found := false + for _, ip := range gotStaticIPs { + if reflect.DeepEqual(ip, exp) { + found = true + break + } + } + if !found { + t.Errorf("Expected %+v to be included in %+v, but it was not found", exp, gotStaticIPs) + } + } +} diff --git a/fixtures/StaticIPs.json b/fixtures/StaticIPs.json new file mode 100644 index 0000000..1497759 --- /dev/null +++ b/fixtures/StaticIPs.json @@ -0,0 +1 @@ +{"af-south-1":["13.244.136.126","13.244.236.0","13.245.106.43","13.245.148.249","13.246.130.231","13.247.30.251"],"ap-east-1":["16.162.201.45","16.163.4.182","16.163.237.192","18.163.68.67","18.166.1.159","43.199.11.165"],"ap-northeast-1":["3.114.75.140","13.113.60.181","18.177.47.156","18.177.64.19","18.177.131.104","57.180.166.209"],"ap-northeast-2":["3.34.12.129","3.37.59.136","13.209.34.65","13.209.211.14","15.164.121.38","15.164.200.158","43.202.173.18","43.202.219.81"],"ap-northeast-3":["13.208.82.239","15.152.103.65","15.152.125.107","15.168.5.169","15.168.45.240","15.168.106.208"],"ap-south-1":["3.6.20.243","3.109.106.92","3.109.109.177","3.109.167.65","15.207.219.98","65.0.112.148"],"ap-southeast-1":["13.214.117.85","13.228.244.82","52.74.182.121","52.220.40.195","54.151.146.209","122.248.238.187"],"ap-southeast-2":["13.54.45.181","13.55.213.62","13.211.27.92","13.237.8.239","13.238.60.37","54.252.49.95"],"ap-southeast-3":["43.218.8.168","43.218.19.251","43.218.154.173","43.218.202.200","43.218.237.62","43.218.250.173"],"ca-central-1":["3.98.215.170","15.156.126.181","15.157.99.108","15.157.101.15","52.60.138.202","52.60.240.255"],"eu-central-1":["3.74.107.228","3.78.155.165","3.125.108.148","18.184.0.56","18.193.141.226","18.197.172.39"],"eu-north-1":["13.48.162.54","16.170.38.94","51.20.29.65","51.20.153.4","51.20.158.132","51.21.16.250"],"eu-south-1":["15.161.180.124","18.102.35.213","18.102.39.196","18.102.46.79","18.102.59.137","18.102.92.39"],"eu-west-1":["3.255.54.55","34.242.17.159","34.246.74.139","34.246.204.133","52.212.195.72","54.195.90.61"],"eu-west-2":["13.43.156.224","18.132.86.84","18.134.149.153","18.134.201.213","18.135.179.55","35.176.249.175"],"eu-west-3":["13.36.226.230","13.39.3.231","13.39.232.43","13.39.236.84","15.188.122.137","35.180.153.150"],"me-south-1":["15.184.22.185","15.184.252.133","15.185.92.84","15.185.104.229","15.185.156.205","157.241.93.172"],"sa-east-1":["15.229.56.35","18.229.243.24","18.230.50.229","54.94.0.175","54.207.45.193","54.233.84.10"],"us-east-1":["3.211.217.48","34.230.212.244","44.206.103.147","44.210.168.97","44.221.164.148","44.221.215.176","52.22.78.31","52.71.172.74","52.201.210.118","54.146.26.208","54.161.245.122","54.164.9.242"],"us-east-2":["3.17.106.146","3.134.230.82","3.134.239.96","3.143.128.19","18.188.44.71","18.223.8.66"],"us-west-1":["13.52.93.107","52.8.167.200","54.177.183.174","54.241.196.177"],"us-west-2":["34.209.2.129","34.215.165.113","44.235.44.60","44.237.79.79","44.240.116.205","52.36.214.162","52.42.153.190","54.186.86.230"]} \ No newline at end of file diff --git a/fixtures/StaticIPv6s.json b/fixtures/StaticIPv6s.json new file mode 100644 index 0000000..f6600da --- /dev/null +++ b/fixtures/StaticIPv6s.json @@ -0,0 +1 @@ +{"af-south-1":"2406:da11:e9f:a800::/56","ap-east-1":"2406:da1e:f19:b000::/56","ap-northeast-1":"2406:da14:8e6:8700::/56","ap-northeast-2":"2406:da12:940:c700::/56","ap-northeast-3":"2406:da16:422:8400::/56","ap-south-1":"2406:da1a:ec4:6100::/56","ap-southeast-1":"2406:da18:51c:e500::/56","ap-southeast-2":"2406:da1c:a54:1c00::/56","ap-southeast-3":"2406:da19:772:2100::/56","ca-central-1":"2600:1f11:c05:aa00::/56","eu-central-1":"2a05:d014:ed3:f100::/56","eu-north-1":"2a05:d016:407:6600::/56","eu-south-1":"2a05:d01a:d6a:3100::/56","eu-west-1":"2a05:d018:6a0:af00::/56","eu-west-2":"2a05:d01c:b4b:c00::/56","eu-west-3":"2a05:d012:4ab:2200::/56","me-south-1":"2a05:d01e:364:c000::/56","sa-east-1":"2600:1f1e:d01:6d00::/56","us-east-1":"2600:1f18:12ca:3000::/56","us-east-2":"2600:1f16:1971:eb00::/56","us-west-1":"2600:1f1c:95e:a400::/56","us-west-2":"2600:1f14:3144:5c00::/56"} \ No newline at end of file diff --git a/types.go b/types.go index b618c00..527eec2 100644 --- a/types.go +++ b/types.go @@ -7,6 +7,7 @@ import ( "io" "log" "net/http" + "net/netip" "time" ) @@ -356,6 +357,8 @@ type Client interface { ctx context.Context, ID string, ) (*Runtime, error) + + GetStaticIPs(ctx context.Context) ([]StaticIP, error) } // client represents a Checkly client. If the Debug field is set to an io.Writer @@ -912,6 +915,14 @@ type Runtime struct { Description string `json:"description"` } +// This type is used to describe Checkly's official +// public range of IP addresses checks are executed from +// see https://www.checklyhq.com/docs/monitoring/allowlisting/#ip-range-allowlisting +type StaticIP struct { + Region string + Address netip.Prefix +} + // SetConfig sets config of alert channel based on it's type func (a *AlertChannel) SetConfig(cfg interface{}) { switch v := cfg.(type) {