feat(cli): add aws check to ping p2p diagnostics (#14450)

This commit is contained in:
Ethan
2024-08-29 21:41:12 +10:00
committed by GitHub
parent e65eb0321c
commit 4b5c45d6df
7 changed files with 426 additions and 41 deletions
+114
View File
@@ -0,0 +1,114 @@
package cliutil
import (
"context"
"encoding/json"
"io"
"net/http"
"net/netip"
"time"
"golang.org/x/xerrors"
)
const AWSIPRangesURL = "https://ip-ranges.amazonaws.com/ip-ranges.json"
type awsIPv4Prefix struct {
Prefix string `json:"ip_prefix"`
Region string `json:"region"`
Service string `json:"service"`
NetworkBorderGroup string `json:"network_border_group"`
}
type awsIPv6Prefix struct {
Prefix string `json:"ipv6_prefix"`
Region string `json:"region"`
Service string `json:"service"`
NetworkBorderGroup string `json:"network_border_group"`
}
type AWSIPRanges struct {
V4 []netip.Prefix
V6 []netip.Prefix
}
type awsIPRangesResponse struct {
SyncToken string `json:"syncToken"`
CreateDate string `json:"createDate"`
IPV4Prefixes []awsIPv4Prefix `json:"prefixes"`
IPV6Prefixes []awsIPv6Prefix `json:"ipv6_prefixes"`
}
func FetchAWSIPRanges(ctx context.Context, url string) (*AWSIPRanges, error) {
client := &http.Client{}
reqCtx, reqCancel := context.WithTimeout(ctx, 5*time.Second)
defer reqCancel()
req, _ := http.NewRequestWithContext(reqCtx, http.MethodGet, url, nil)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, _ := io.ReadAll(resp.Body)
return nil, xerrors.Errorf("unexpected status code %d: %s", resp.StatusCode, b)
}
var body awsIPRangesResponse
err = json.NewDecoder(resp.Body).Decode(&body)
if err != nil {
return nil, xerrors.Errorf("json decode: %w", err)
}
out := &AWSIPRanges{
V4: make([]netip.Prefix, 0, len(body.IPV4Prefixes)),
V6: make([]netip.Prefix, 0, len(body.IPV6Prefixes)),
}
for _, p := range body.IPV4Prefixes {
prefix, err := netip.ParsePrefix(p.Prefix)
if err != nil {
return nil, xerrors.Errorf("parse ip prefix: %w", err)
}
if prefix.Addr().Is6() {
return nil, xerrors.Errorf("ipv4 prefix contains ipv6 address: %s", p.Prefix)
}
out.V4 = append(out.V4, prefix)
}
for _, p := range body.IPV6Prefixes {
prefix, err := netip.ParsePrefix(p.Prefix)
if err != nil {
return nil, xerrors.Errorf("parse ip prefix: %w", err)
}
if prefix.Addr().Is4() {
return nil, xerrors.Errorf("ipv6 prefix contains ipv4 address: %s", p.Prefix)
}
out.V6 = append(out.V6, prefix)
}
return out, nil
}
// CheckIP checks if the given IP address is an AWS IP.
func (r *AWSIPRanges) CheckIP(ip netip.Addr) bool {
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() || ip.IsPrivate() {
return false
}
if ip.Is4() {
for _, p := range r.V4 {
if p.Contains(ip) {
return true
}
}
} else {
for _, p := range r.V6 {
if p.Contains(ip) {
return true
}
}
}
return false
}
+95
View File
@@ -0,0 +1,95 @@
package cliutil
import (
"context"
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/testutil"
)
func TestIPV4Check(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, awsIPRangesResponse{
IPV4Prefixes: []awsIPv4Prefix{
{
Prefix: "3.24.0.0/14",
},
{
Prefix: "15.230.15.29/32",
},
{
Prefix: "47.128.82.100/31",
},
},
IPV6Prefixes: []awsIPv6Prefix{
{
Prefix: "2600:9000:5206::/48",
},
{
Prefix: "2406:da70:8800::/40",
},
{
Prefix: "2600:1f68:5000::/40",
},
},
})
}))
ctx := testutil.Context(t, testutil.WaitShort)
ranges, err := FetchAWSIPRanges(ctx, srv.URL)
require.NoError(t, err)
t.Run("Private/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("192.168.0.1")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("AWS/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("3.25.61.113")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.True(t, isAws)
})
t.Run("NonAWS/IPV4", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("159.196.123.40")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("Private/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("::1")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
t.Run("AWS/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("2600:9000:5206:0001:0000:0000:0000:0001")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.True(t, isAws)
})
t.Run("NonAWS/IPV6", func(t *testing.T) {
t.Parallel()
ip, err := netip.ParseAddr("2403:5807:885f:0:a544:49d4:58f8:aedf")
require.NoError(t, err)
isAws := ranges.CheckIP(ip)
require.False(t, isAws)
})
}