1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-06-15 00:15:00 +02:00

Integrate new header injectors with OAuth2 Proxy

This commit is contained in:
Joel Speed
2020-07-29 20:10:14 +01:00
parent d26c65ba8d
commit 8059a812cd
7 changed files with 485 additions and 551 deletions

View File

@ -495,6 +495,8 @@ func TestBasicAuthPassword(t *testing.T) {
t.Fatal(err)
}
}))
basicAuthPassword := "This is a secure password"
opts := baseTestOptions()
opts.UpstreamServers = options.Upstreams{
{
@ -505,11 +507,22 @@ func TestBasicAuthPassword(t *testing.T) {
}
opts.Cookie.Secure = false
opts.PassBasicAuth = true
opts.SetBasicAuth = true
opts.PassUserHeaders = true
opts.PreferEmailToUser = true
opts.BasicAuthPassword = "This is a secure password"
opts.InjectRequestHeaders = []options.Header{
{
Name: "Authorization",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte(basicAuthPassword))),
},
},
},
},
},
}
err := validation.Validate(opts)
assert.NoError(t, err)
@ -524,148 +537,44 @@ func TestBasicAuthPassword(t *testing.T) {
t.Fatal(err)
}
// Save the required session
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:", strings.NewReader(""))
req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
proxy.ServeHTTP(rw, req)
if rw.Code >= 400 {
t.Fatalf("expected 3xx got %d", rw.Code)
}
cookie := rw.Header().Values("Set-Cookie")[1]
cookieName := proxy.CookieName
var value string
keyPrefix := cookieName + "="
for _, field := range strings.Split(cookie, "; ") {
value = strings.TrimPrefix(field, keyPrefix)
if value != field {
break
} else {
value = ""
}
}
req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
req.AddCookie(&http.Cookie{
Name: cookieName,
Value: value,
Path: "/",
Expires: time.Now().Add(time.Duration(24)),
HttpOnly: true,
req, _ := http.NewRequest("GET", "/", nil)
err = proxy.sessionStore.Save(rw, req, &sessions.SessionState{
Email: emailAddress,
})
req.AddCookie(proxy.MakeCSRFCookie(req, "nonce", proxy.CookieExpire, time.Now()))
assert.NoError(t, err)
// Extract the cookie value to inject into the test request
cookie := rw.Header().Values("Set-Cookie")[0]
req, _ = http.NewRequest("GET", "/", nil)
req.Header.Set("Cookie", cookie)
rw = httptest.NewRecorder()
proxy.ServeHTTP(rw, req)
// The username in the basic auth credentials is expected to be equal to the email address from the
// auth response, so we use the same variable here.
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+basicAuthPassword))
assert.Equal(t, expectedHeader, rw.Body.String())
providerServer.Close()
}
func TestBasicAuthWithEmail(t *testing.T) {
opts := baseTestOptions()
opts.PassBasicAuth = true
opts.PassUserHeaders = false
opts.PreferEmailToUser = false
opts.BasicAuthPassword = "This is a secure password"
err := validation.Validate(opts)
assert.NoError(t, err)
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
// The username in the basic auth credentials is expected to be equal to the email address from the
expectedEmailHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(emailAddress+":"+opts.BasicAuthPassword))
expectedUserHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(userName+":"+opts.BasicAuthPassword))
created := time.Now()
session := &sessions.SessionState{
User: userName,
Email: emailAddress,
AccessToken: "oauth_token",
CreatedAt: &created,
}
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, expectedUserHeader, req.Header["Authorization"][0])
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
}
opts.PreferEmailToUser = true
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, expectedEmailHeader, req.Header["Authorization"][0])
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
}
}
func TestPassUserHeadersWithEmail(t *testing.T) {
opts := baseTestOptions()
err := validation.Validate(opts)
assert.NoError(t, err)
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
created := time.Now()
session := &sessions.SessionState{
User: userName,
Email: emailAddress,
AccessToken: "oauth_token",
CreatedAt: &created,
}
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, userName, req.Header["X-Forwarded-User"][0])
}
opts.PreferEmailToUser = true
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase1", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, emailAddress, req.Header["X-Forwarded-User"][0])
}
}
func TestPassGroupsHeadersWithGroups(t *testing.T) {
opts := baseTestOptions()
opts.InjectRequestHeaders = []options.Header{
{
Name: "X-Forwarded-Groups",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "groups",
},
},
},
},
}
err := validation.Validate(opts)
assert.NoError(t, err)
@ -681,161 +590,27 @@ func TestPassGroupsHeadersWithGroups(t *testing.T) {
AccessToken: "oauth_token",
CreatedAt: &created,
}
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
}
}
func TestStripAuthHeaders(t *testing.T) {
testCases := map[string]struct {
SkipAuthStripHeaders bool
PassBasicAuth bool
PassUserHeaders bool
PassAccessToken bool
PassAuthorization bool
StrippedHeaders map[string]bool
}{
"Default options": {
SkipAuthStripHeaders: true,
PassBasicAuth: true,
PassUserHeaders: true,
PassAccessToken: false,
PassAuthorization: false,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": false,
"Authorization": true,
},
},
"Pass access token": {
SkipAuthStripHeaders: true,
PassBasicAuth: true,
PassUserHeaders: true,
PassAccessToken: true,
PassAuthorization: false,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": true,
"Authorization": true,
},
},
"Nothing setting Authorization": {
SkipAuthStripHeaders: true,
PassBasicAuth: false,
PassUserHeaders: true,
PassAccessToken: true,
PassAuthorization: false,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": true,
"Authorization": false,
},
},
"Only Authorization header modified": {
SkipAuthStripHeaders: true,
PassBasicAuth: false,
PassUserHeaders: false,
PassAccessToken: false,
PassAuthorization: true,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false,
"Authorization": true,
},
},
"Don't strip any headers (default options)": {
SkipAuthStripHeaders: false,
PassBasicAuth: true,
PassUserHeaders: true,
PassAccessToken: false,
PassAuthorization: false,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false,
"Authorization": false,
},
},
"Don't strip any headers (custom options)": {
SkipAuthStripHeaders: false,
PassBasicAuth: true,
PassUserHeaders: true,
PassAccessToken: true,
PassAuthorization: false,
StrippedHeaders: map[string]bool{
"X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false,
"Authorization": false,
},
},
}
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
assert.NoError(t, err)
initialHeaders := map[string]string{
"X-Forwarded-User": "9fcab5c9b889a557",
"X-Forwarded-Email": "john.doe@example.com",
"X-Forwarded-Groups": "a,b,c",
"X-Forwarded-Preferred-Username": "john.doe",
"X-Forwarded-Access-Token": "AccessToken",
"Authorization": "bearer IDToken",
}
// Save the required session
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
err = proxy.sessionStore.Save(rw, req, session)
assert.NoError(t, err)
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
opts := baseTestOptions()
opts.SkipAuthStripHeaders = tc.SkipAuthStripHeaders
opts.PassBasicAuth = tc.PassBasicAuth
opts.PassUserHeaders = tc.PassUserHeaders
opts.PassAccessToken = tc.PassAccessToken
opts.PassAuthorization = tc.PassAuthorization
err := validation.Validate(opts)
assert.NoError(t, err)
// Extract the cookie value to inject into the test request
cookie := rw.Header().Values("Set-Cookie")[0]
req, _ := http.NewRequest("GET", fmt.Sprintf("%s/testCase", opts.ProxyPrefix), nil)
for header, val := range initialHeaders {
req.Header.Set(header, val)
}
req, _ = http.NewRequest("GET", "/", nil)
req.Header.Set("Cookie", cookie)
rw = httptest.NewRecorder()
proxy.ServeHTTP(rw, req)
proxy, err := NewOAuthProxy(opts, func(_ string) bool { return true })
assert.NoError(t, err)
if proxy.skipAuthStripHeaders {
proxy.stripAuthHeaders(req)
}
for header, stripped := range tc.StrippedHeaders {
if stripped {
assert.Equal(t, req.Header.Get(header), "")
} else {
assert.Equal(t, req.Header.Get(header), initialHeaders[header])
}
}
})
}
assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
}
type PassAccessTokenTest struct {
@ -884,7 +659,21 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
}
patt.opts.Cookie.Secure = false
patt.opts.PassAccessToken = opts.PassAccessToken
if opts.PassAccessToken {
patt.opts.InjectRequestHeaders = []options.Header{
{
Name: "X-Forwarded-Access-Token",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "access_token",
},
},
},
},
}
}
err := validation.Validate(patt.opts)
if err != nil {
return nil, err
@ -1442,7 +1231,48 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = baseTestOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.InjectResponseHeaders = []options.Header{
{
Name: "X-Auth-Request-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Auth-Request-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
{
Name: "X-Auth-Request-Groups",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "groups",
},
},
},
},
{
Name: "X-Forwarded-Preferred-Username",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "preferred_username",
},
},
},
},
}
pcTest.opts.AllowedGroups = []string{"oauth_groups"}
err := validation.Validate(pcTest.opts)
assert.NoError(t, err)
@ -1480,8 +1310,62 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = baseTestOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.SetBasicAuth = true
pcTest.opts.InjectResponseHeaders = []options.Header{
{
Name: "X-Auth-Request-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Auth-Request-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
{
Name: "X-Auth-Request-Groups",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "groups",
},
},
},
},
{
Name: "X-Forwarded-Preferred-Username",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "preferred_username",
},
},
},
},
{
Name: "Authorization",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
},
},
},
},
},
}
err := validation.Validate(pcTest.opts)
assert.NoError(t, err)
@ -1511,7 +1395,7 @@ func TestAuthOnlyEndpointSetBasicAuthTrueRequestHeaders(t *testing.T) {
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
assert.Equal(t, "oauth_user", pcTest.rw.Header().Values("X-Auth-Request-User")[0])
assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Values("X-Auth-Request-Email")[0])
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:"+pcTest.opts.BasicAuthPassword))
expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte("oauth_user:This is a secure password"))
assert.Equal(t, expectedHeader, pcTest.rw.Header().Values("Authorization")[0])
}
@ -1519,8 +1403,48 @@ func TestAuthOnlyEndpointSetBasicAuthFalseRequestHeaders(t *testing.T) {
var pcTest ProcessCookieTest
pcTest.opts = baseTestOptions()
pcTest.opts.SetXAuthRequest = true
pcTest.opts.SetBasicAuth = false
pcTest.opts.InjectResponseHeaders = []options.Header{
{
Name: "X-Auth-Request-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Auth-Request-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
{
Name: "X-Auth-Request-Groups",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "groups",
},
},
},
},
{
Name: "X-Forwarded-Preferred-Username",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "preferred_username",
},
},
},
},
}
err := validation.Validate(pcTest.opts)
assert.NoError(t, err)
@ -1985,9 +1909,74 @@ func TestGetJwtSession(t *testing.T) {
&oidc.Config{ClientID: "https://test.myapp.com", SkipExpiryCheck: true})
test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
opts.PassAuthorization = true
opts.SetAuthorization = true
opts.SetXAuthRequest = true
opts.InjectRequestHeaders = []options.Header{
{
Name: "Authorization",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "id_token",
Prefix: "Bearer ",
},
},
},
},
{
Name: "X-Forwarded-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Forwarded-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
}
opts.InjectResponseHeaders = []options.Header{
{
Name: "Authorization",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "id_token",
Prefix: "Bearer ",
},
},
},
},
{
Name: "X-Auth-Request-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Auth-Request-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
}
opts.SkipJwtBearerTokens = true
opts.SetJWTBearerVerifiers(append(opts.GetJWTBearerVerifiers(), verifier))
})
@ -2004,15 +1993,6 @@ func TestGetJwtSession(t *testing.T) {
"Authorization": {authHeader},
}
// Bearer
expires := time.Unix(1912151821, 0)
session, err := test.proxy.getAuthenticatedSession(test.rw, test.req)
assert.NoError(t, err)
assert.Equal(t, session.User, "1234567890")
assert.Equal(t, session.Email, "john@example.com")
assert.Equal(t, session.ExpiresOn, &expires)
assert.Equal(t, session.IDToken, goodJwt)
test.proxy.ServeHTTP(test.rw, test.req)
if test.rw.Code >= 400 {
t.Fatalf("expected 3xx got %d", test.rw.Code)
@ -2140,6 +2120,43 @@ func baseTestOptions() *options.Options {
opts.ClientID = clientID
opts.ClientSecret = clientSecret
opts.EmailDomains = []string{"*"}
// Default injected headers for legacy configuration
opts.InjectRequestHeaders = []options.Header{
{
Name: "Authorization",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
BasicAuthPassword: &options.SecretSource{
Value: []byte(base64.StdEncoding.EncodeToString([]byte("This is a secure password"))),
},
},
},
},
},
{
Name: "X-Forwarded-User",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "user",
},
},
},
},
{
Name: "X-Forwarded-Email",
Values: []options.HeaderValue{
{
ClaimSource: &options.ClaimSource{
Claim: "email",
},
},
},
},
}
return opts
}