package ip_utils import ( "fmt" "github.com/aws/aws-lambda-go/events" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/handler_utils" "net" "os" "strings" ) // ==================================================================================================== // region Payfast // ==================================================================================================== // GetPayFastIPs returns a slice of string containing the current IP addresses // associated with host names published by PayFast. func getPayFastIPs() []string { hostnames := []string{ "www.payfast.co.za", "w1w.payfast.co.za", "w2w.payfast.co.za", "sandbox.payfast.co.za", "api.payfast.co.za", "ips.payfast.co.za", } ips := []string{} for _, v := range hostnames { addresses, err := net.LookupIP(v) if err != nil { // Swallow and continue. continue } for _, a := range addresses { ips = append(ips, a.String()) } } return ips } func VerifyPayfastSourceIP(sourceIP string) bool { ips := getPayFastIPs() for _, v := range ips { if v == sourceIP { return true } } // If we got here, it means the lookup failed, so let's check the IP ranges as defined by Payfast // Define the list of IP ranges as CIDR blocks - this is obtained from Payfast's documentation ipRanges := []string{ "197.97.145.144/28", "41.74.179.192/27", "102.216.36.0/28", "102.216.36.128/28", "144.126.193.139/32", } // Now verify if isValidIP(sourceIP, ipRanges) { return true } return false } // endregion Payfast // ==================================================================================================== // region Bob Pay // ==================================================================================================== const ( bobPayDevIP = "13.245.84.126" bobPaySandboxIP = "13.246.115.225" bobPayProdIP = "13.246.100.25" ) func VerifyBobPaySourceIP(sourceIP string) bool { env := os.Getenv("ENVIRONMENT") switch env { case "dev": return sourceIP == bobPayDevIP case "stage": return sourceIP == bobPaySandboxIP case "prod": return sourceIP == bobPayProdIP } return false } // endregion Bob Pay // ==================================================================================================== // region Cloudflare // ==================================================================================================== func VerifyCloudflareSourceIP(sourceIP string) bool { ipRanges := []string{ "173.245.48.0/20", "103.21.244.0/22", "103.22.200.0/22", "103.31.4.0/22", "141.101.64.0/18", "108.162.192.0/18", "190.93.240.0/20", "188.114.96.0/20", "197.234.240.0/22", "198.41.128.0/17", "162.158.0.0/15", "104.16.0.0/13", "104.24.0.0/14", "172.64.0.0/13", "131.0.72.0/22", } // Now verify if isValidIP(sourceIP, ipRanges) { return true } return false } // endregion Cloudflare // ==================================================================================================== // region Helpers // ==================================================================================================== func isValidIP(ipStr string, ipRanges []string) bool { ip := net.ParseIP(ipStr) if ip == nil { return false } for _, cidr := range ipRanges { _, subnet, err := net.ParseCIDR(cidr) if err != nil { fmt.Println("Error parsing CIDR:", err) continue } if subnet.Contains(ip) { return true } } return false } func ValidateIPAddress(ipAddress string) (cleanedIPAddress string, err error) { ipAddress = strings.ToLower(strings.TrimSpace(ipAddress)) ip := net.ParseIP(ipAddress) if ip == nil { return "", errors.Error("invalid IP address") } return ipAddress, nil } func GetRequestSourceIP(proxyRequest *events.APIGatewayProxyRequest, websocketRequest *events.APIGatewayWebsocketProxyRequest) string { var requestSourceIP string if proxyRequest != nil { requestSourceIP = proxyRequest.RequestContext.Identity.SourceIP // Cloudflare uses this header to pass the real IP forwardedForHeader := handler_utils.FindHeaderValue(proxyRequest.Headers, "x-forwarded-for") if forwardedForHeader != "" && VerifyCloudflareSourceIP(requestSourceIP) { forwardedForHeaderIPs := strings.Split(forwardedForHeader, ",") if len(forwardedForHeaderIPs) > 1 { // Use the second-to-last IP – the last IP will be the Cloudflare proxy's IP headerSourceIP := strings.TrimSpace(forwardedForHeaderIPs[len(forwardedForHeaderIPs)-2]) return headerSourceIP } } } else if websocketRequest != nil { requestSourceIP = websocketRequest.RequestContext.Identity.SourceIP } return requestSourceIP } // endregion Helpers