release: v7.15.5 (#3553)

* update to release version v7.15.5

* Merge commit from fork

Signed-off-by: Jan Larwig <jan@larwig.com>
Co-authored-by: blakeroberts-wk <blake.roberts@workiva.com>

* Merge commit from fork

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

* Merge commit from fork

* fix: validate trusted IP proxy headers

Respect trusted proxy boundaries before using real-client-IP headers for authentication bypass decisions and safely traverse X-Forwarded-For chains.

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

* fix: trusted-ip header bypass

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

* docs: add changelog entry

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

---------

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

* docs: changelog for v7.15.5

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

* docs: update order of owners for prow

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

* ci: make the linter happy again

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

---------

Signed-off-by: Jan Larwig <jan@larwig.com>
Signed-off-by: Jan Larwig <jan.larwig@digits.schwarz>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jan Larwig <jan@larwig.com>
Co-authored-by: blakeroberts-wk <blake.roberts@workiva.com>
Co-authored-by: Jan Larwig <jan.larwig@digits.schwarz>
This commit is contained in:
github-actions[bot]
2026-10-01 11:01:25 +02:00
committed by GitHub
co-authored by blakeroberts-wk Jan Larwig Jan Larwig
parent e05f04ef08
commit bea3f04bf8
27 changed files with 1360 additions and 108 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ func validateAllowlists(o *options.Options) []string {
msgs = append(msgs, validateTrustedIPs(o)...)
if len(o.TrustedIPs) > 0 && o.ReverseProxy {
_, err := fmt.Fprintln(os.Stderr, "WARNING: mixing --trusted-ip with --reverse-proxy is a potential security vulnerability. An attacker can inject a trusted IP into an X-Real-IP or X-Forwarded-For header if they aren't properly protected outside of oauth2-proxy")
_, err := fmt.Fprintln(os.Stderr, "WARNING: --trusted-ip bypasses authentication. When using it with --reverse-proxy, configure --trusted-proxy-ip and ensure the proxy overwrites the selected real-client-IP header (or safely appends X-Forwarded-For).")
if err != nil {
panic(err)
}
+10 -1
View File
@@ -8,6 +8,7 @@ import (
"net/url"
"strings"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
@@ -89,7 +90,15 @@ func Validate(o *options.Options) error {
// Allow the logger to get client IPs
logger.SetGetClientFunc(func(r *http.Request) string {
return ip.GetClientString(o.GetRealClientIPParser(), r, false)
parser := o.GetRealClientIPParser()
var trustedProxies *ip.NetSet
scope := middlewareapi.GetRequestScope(r)
if scope != nil && scope.CanTrustForwardedHeaders(r) {
trustedProxies = scope.TrustedProxies
} else {
parser = nil
}
return ip.GetClientString(parser, r, trustedProxies, false)
})
}
+67
View File
@@ -1,16 +1,24 @@
package validation
import (
"bytes"
"crypto"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"time"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
"github.com/onsi/ginkgo/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
@@ -224,6 +232,65 @@ func TestRealClientIPHeader(t *testing.T) {
assert.Nil(t, o.GetRealClientIPParser())
}
func TestRealClientIPLogging(t *testing.T) {
t.Cleanup(func() {
logger.SetOutput(ginkgo.GinkgoWriter)
logger.SetAuthTemplate(logger.DefaultAuthLoggingFormat)
logger.SetReqTemplate(logger.DefaultRequestLoggingFormat)
logger.SetGetClientFunc(func(r *http.Request) string { return r.RemoteAddr })
})
tests := []struct {
name string
remoteAddr string
headerValues []string
reverseProxy bool
nilParser bool
expected string
}{
{"Untrusted peer", "198.51.100.23:1234", []string{"10.0.0.5"}, true, false, "198.51.100.23"},
{"Appending trusted proxy", "192.0.2.10:1234", []string{"10.0.0.5, 198.51.100.23"}, true, false, "198.51.100.23"},
{"Repeated fields and multiple proxies", "192.0.2.10:1234", []string{"10.0.0.5", "198.51.100.23, 192.0.2.20", "192.0.2.21"}, true, false, "198.51.100.23"},
{"Legitimate client", "192.0.2.10:1234", []string{"10.0.0.5, 192.0.2.20"}, true, false, "10.0.0.5"},
{"Missing header", "192.0.2.10:1234", nil, true, false, "192.0.2.10"},
{"Malformed relevant hop", "192.0.2.10:1234", []string{"10.0.0.5, invalid, 192.0.2.20"}, true, false, "192.0.2.10"},
{"Nil parser", "192.0.2.10:1234", []string{"10.0.0.5"}, true, true, "192.0.2.10"},
{"Reverse proxy disabled in scope", "192.0.2.10:1234", []string{"10.0.0.5"}, false, false, "192.0.2.10"},
{"IPv6", "[2001:db8:1::10]:1234", []string{"10.0.0.5", " [2001:db8:2::23]:443 , 2001:db8:1::20"}, true, false, "2001:db8:2::23"},
{"Unix socket", "@", []string{"10.0.0.5, 198.51.100.23"}, true, false, "198.51.100.23"},
{"Entire chain trusted", "192.0.2.10:1234", []string{"192.0.2.30, 192.0.2.20"}, true, false, "192.0.2.30"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
o := testOptions()
o.ReverseProxy = true
o.RealClientIPHeader = "X-Forwarded-For"
o.TrustedProxyIPs = []string{"192.0.2.0/24", "2001:db8:1::/48"}
o.Logging.AuthFormat = "{{.Client}}"
o.Logging.RequestFormat = "{{.Client}}"
require.NoError(t, Validate(o))
if test.nilParser {
o.SetRealClientIPParser(nil)
}
trustedProxies, err := ip.ParseNetSet(o.TrustedProxyIPs)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.RemoteAddr = test.remoteAddr
for _, value := range test.headerValues {
req.Header.Add("X-Forwarded-For", value)
}
req = middlewareapi.AddRequestScope(req, &middlewareapi.RequestScope{
ReverseProxy: test.reverseProxy,
TrustedProxies: trustedProxies,
})
var output bytes.Buffer
logger.SetOutput(&output)
logger.PrintAuthf("", req, logger.AuthFailure, "denied")
logger.PrintReq("", "", req, *req.URL, time.Now(), http.StatusForbidden, 0)
assert.Equal(t, test.expected+"\n"+test.expected+"\n", output.String())
})
}
}
func TestProviderCAFilesError(t *testing.T) {
file, err := os.CreateTemp("", "absent.*.crt")
assert.NoError(t, err)