fix: handle Unix socket RemoteAddr in IP resolution (#3374)

* fix: handle Unix socket RemoteAddr in IP resolution

When oauth2-proxy listens on a Unix socket, Go sets RemoteAddr to "@"
instead of the usual "host:port" format. This caused net.SplitHostPort
to fail on every request, flooding logs with errors:

  Error obtaining real IP for trusted IP list: unable to get ip and
  port from http.RemoteAddr (@)

Fix by handling the "@" RemoteAddr at the source in getRemoteIP,
returning nil without error since Unix sockets have no meaningful
client IP. Also simplify the isTrustedIP guard and add a nil check
in GetClientString to prevent calling String() on nil net.IP.

Fixes #3373

Signed-off-by: h1net <ben@freshdevs.com>

* docs: add changelog entry and Unix socket trusted IPs documentation

Add changelog entry for #3374. Document that trusted IPs cannot match
against RemoteAddr for Unix socket listeners since Go sets it to "@",
and that IP-based trust still works via X-Forwarded-For with reverse-proxy.

Signed-off-by: Ben Newbery <ben.newbery@gmail.com>
Signed-off-by: h1net <ben@freshdevs.com>

* doc: fix changelog entry for #3374

Signed-off-by: Jan Larwig <jan@larwig.com>

* doc: add trusted ip a section to versioned docs as well

Signed-off-by: Jan Larwig <jan@larwig.com>

---------

Signed-off-by: h1net <ben@freshdevs.com>
Signed-off-by: Ben Newbery <ben.newbery@gmail.com>
Signed-off-by: Jan Larwig <jan@larwig.com>
Co-authored-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
H1net 2026-03-23 09:22:36 +00:00 committed by GitHub
parent 9f09d54ba4
commit a4d89036ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 56 additions and 4 deletions

View File

@ -14,6 +14,8 @@
# V7.15.0
- [#3382](https://github.com/oauth2-proxy/oauth2-proxy/pull/3382) chore(deps): update gomod and golangci/golangci-lint to v2.11.4 (@tuunit)
- [#3374](https://github.com/oauth2-proxy/oauth2-proxy/pull/3374) fix: handle Unix socket RemoteAddr in IP resolution (@H1net)
## Release Highlights

View File

@ -40,4 +40,12 @@ the listener it created onto the process, starting with file descriptor 3.
--client-secret=...
```
## Trusted IPs
When listening on a Unix socket, Go sets `http.Request.RemoteAddr` to `"@"` instead of the usual `"host:port"` format. This means there is no client IP available from the connection itself.
As a result, `--trusted-ip` entries cannot match against the direct connection address for Unix socket listeners. Requests arriving over a Unix socket will never be considered "trusted" based on their `RemoteAddr`. IP-based trust decisions will still work if a trusted reverse proxy sets `X-Forwarded-For` or `X-Real-IP` headers and `--reverse-proxy=true` is configured.
## TLS
Currently TLS is not supported (but it's doable).

View File

@ -40,4 +40,12 @@ the listener it created onto the process, starting with file descriptor 3.
--client-secret=...
```
## Trusted IPs
When listening on a Unix socket, Go sets `http.Request.RemoteAddr` to `"@"` instead of the usual `"host:port"` format. This means there is no client IP available from the connection itself.
As a result, `--trusted-ip` entries cannot match against the direct connection address for Unix socket listeners. Requests arriving over a Unix socket will never be considered "trusted" based on their `RemoteAddr`. IP-based trust decisions will still work if a trusted reverse proxy sets `X-Forwarded-For` or `X-Real-IP` headers and `--reverse-proxy=true` is configured.
## TLS
Currently TLS is not supported (but it's doable).

View File

@ -613,9 +613,7 @@ func (p *OAuthProxy) isAPIPath(req *http.Request) bool {
// isTrustedIP is used to check if a request comes from a trusted client IP address.
func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
// RemoteAddr @ means unix socket
// https://github.com/golang/go/blob/0fa53e41f122b1661d0678a6d36d71b7b5ad031d/src/syscall/syscall_linux.go#L506-L511
if p.trustedIPs == nil && req.RemoteAddr != "@" {
if p.trustedIPs == nil {
return false
}

View File

@ -2150,6 +2150,32 @@ func TestTrustedIPs(t *testing.T) {
}(),
expectTrusted: false,
},
// Check Unix socket with no trusted IPs configured does not error.
{
name: "UnixSocketWithoutTrustedIPs",
trustedIPs: nil,
reverseProxy: false,
realClientIPHeader: "X-Real-IP",
req: func() *http.Request {
req, _ := http.NewRequest("GET", "/", nil)
req.RemoteAddr = "@"
return req
}(),
expectTrusted: false,
},
// Check Unix socket with trusted IPs configured returns false (no IP to match).
{
name: "UnixSocketWithTrustedIPs",
trustedIPs: []string{"127.0.0.1"},
reverseProxy: false,
realClientIPHeader: "X-Real-IP",
req: func() *http.Request {
req, _ := http.NewRequest("GET", "/", nil)
req.RemoteAddr = "@"
return req
}(),
expectTrusted: false,
},
// Check using req.RemoteAddr (Options.ReverseProxy == false).
{
name: "WithRemoteAddr",

View File

@ -73,6 +73,12 @@ func GetClientIP(p ipapi.RealClientIPParser, req *http.Request) (net.IP, error)
// getRemoteIP obtains the IP of the low-level connected network host
func getRemoteIP(req *http.Request) (net.IP, error) {
// Unix domain sockets set RemoteAddr to "@" which has no meaningful IP.
// https://github.com/golang/go/blob/0fa53e41f122b1661d0678a6d36d71b7b5ad031d/src/syscall/syscall_linux.go#L506-L511
if req.RemoteAddr == "@" {
return nil, nil
}
//revive:disable:indent-error-flow
if ipStr, _, err := net.SplitHostPort(req.RemoteAddr); err != nil {
return nil, fmt.Errorf("unable to get ip and port from http.RemoteAddr (%s)", req.RemoteAddr)
@ -94,7 +100,7 @@ func GetClientString(p ipapi.RealClientIPParser, req *http.Request, full bool) (
}
var remoteIPStr string
if remoteIP, err := getRemoteIP(req); err == nil {
if remoteIP, err := getRemoteIP(req); err == nil && remoteIP != nil {
remoteIPStr = remoteIP.String()
}

View File

@ -112,6 +112,8 @@ func TestGetRemoteIP(t *testing.T) {
errString string
expectedIP net.IP
}{
// Unix domain sockets set RemoteAddr to "@"
{"@", "", nil},
{"", "unable to get ip and port from http.RemoteAddr ()", nil},
{"nil", "unable to get ip and port from http.RemoteAddr (nil)", nil},
{"235.28.129.186", "unable to get ip and port from http.RemoteAddr (235.28.129.186)", nil},
@ -155,6 +157,8 @@ func TestGetClientString(t *testing.T) {
}{
// Should fail quietly, only printing warnings to the log
{nil, "", "", "", ""},
// Unix domain socket — no IP available
{nil, "@", "", "", ""},
{p, "127.0.0.1:11950", "", "127.0.0.1", "127.0.0.1"},
{p, "[::1]:28660", "99.103.56.12", "99.103.56.12", "::1 (99.103.56.12)"},
{nil, "10.254.244.165:62750", "", "10.254.244.165", "10.254.244.165"},