Skip to content

Commit e9347e8

Browse files
authored
providers/proxy: drop headers with underscores (#17650)
drop any headers with underscores that we set in the remote system Signed-off-by: Jens Langhammer <jens@goauthentik.io>
1 parent 92de2c3 commit e9347e8

File tree

2 files changed

+194
-38
lines changed

2 files changed

+194
-38
lines changed

internal/outpost/proxyv2/application/mode_common.go

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,62 +14,83 @@ import (
1414
"goauthentik.io/internal/outpost/proxyv2/types"
1515
)
1616

17-
// Attempt to set basic auth based on user's attributes
18-
func (a *Application) setAuthorizationHeader(headers http.Header, c *types.Claims) {
19-
if !*a.proxyConfig.BasicAuthEnabled {
20-
return
21-
}
22-
userAttributes := c.Proxy.UserAttributes
23-
var ok bool
24-
var password string
25-
if password, ok = userAttributes[*a.proxyConfig.BasicAuthPasswordAttribute].(string); !ok {
26-
password = ""
27-
}
28-
// Check if we should use email or a custom attribute as username
29-
var username string
30-
if username, ok = userAttributes[*a.proxyConfig.BasicAuthUserAttribute].(string); !ok {
31-
username = c.Email
17+
func (a *Application) addHeaders(headers http.Header, c *types.Claims) {
18+
nh := a.getHeaders(c)
19+
for key, val := range nh {
20+
headers.Set(key, val)
3221
}
33-
if username == "" && password == "" {
34-
return
22+
a.removeDuplicateUnderscoreHeader(headers)
23+
}
24+
25+
func (a *Application) removeDuplicateUnderscoreHeader(h http.Header) {
26+
for key := range h {
27+
ush := strings.ReplaceAll(key, "_", "-")
28+
if _, ok := h[ush]; !ok {
29+
h.Del(key)
30+
}
3531
}
36-
authVal := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
37-
a.log.WithField("username", username).Trace("setting http basic auth")
38-
headers.Set("Authorization", fmt.Sprintf("Basic %s", authVal))
3932
}
4033

41-
func (a *Application) addHeaders(headers http.Header, c *types.Claims) {
34+
func (a *Application) getHeaders(c *types.Claims) map[string]string {
35+
headers := map[string]string{}
4236
// https://docs.goauthentik.io/add-secure-apps/providers/proxy
43-
headers.Set("X-authentik-username", c.PreferredUsername)
44-
headers.Set("X-authentik-groups", strings.Join(c.Groups, "|"))
45-
headers.Set("X-authentik-entitlements", strings.Join(c.Entitlements, "|"))
46-
headers.Set("X-authentik-email", c.Email)
47-
headers.Set("X-authentik-name", c.Name)
48-
headers.Set("X-authentik-uid", c.Sub)
49-
headers.Set("X-authentik-jwt", c.RawToken)
37+
headers["X-authentik-username"] = c.PreferredUsername
38+
headers["X-authentik-groups"] = strings.Join(c.Groups, "|")
39+
headers["X-authentik-entitlements"] = strings.Join(c.Entitlements, "|")
40+
headers["X-authentik-email"] = c.Email
41+
headers["X-authentik-name"] = c.Name
42+
headers["X-authentik-uid"] = c.Sub
43+
headers["X-authentik-jwt"] = c.RawToken
5044

5145
// System headers
52-
headers.Set("X-authentik-meta-jwks", a.endpoint.JwksUri)
53-
headers.Set("X-authentik-meta-outpost", a.outpostName)
54-
headers.Set("X-authentik-meta-provider", a.proxyConfig.Name)
55-
headers.Set("X-authentik-meta-app", a.proxyConfig.AssignedApplicationSlug)
56-
headers.Set("X-authentik-meta-version", constants.UserAgentOutpost())
46+
headers["X-authentik-meta-jwks"] = a.endpoint.JwksUri
47+
headers["X-authentik-meta-outpost"] = a.outpostName
48+
headers["X-authentik-meta-provider"] = a.proxyConfig.Name
49+
headers["X-authentik-meta-app"] = a.proxyConfig.AssignedApplicationSlug
50+
headers["X-authentik-meta-version"] = constants.UserAgentOutpost()
5751

5852
if c.Proxy == nil {
59-
return
53+
return headers
54+
}
55+
if authz := a.setAuthorizationHeader(c); authz != "" {
56+
headers["Authorization"] = authz
6057
}
61-
userAttributes := c.Proxy.UserAttributes
62-
a.setAuthorizationHeader(headers, c)
6358
// Check if user has additional headers set that we should sent
59+
userAttributes := c.Proxy.UserAttributes
6460
if additionalHeaders, ok := userAttributes["additionalHeaders"]; ok {
6561
a.log.WithField("headers", additionalHeaders).Trace("setting additional headers")
6662
if additionalHeaders == nil {
67-
return
63+
return headers
6864
}
6965
for key, value := range additionalHeaders.(map[string]interface{}) {
70-
headers.Set(key, toString(value))
66+
headers[key] = toString(value)
7167
}
7268
}
69+
return headers
70+
}
71+
72+
// Attempt to set basic auth based on user's attributes
73+
func (a *Application) setAuthorizationHeader(c *types.Claims) string {
74+
if !*a.proxyConfig.BasicAuthEnabled {
75+
return ""
76+
}
77+
userAttributes := c.Proxy.UserAttributes
78+
var ok bool
79+
var username string
80+
var password string
81+
if password, ok = userAttributes[*a.proxyConfig.BasicAuthPasswordAttribute].(string); !ok {
82+
password = ""
83+
}
84+
// Check if we should use email or a custom attribute as username
85+
if username, ok = userAttributes[*a.proxyConfig.BasicAuthUserAttribute].(string); !ok {
86+
username = c.Email
87+
}
88+
if password == "" {
89+
return ""
90+
}
91+
authVal := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
92+
a.log.WithField("username", username).Trace("setting http basic auth")
93+
return fmt.Sprintf("Basic %s", authVal)
7394
}
7495

7596
// getTraefikForwardUrl See https://doc.traefik.io/traefik/middlewares/forwardauth/

internal/outpost/proxyv2/application/mode_common_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package application
22

33
import (
4+
"net/http"
45
"net/url"
56
"regexp"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
910
"goauthentik.io/api/v3"
11+
"goauthentik.io/internal/constants"
12+
"goauthentik.io/internal/outpost/proxyv2/types"
1013
)
1114

1215
func urlMustParse(u string) *url.URL {
@@ -48,3 +51,135 @@ func TestIsAllowlisted_Proxy_Domain(t *testing.T) {
4851
assert.Equal(t, false, a.IsAllowlisted(urlMustParse("https://health.domain.tld/")))
4952
assert.Equal(t, true, a.IsAllowlisted(urlMustParse("https://health.domain.tld/ping/qq")))
5053
}
54+
55+
func TestAdHeaders_Standard(t *testing.T) {
56+
a := newTestApplication()
57+
h := http.Header{}
58+
a.addHeaders(h, &types.Claims{
59+
PreferredUsername: "foo",
60+
Groups: []string{"foo", "bar"},
61+
Entitlements: []string{"bar", "quox"},
62+
Email: "bar@authentik.company",
63+
Name: "foo",
64+
Sub: "bar",
65+
RawToken: "baz",
66+
})
67+
assert.Equal(t, http.Header{
68+
"X-Authentik-Email": []string{"bar@authentik.company"},
69+
"X-Authentik-Entitlements": []string{"bar|quox"},
70+
"X-Authentik-Groups": []string{"foo|bar"},
71+
"X-Authentik-Jwt": []string{"baz"},
72+
"X-Authentik-Meta-App": []string{""},
73+
"X-Authentik-Meta-Jwks": []string{""},
74+
"X-Authentik-Meta-Outpost": []string{""},
75+
"X-Authentik-Meta-Provider": []string{a.proxyConfig.Name},
76+
"X-Authentik-Meta-Version": []string{constants.UserAgentOutpost()},
77+
"X-Authentik-Name": []string{"foo"},
78+
"X-Authentik-Uid": []string{"bar"},
79+
"X-Authentik-Username": []string{"foo"},
80+
}, h)
81+
}
82+
83+
func TestAdHeaders_BasicAuth(t *testing.T) {
84+
a := newTestApplication()
85+
a.proxyConfig.BasicAuthEnabled = api.PtrBool(true)
86+
a.proxyConfig.BasicAuthUserAttribute = api.PtrString("user")
87+
a.proxyConfig.BasicAuthPasswordAttribute = api.PtrString("pass")
88+
h := http.Header{}
89+
a.addHeaders(h, &types.Claims{
90+
PreferredUsername: "foo",
91+
Groups: []string{"foo", "bar"},
92+
Entitlements: []string{"bar", "quox"},
93+
Email: "bar@authentik.company",
94+
Name: "foo",
95+
Sub: "bar",
96+
RawToken: "baz",
97+
Proxy: &types.ProxyClaims{
98+
UserAttributes: map[string]any{
99+
"user": "foo",
100+
"pass": "baz",
101+
},
102+
},
103+
})
104+
assert.Equal(t, http.Header{
105+
"Authorization": []string{"Basic Zm9vOmJheg=="},
106+
"X-Authentik-Email": []string{"bar@authentik.company"},
107+
"X-Authentik-Entitlements": []string{"bar|quox"},
108+
"X-Authentik-Groups": []string{"foo|bar"},
109+
"X-Authentik-Jwt": []string{"baz"},
110+
"X-Authentik-Meta-App": []string{""},
111+
"X-Authentik-Meta-Jwks": []string{""},
112+
"X-Authentik-Meta-Outpost": []string{""},
113+
"X-Authentik-Meta-Provider": []string{a.proxyConfig.Name},
114+
"X-Authentik-Meta-Version": []string{constants.UserAgentOutpost()},
115+
"X-Authentik-Name": []string{"foo"},
116+
"X-Authentik-Uid": []string{"bar"},
117+
"X-Authentik-Username": []string{"foo"},
118+
}, h)
119+
}
120+
121+
func TestAdHeaders_Extra(t *testing.T) {
122+
a := newTestApplication()
123+
h := http.Header{}
124+
a.addHeaders(h, &types.Claims{
125+
PreferredUsername: "foo",
126+
Groups: []string{"foo", "bar"},
127+
Entitlements: []string{"bar", "quox"},
128+
Email: "bar@authentik.company",
129+
Name: "foo",
130+
Sub: "bar",
131+
RawToken: "baz",
132+
Proxy: &types.ProxyClaims{
133+
UserAttributes: map[string]any{
134+
"additionalHeaders": map[string]any{
135+
"foo": "bar",
136+
},
137+
},
138+
},
139+
})
140+
assert.Equal(t, http.Header{
141+
"Foo": []string{"bar"},
142+
"X-Authentik-Email": []string{"bar@authentik.company"},
143+
"X-Authentik-Entitlements": []string{"bar|quox"},
144+
"X-Authentik-Groups": []string{"foo|bar"},
145+
"X-Authentik-Jwt": []string{"baz"},
146+
"X-Authentik-Meta-App": []string{""},
147+
"X-Authentik-Meta-Jwks": []string{""},
148+
"X-Authentik-Meta-Outpost": []string{""},
149+
"X-Authentik-Meta-Provider": []string{a.proxyConfig.Name},
150+
"X-Authentik-Meta-Version": []string{constants.UserAgentOutpost()},
151+
"X-Authentik-Name": []string{"foo"},
152+
"X-Authentik-Uid": []string{"bar"},
153+
"X-Authentik-Username": []string{"foo"},
154+
}, h)
155+
}
156+
157+
func TestAdHeaders_UnderscoreInitial(t *testing.T) {
158+
a := newTestApplication()
159+
h := http.Header{}
160+
h.Set("X_AUTHENTIK_USERNAME", "another user")
161+
h.Set("X-Authentik_username", "another user")
162+
a.addHeaders(h, &types.Claims{
163+
PreferredUsername: "foo",
164+
Groups: []string{"foo", "bar"},
165+
Entitlements: []string{"bar", "quox"},
166+
Email: "bar@authentik.company",
167+
Name: "foo",
168+
Sub: "bar",
169+
RawToken: "baz",
170+
})
171+
assert.Equal(t, http.Header{
172+
"X-Authentik-Email": []string{"bar@authentik.company"},
173+
"X-Authentik-Entitlements": []string{"bar|quox"},
174+
"X-Authentik-Groups": []string{"foo|bar"},
175+
"X-Authentik-Jwt": []string{"baz"},
176+
"X-Authentik-Meta-App": []string{""},
177+
"X-Authentik-Meta-Jwks": []string{""},
178+
"X-Authentik-Meta-Outpost": []string{""},
179+
"X-Authentik-Meta-Provider": []string{a.proxyConfig.Name},
180+
"X-Authentik-Meta-Version": []string{constants.UserAgentOutpost()},
181+
"X-Authentik-Name": []string{"foo"},
182+
"X-Authentik-Uid": []string{"bar"},
183+
"X-Authentik-Username": []string{"foo"},
184+
}, h)
185+
}

0 commit comments

Comments
 (0)