mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-20 04:57:02 +00:00
fix(security): allow loopback hosts for loopback providerURL (#154)
Issue #154: a providerURL that points at a localhost IdP caused an infinite redirect loop. validateHost rejected loopback hosts unconditionally, so buildAuthURL returned an empty string. Derive allowLoopbackHosts from a loopback providerURL at construction time. Discovery validation already used this derivation, so the two validation layers now agree. This adds no new config surface. Cloud metadata hosts (169.254.169.254, metadata.google.internal), link-local, and unspecified addresses stay blocked unconditionally. Non-loopback providers keep identical behaviour. Add issue154_regression_test.go: 8 tests pin the new allowance, the default block, and the security envelope.
This commit is contained in:
+20
-7
@@ -390,8 +390,15 @@ func (t *TraefikOidc) validateHost(host string) error {
|
||||
|
||||
ip := net.ParseIP(hostname)
|
||||
if ip != nil {
|
||||
// Always block loopback, link-local, and multicast addresses
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
// Loopback addresses are blocked by default, but allowed when the
|
||||
// plugin was constructed with a loopback providerURL (local
|
||||
// development) — see allowLoopbackHosts.
|
||||
if ip.IsLoopback() && !t.allowLoopbackHosts {
|
||||
return fmt.Errorf("access to loopback/link-local IP addresses is not allowed: %s", ip.String())
|
||||
}
|
||||
|
||||
// Link-local and multicast addresses are always blocked.
|
||||
if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
return fmt.Errorf("access to loopback/link-local IP addresses is not allowed: %s", ip.String())
|
||||
}
|
||||
|
||||
@@ -405,16 +412,22 @@ func (t *TraefikOidc) validateHost(host string) error {
|
||||
}
|
||||
}
|
||||
|
||||
dangerousHosts := map[string]bool{
|
||||
"localhost": true,
|
||||
"127.0.0.1": true,
|
||||
"::1": true,
|
||||
// Always blocked regardless of allowLoopbackHosts: these have no IP-branch
|
||||
// backstop (net.ParseIP returns nil for hostnames), so bypassing them
|
||||
// would open an SSRF path to the cloud metadata service.
|
||||
alwaysDangerousHosts := map[string]bool{
|
||||
"0.0.0.0": true,
|
||||
"169.254.169.254": true,
|
||||
"metadata.google.internal": true,
|
||||
}
|
||||
|
||||
if dangerousHosts[strings.ToLower(hostname)] {
|
||||
if alwaysDangerousHosts[strings.ToLower(hostname)] {
|
||||
return fmt.Errorf("access to dangerous hostname is not allowed: %s", hostname)
|
||||
}
|
||||
|
||||
// localhost/loopback literals are blocked unless allowLoopbackHosts
|
||||
// permits them (see allowLoopbackHosts doc comment on TraefikOidc).
|
||||
if !t.allowLoopbackHosts && isLoopbackHost(hostname) {
|
||||
return fmt.Errorf("access to dangerous hostname is not allowed: %s", hostname)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user