git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/cloudflare/ips.go (about) 1 package cloudflare 2 3 import ( 4 "context" 5 _ "embed" 6 "encoding/json" 7 "fmt" 8 "net" 9 "net/http" 10 "sync" 11 ) 12 13 //go:embed ips.json 14 var IpsJson []byte 15 16 var cloudflareIps *cloudflareIpsRanges 17 var cloudflareIpsMutex sync.RWMutex 18 19 type cloudflareIpsRanges struct { 20 Ipv4 []net.IPNet 21 Ipv6 []net.IPNet 22 } 23 24 // CustomHostnameOwnershipVerificationHTTP represents a response from the Custom Hostnames endpoints. 25 type CloudflareIpsResult struct { 26 Ipv4Cidrs []string `json:"ipv4_cidrs"` 27 Ipv6Cidrs []string `json:"ipv6_cidrs"` 28 Etag string `json:"etag"` 29 } 30 31 // TODO: return error? 32 func IsCloudflareIP(ipAddress string) (ret bool, err error) { 33 if cloudflareIps == nil { 34 err = loadCloudflareIps() 35 if err != nil { 36 return 37 } 38 } 39 40 ip := net.ParseIP(ipAddress) 41 if ip == nil { 42 err = fmt.Errorf("IP address is not valid: %s", ipAddress) 43 return 44 } 45 46 cloudflareIpsMutex.RLock() 47 defer cloudflareIpsMutex.RUnlock() 48 49 // Is IPv4 50 var networks []net.IPNet 51 if ip.To4() != nil { 52 networks = cloudflareIps.Ipv4 53 } else { 54 networks = cloudflareIps.Ipv6 55 } 56 57 for _, network := range networks { 58 if network.Contains(ip) { 59 ret = true 60 return 61 } 62 } 63 64 return 65 } 66 67 func loadCloudflareIps() (err error) { 68 var cloudflareIpsData CloudflareIpsResult 69 70 err = json.Unmarshal(IpsJson, &cloudflareIpsData) 71 if err != nil { 72 err = fmt.Errorf("cloudflare: Error parsing Cloduflare IPs ranges: %w", err) 73 return 74 } 75 76 ips := cloudflareIpsRanges{ 77 Ipv4: make([]net.IPNet, len(cloudflareIpsData.Ipv4Cidrs)), 78 Ipv6: make([]net.IPNet, len(cloudflareIpsData.Ipv6Cidrs)), 79 } 80 81 for i, cidr := range cloudflareIpsData.Ipv4Cidrs { 82 var network *net.IPNet 83 _, network, err = net.ParseCIDR(cidr) 84 if err != nil { 85 err = fmt.Errorf("cloudflare: CIDR (%s) is not valid: %w", cidr, err) 86 return 87 } 88 ips.Ipv4[i] = *network 89 } 90 91 for i, cidr := range cloudflareIpsData.Ipv6Cidrs { 92 var network *net.IPNet 93 _, network, err = net.ParseCIDR(cidr) 94 if err != nil { 95 err = fmt.Errorf("cloudflare: CIDR (%s) is not valid: %w", cidr, err) 96 return 97 } 98 ips.Ipv6[i] = *network 99 } 100 101 cloudflareIpsMutex.Lock() 102 cloudflareIps = &ips 103 cloudflareIpsMutex.Unlock() 104 return 105 } 106 107 // Fetch the IPs used on the Cloudflare network 108 // Ips are fetched from https://api.cloudflare.com/client/v4/ips and can be formated using | python3 -m json.tool 109 // Changelog: https://www.cloudflare.com/en-gb/ips/ 110 // API Docs: https://developers.cloudflare.com/api/operations/cloudflare-i-ps-cloudflare-ip-details 111 func (client *Client) FetchCloudflareIps(ctx context.Context) (res CloudflareIpsResult, err error) { 112 err = client.request(ctx, requestParams{ 113 Method: http.MethodGet, 114 URL: "/client/v4/ips", 115 }, &res) 116 if err != nil { 117 return 118 } 119 120 return 121 }