mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-06-03 17:32:21 +02:00
7f776fe0fe
* Add Warn and Trace loglevels * Move to slog * fixes * missing test file * Fix tests * Add wrapper with trace * fix * fix lint * LINT * LINT * 🍱 Use only 4 level of logs * fix after merge * 🍱 fix lint * 🍱 fix lint + remove trace logger from tests * 🍱 fix tests * 🍱 fix lint * 🍱 try to fix test * 🍱 Fix tests * 🍱 fix README + adjust logs --------- Co-authored-by: maxlerebourg <maxlerebourg@gmail.com>
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
// Package ip implements utility routines to manipulate IP and CIDR.
|
|
// It allows searching an IP on a list, and find if an IP is part of a list of CIDR.
|
|
package ip
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// CHECKER
|
|
|
|
// Checker allows to check that addresses are in a trusted IPs.
|
|
type Checker struct {
|
|
authorizedIPs []*net.IP
|
|
authorizedIPsNet []*net.IPNet
|
|
}
|
|
|
|
// NewChecker builds a new Checker given a list of CIDR-Strings to trusted IPs.
|
|
func NewChecker(log *slog.Logger, trustedIPs []string) (*Checker, error) {
|
|
checker := &Checker{}
|
|
|
|
for _, ipMaskRaw := range trustedIPs {
|
|
ipMask := strings.TrimSpace(ipMaskRaw)
|
|
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
|
|
checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr)
|
|
log.Debug(fmt.Sprintf("IP %v is trusted", ipAddr))
|
|
continue
|
|
}
|
|
|
|
_, ipAddr, err := net.ParseCIDR(ipMask)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing CIDR trusted IPs %s: %w", ipAddr, err)
|
|
}
|
|
checker.authorizedIPsNet = append(checker.authorizedIPsNet, ipAddr)
|
|
log.Debug(fmt.Sprintf("IP network %v is trusted", ipAddr))
|
|
}
|
|
|
|
return checker, nil
|
|
}
|
|
|
|
// Contains checks if provided address is in the trusted IPs.
|
|
func (ip *Checker) Contains(addr string) (bool, error) {
|
|
if len(addr) == 0 {
|
|
return false, errors.New("Contains:noAddress")
|
|
}
|
|
|
|
ipAddr, err := parseIP(addr)
|
|
if err != nil {
|
|
return false, fmt.Errorf("Contains:parseAddress addr:%s %w", addr, err)
|
|
}
|
|
|
|
return ip.ContainsIP(ipAddr), nil
|
|
}
|
|
|
|
// ContainsIP checks if provided address is in the trusted IPs.
|
|
func (ip *Checker) ContainsIP(addr net.IP) bool {
|
|
for _, authorizedIP := range ip.authorizedIPs {
|
|
if authorizedIP.Equal(addr) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
for _, authorizedNet := range ip.authorizedIPsNet {
|
|
if authorizedNet.Contains(addr) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func parseIP(addr string) (net.IP, error) {
|
|
userIP := net.ParseIP(addr)
|
|
if userIP == nil {
|
|
return nil, fmt.Errorf("parseIP:parseAddress %s", addr)
|
|
}
|
|
|
|
return userIP, nil
|
|
}
|
|
|
|
// STRATEGY
|
|
|
|
// PoolStrategy is a strategy based on an IP Checker.
|
|
// It allows to check whether addresses are in a given pool of IPs.
|
|
type PoolStrategy struct {
|
|
Checker *Checker
|
|
}
|
|
|
|
// GetIP checks the list of Forwarded IPs (most recent first) against the
|
|
// Checker pool of IPs. It returns the first IP that is not in the pool, or the
|
|
// empty string otherwise.
|
|
func (s *PoolStrategy) getIP(req *http.Request, customHeader string) string {
|
|
if s.Checker == nil {
|
|
return ""
|
|
}
|
|
|
|
xff := req.Header.Get(customHeader)
|
|
|
|
xffs := strings.Split(xff, ",")
|
|
|
|
for i := len(xffs) - 1; i >= 0; i-- {
|
|
xffTrimmed := strings.TrimSpace(xffs[i])
|
|
if len(xffTrimmed) == 0 {
|
|
continue
|
|
}
|
|
if contain, _ := s.Checker.Contains(xffTrimmed); !contain {
|
|
return xffTrimmed
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// GetRemoteIP It returns the first IP that is not in the pool, or the empty string otherwise.
|
|
func GetRemoteIP(req *http.Request, strategy *PoolStrategy, customHeader string) (string, error) {
|
|
remoteIP := strategy.getIP(req, customHeader)
|
|
if len(remoteIP) != 0 {
|
|
return remoteIP, nil
|
|
}
|
|
remoteIP, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("GetRemoteIP:extractIP: %w", err)
|
|
}
|
|
return remoteIP, nil
|
|
}
|