Skip to content

Commit

Permalink
Merge pull request #671 from muety/665-trusted-proxy-ip-ranges
Browse files Browse the repository at this point in the history
feat: support ip ranges for trusted proxy header auth (resolve #665)
  • Loading branch information
poettig authored Aug 25, 2024
2 parents 27bfad1 + 5b5076d commit e39c9ec
Show file tree
Hide file tree
Showing 5 changed files with 6,468 additions and 6,427 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ argument) or via environment variables. Here is an overview of all options.
| `security.expose_metrics` /<br> `WAKAPI_EXPOSE_METRICS` | `false` | Whether to expose Prometheus metrics under `/api/metrics` |
| `security.trusted_header_auth` /<br> `WAKAPI_TRUSTED_HEADER_AUTH` | `false` | Whether to enable trusted header authentication for reverse proxies (see [#534](https://github.com/muety/wakapi/issues/534)). **Use with caution!** |
| `security.trusted_header_auth_key` /<br> `WAKAPI_TRUSTED_HEADER_AUTH_KEY` | `Remote-User` | Header field for trusted header authentication. **Caution:** proxy must be configured to strip this header from client requests! |
| `security.trust_reverse_proxy_ips` /<br> `WAKAPI_TRUST_REVERSE_PROXY_IPS` | - | Comma-separated list IPv4 or IPv6 addresses of reverse proxies to trust to handle authentication. |
| `security.trust_reverse_proxy_ips` /<br> `WAKAPI_TRUST_REVERSE_PROXY_IPS` | - | Comma-separated list of IPv4 or IPv6 addresses or CIDRs of reverse proxies to trust to handle authentication (e.g. `172.17.0.1`, `192.168.0.0/24`, `[::1]`). |
| `security.signup_max_rate` /<br> `WAKAPI_SIGNUP_MAX_RATE` | `5/1h` | Rate limiting config for signup endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
| `security.login_max_rate` /<br> `WAKAPI_LOGIN_MAX_RATE` | `10/1m` | Rate limiting config for login endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
| `security.password_reset_max_rate` /<br> `WAKAPI_PASSWORD_RESET_MAX_RATE` | `5/1h` | Rate limiting config for password reset endpoint in format `<max_req>/<multiplier><unit>`, where `unit` is one of `s`, `m` or `h`. |
Expand Down
56 changes: 36 additions & 20 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,18 @@ type securityConfig struct {
EnableProxy bool `yaml:"enable_proxy" default:"false" env:"WAKAPI_ENABLE_PROXY"` // only intended for production instance at wakapi.dev
DisableFrontpage bool `yaml:"disable_frontpage" default:"false" env:"WAKAPI_DISABLE_FRONTPAGE"`
// this is actually a pepper (https://en.wikipedia.org/wiki/Pepper_(cryptography))
PasswordSalt string `yaml:"password_salt" default:"" env:"WAKAPI_PASSWORD_SALT"`
InsecureCookies bool `yaml:"insecure_cookies" default:"false" env:"WAKAPI_INSECURE_COOKIES"`
CookieMaxAgeSec int `yaml:"cookie_max_age" default:"172800" env:"WAKAPI_COOKIE_MAX_AGE"`
TrustedHeaderAuth bool `yaml:"trusted_header_auth" default:"false" env:"WAKAPI_TRUSTED_HEADER_AUTH"`
TrustedHeaderAuthKey string `yaml:"trusted_header_auth_key" default:"Remote-User" env:"WAKAPI_TRUSTED_HEADER_AUTH_KEY"`
TrustReverseProxyIps string `yaml:"trust_reverse_proxy_ips" default:"" env:"WAKAPI_TRUST_REVERSE_PROXY_IPS"` // comma-separated list of trusted reverse proxy ips
SignupMaxRate string `yaml:"signup_max_rate" default:"5/1h" env:"WAKAPI_SIGNUP_MAX_RATE"`
LoginMaxRate string `yaml:"login_max_rate" default:"10/1m" env:"WAKAPI_LOGIN_MAX_RATE"`
PasswordResetMaxRate string `yaml:"password_reset_max_rate" default:"5/1h" env:"WAKAPI_PASSWORD_RESET_MAX_RATE"`
SecureCookie *securecookie.SecureCookie `yaml:"-"`
SessionKey []byte `yaml:"-"`
trustReverseProxyIpParsed []net.IP
PasswordSalt string `yaml:"password_salt" default:"" env:"WAKAPI_PASSWORD_SALT"`
InsecureCookies bool `yaml:"insecure_cookies" default:"false" env:"WAKAPI_INSECURE_COOKIES"`
CookieMaxAgeSec int `yaml:"cookie_max_age" default:"172800" env:"WAKAPI_COOKIE_MAX_AGE"`
TrustedHeaderAuth bool `yaml:"trusted_header_auth" default:"false" env:"WAKAPI_TRUSTED_HEADER_AUTH"`
TrustedHeaderAuthKey string `yaml:"trusted_header_auth_key" default:"Remote-User" env:"WAKAPI_TRUSTED_HEADER_AUTH_KEY"`
TrustReverseProxyIps string `yaml:"trust_reverse_proxy_ips" default:"" env:"WAKAPI_TRUST_REVERSE_PROXY_IPS"` // comma-separated list of trusted reverse proxy ips
SignupMaxRate string `yaml:"signup_max_rate" default:"5/1h" env:"WAKAPI_SIGNUP_MAX_RATE"`
LoginMaxRate string `yaml:"login_max_rate" default:"10/1m" env:"WAKAPI_LOGIN_MAX_RATE"`
PasswordResetMaxRate string `yaml:"password_reset_max_rate" default:"5/1h" env:"WAKAPI_PASSWORD_RESET_MAX_RATE"`
SecureCookie *securecookie.SecureCookie `yaml:"-"`
SessionKey []byte `yaml:"-"`
trustReverseProxyIpsParsed []net.IPNet
}

type dbConfig struct {
Expand Down Expand Up @@ -331,18 +331,34 @@ func (c *appConfig) HeartbeatsMaxAge() time.Duration {
}

func (c *securityConfig) ParseTrustReverseProxyIPs() {
c.trustReverseProxyIpParsed = make([]net.IP, 0)
c.trustReverseProxyIpsParsed = make([]net.IPNet, 0)

for _, ip := range strings.Split(c.TrustReverseProxyIps, ",") {
if parsedIp := net.ParseIP(strings.TrimSpace(ip)); parsedIp == nil {
slog.Warn("failed to parse reverse proxy ip")
} else {
c.trustReverseProxyIpParsed = append(c.trustReverseProxyIpParsed, parsedIp)
// try parse as address range
_, parsedIpNet, err := net.ParseCIDR(ip)
if err == nil {
c.trustReverseProxyIpsParsed = append(c.trustReverseProxyIpsParsed, *parsedIpNet)
continue
}

// try parse as single ip
parsedIp := net.ParseIP(strings.TrimSpace(ip))
if parsedIp != nil {
ipBits := net.IPv4len * 8
if parsedIp.To4() == nil {
ipBits = net.IPv6len * 8
}
ipNet := net.IPNet{IP: parsedIp, Mask: net.CIDRMask(ipBits, ipBits)}
c.trustReverseProxyIpsParsed = append(c.trustReverseProxyIpsParsed, ipNet)
continue
}

slog.Warn("failed to parse reverse proxy ip ranges")
}
}

func (c *securityConfig) TrustReverseProxyIPs() []net.IP {
return c.trustReverseProxyIpParsed
func (c *securityConfig) TrustReverseProxyIPs() []net.IPNet {
return c.trustReverseProxyIpsParsed
}

func (c *securityConfig) GetSignupMaxRate() (int, time.Duration) {
Expand Down Expand Up @@ -528,7 +544,7 @@ func Load(configFlag string, version string) *Config {
if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil {
Log().Fatal("invalid duration set for heartbeat_max_age")
}
if config.Security.TrustedHeaderAuth && len(config.Security.trustReverseProxyIpParsed) == 0 {
if config.Security.TrustedHeaderAuth && len(config.Security.trustReverseProxyIpsParsed) == 0 {
config.Security.TrustedHeaderAuth = false
}
if d, err := time.Parse(config.App.DateFormat, config.App.DateFormat); err != nil || !d.Equal(time.Date(2006, time.January, 2, 0, 0, 0, 0, d.Location())) {
Expand Down
Loading

0 comments on commit e39c9ec

Please sign in to comment.