From a4d89036ec102509fbb0d393f77cc90af6d083c8 Mon Sep 17 00:00:00 2001 From: H1net Date: Mon, 23 Mar 2026 09:22:36 +0000 Subject: [PATCH] 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 * 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 Signed-off-by: h1net * doc: fix changelog entry for #3374 Signed-off-by: Jan Larwig * doc: add trusted ip a section to versioned docs as well Signed-off-by: Jan Larwig --------- Signed-off-by: h1net Signed-off-by: Ben Newbery Signed-off-by: Jan Larwig Co-authored-by: Jan Larwig --- CHANGELOG.md | 2 ++ docs/docs/configuration/systemd_socket.md | 8 ++++++ .../configuration/systemd_socket.md | 8 ++++++ oauthproxy.go | 4 +-- oauthproxy_test.go | 26 +++++++++++++++++++ pkg/ip/realclientip.go | 8 +++++- pkg/ip/realclientip_test.go | 4 +++ 7 files changed, 56 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d740cb9..aabc1385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/docs/configuration/systemd_socket.md b/docs/docs/configuration/systemd_socket.md index 642e6f3f..490dabbf 100644 --- a/docs/docs/configuration/systemd_socket.md +++ b/docs/docs/configuration/systemd_socket.md @@ -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). diff --git a/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md b/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md index 642e6f3f..490dabbf 100644 --- a/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md +++ b/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md @@ -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). diff --git a/oauthproxy.go b/oauthproxy.go index dc3f8b57..f260acc6 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -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 } diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 38cdccab..e06f50e9 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -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", diff --git a/pkg/ip/realclientip.go b/pkg/ip/realclientip.go index 8bfc7ea3..db8f2595 100644 --- a/pkg/ip/realclientip.go +++ b/pkg/ip/realclientip.go @@ -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() } diff --git a/pkg/ip/realclientip_test.go b/pkg/ip/realclientip_test.go index c56e0170..3cbca114 100644 --- a/pkg/ip/realclientip_test.go +++ b/pkg/ip/realclientip_test.go @@ -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"},