Files
findos/internal/clouddetect/detect.go
Dev 4d51c65060
Some checks failed
Go CI / test (push) Has been cancelled
up
2025-09-13 12:30:01 +03:00

129 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package clouddetect
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"time"
)
// Result represents the outcome of a cloud proxy detection.
type Result struct {
IsProxy bool `json:"is_proxy"`
Provider string `json:"provider,omitempty"`
Details string `json:"details,omitempty"`
}
// knownCIDRs maps providers to a slice of CIDR strings that represent their IP ranges.
// In a production implementation these would be loaded from data files; for this prototype
// a small representative subset is hardcoded.
var knownCIDRs = map[string][]string{
"Cloudflare": {
"103.21.244.0/22",
"104.16.0.0/12",
"2606:4700::/32",
},
"AWS CloudFront": {
"13.32.0.0/15",
"13.54.0.0/15",
"2600:9000::/28",
},
"Azure Front Door": {
"40.112.0.0/13",
"52.239.0.0/16",
"2603:1030::/32",
},
}
// Detect attempts to determine whether the target is behind a known cloud proxy.
// It performs DNS resolution, checks the resolved IPs against known CIDR ranges,
// and inspects HTTP response headers for provider signatures.
func Detect(target string) (*Result, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Resolve IP addresses for the target.
// Strip any port component if present (e.g., "example.com:443").
host := target
if strings.Contains(target, ":") {
if h, _, err := net.SplitHostPort(target); err == nil {
host = h
}
}
var ips []net.IP
ips, err := net.DefaultResolver.LookupIP(ctx, "ip", host)
if err != nil {
// DNS lookup failed; continue without IPs.
ips = nil
}
// Duplicate error handling removed the previous block already handles DNS failures.
// Prepare CIDR parsers.
parsedCIDRs := make(map[string][]*net.IPNet)
for prov, cidrs := range knownCIDRs {
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
continue // skip malformed entries
}
parsedCIDRs[prov] = append(parsedCIDRs[prov], ipnet)
}
}
// Load additional CIDR ranges from external file (cloud_ranges.txt).
if extCIDRs, err := LoadCIDRs("internal/clouddetect/cloud_ranges.txt"); err == nil {
for prov, nets := range extCIDRs {
parsedCIDRs[prov] = append(parsedCIDRs[prov], nets...)
}
}
// Check each resolved IP against the CIDR tables.
for _, ip := range ips {
for prov, nets := range parsedCIDRs {
for _, net := range nets {
if net.Contains(ip) {
return &Result{
IsProxy: true,
Provider: prov,
Details: fmt.Sprintf("IP %s matches %s range", ip, prov),
}, nil
}
}
}
}
// If no CIDR match, attempt an HTTP HEAD request to look for provider headers.
// Use the original target (host) for the request to avoid port issues.
url := fmt.Sprintf("http://%s", host)
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
if err == nil {
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Do(req)
if err == nil {
defer resp.Body.Close()
serverHeader := resp.Header.Get("Server")
viaHeader := resp.Header.Get("Via")
// Simple heuristics based on common header values.
if strings.Contains(strings.ToLower(serverHeader), "cloudflare") ||
strings.Contains(strings.ToLower(viaHeader), "cloudflare") {
return &Result{IsProxy: true, Provider: "Cloudflare", Details: "Detected via HTTP headers"}, nil
}
if strings.Contains(strings.ToLower(serverHeader), "amazon") ||
strings.Contains(strings.ToLower(viaHeader), "aws") {
return &Result{IsProxy: true, Provider: "AWS CloudFront", Details: "Detected via HTTP headers"}, nil
}
if strings.Contains(strings.ToLower(serverHeader), "azure") ||
strings.Contains(strings.ToLower(viaHeader), "azure") {
return &Result{IsProxy: true, Provider: "Azure Front Door", Details: "Detected via HTTP headers"}, nil
}
}
}
// No proxy indicators found.
return &Result{IsProxy: false}, nil
}