mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat(cli): add aws check to ping p2p diagnostics (#14450)
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user