Skip to content

Commit 05cfb12

Browse files
forwardauth: Skip copying missing response headers (#6608)
1 parent 00f948c commit 05cfb12

File tree

3 files changed

+267
-34
lines changed

3 files changed

+267
-34
lines changed

caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest

+97-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
app.example.com {
22
forward_auth authelia:9091 {
3-
uri /api/verify?rd=https://authelia.example.com
3+
uri /api/authz/forward-auth
44
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
55
}
66

@@ -39,6 +39,13 @@ app.example.com {
3939
]
4040
},
4141
"routes": [
42+
{
43+
"handle": [
44+
{
45+
"handler": "vars"
46+
}
47+
]
48+
},
4249
{
4350
"handle": [
4451
{
@@ -47,19 +54,104 @@ app.example.com {
4754
"set": {
4855
"Remote-Email": [
4956
"{http.reverse_proxy.header.Remote-Email}"
50-
],
57+
]
58+
}
59+
}
60+
}
61+
],
62+
"match": [
63+
{
64+
"not": [
65+
{
66+
"vars": {
67+
"{http.reverse_proxy.header.Remote-Email}": [
68+
""
69+
]
70+
}
71+
}
72+
]
73+
}
74+
]
75+
},
76+
{
77+
"handle": [
78+
{
79+
"handler": "headers",
80+
"request": {
81+
"set": {
5182
"Remote-Groups": [
5283
"{http.reverse_proxy.header.Remote-Groups}"
53-
],
84+
]
85+
}
86+
}
87+
}
88+
],
89+
"match": [
90+
{
91+
"not": [
92+
{
93+
"vars": {
94+
"{http.reverse_proxy.header.Remote-Groups}": [
95+
""
96+
]
97+
}
98+
}
99+
]
100+
}
101+
]
102+
},
103+
{
104+
"handle": [
105+
{
106+
"handler": "headers",
107+
"request": {
108+
"set": {
54109
"Remote-Name": [
55110
"{http.reverse_proxy.header.Remote-Name}"
56-
],
111+
]
112+
}
113+
}
114+
}
115+
],
116+
"match": [
117+
{
118+
"not": [
119+
{
120+
"vars": {
121+
"{http.reverse_proxy.header.Remote-Name}": [
122+
""
123+
]
124+
}
125+
}
126+
]
127+
}
128+
]
129+
},
130+
{
131+
"handle": [
132+
{
133+
"handler": "headers",
134+
"request": {
135+
"set": {
57136
"Remote-User": [
58137
"{http.reverse_proxy.header.Remote-User}"
59138
]
60139
}
61140
}
62141
}
142+
],
143+
"match": [
144+
{
145+
"not": [
146+
{
147+
"vars": {
148+
"{http.reverse_proxy.header.Remote-User}": [
149+
""
150+
]
151+
}
152+
}
153+
]
154+
}
63155
]
64156
}
65157
]
@@ -80,7 +172,7 @@ app.example.com {
80172
},
81173
"rewrite": {
82174
"method": "GET",
83-
"uri": "/api/verify?rd=https://authelia.example.com"
175+
"uri": "/api/authz/forward-auth"
84176
},
85177
"upstreams": [
86178
{

caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest

+124-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ forward_auth localhost:9000 {
2828
]
2929
},
3030
"routes": [
31+
{
32+
"handle": [
33+
{
34+
"handler": "vars"
35+
}
36+
]
37+
},
3138
{
3239
"handle": [
3340
{
@@ -36,22 +43,131 @@ forward_auth localhost:9000 {
3643
"set": {
3744
"1": [
3845
"{http.reverse_proxy.header.A}"
39-
],
40-
"3": [
41-
"{http.reverse_proxy.header.C}"
42-
],
43-
"5": [
44-
"{http.reverse_proxy.header.E}"
45-
],
46+
]
47+
}
48+
}
49+
}
50+
],
51+
"match": [
52+
{
53+
"not": [
54+
{
55+
"vars": {
56+
"{http.reverse_proxy.header.A}": [
57+
""
58+
]
59+
}
60+
}
61+
]
62+
}
63+
]
64+
},
65+
{
66+
"handle": [
67+
{
68+
"handler": "headers",
69+
"request": {
70+
"set": {
4671
"B": [
4772
"{http.reverse_proxy.header.B}"
48-
],
73+
]
74+
}
75+
}
76+
}
77+
],
78+
"match": [
79+
{
80+
"not": [
81+
{
82+
"vars": {
83+
"{http.reverse_proxy.header.B}": [
84+
""
85+
]
86+
}
87+
}
88+
]
89+
}
90+
]
91+
},
92+
{
93+
"handle": [
94+
{
95+
"handler": "headers",
96+
"request": {
97+
"set": {
98+
"3": [
99+
"{http.reverse_proxy.header.C}"
100+
]
101+
}
102+
}
103+
}
104+
],
105+
"match": [
106+
{
107+
"not": [
108+
{
109+
"vars": {
110+
"{http.reverse_proxy.header.C}": [
111+
""
112+
]
113+
}
114+
}
115+
]
116+
}
117+
]
118+
},
119+
{
120+
"handle": [
121+
{
122+
"handler": "headers",
123+
"request": {
124+
"set": {
49125
"D": [
50126
"{http.reverse_proxy.header.D}"
51127
]
52128
}
53129
}
54130
}
131+
],
132+
"match": [
133+
{
134+
"not": [
135+
{
136+
"vars": {
137+
"{http.reverse_proxy.header.D}": [
138+
""
139+
]
140+
}
141+
}
142+
]
143+
}
144+
]
145+
},
146+
{
147+
"handle": [
148+
{
149+
"handler": "headers",
150+
"request": {
151+
"set": {
152+
"5": [
153+
"{http.reverse_proxy.header.E}"
154+
]
155+
}
156+
}
157+
}
158+
],
159+
"match": [
160+
{
161+
"not": [
162+
{
163+
"vars": {
164+
"{http.reverse_proxy.header.E}": [
165+
""
166+
]
167+
}
168+
}
169+
]
170+
}
55171
]
56172
}
57173
]

modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go

+46-21
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package forwardauth
1717
import (
1818
"encoding/json"
1919
"net/http"
20+
"sort"
2021
"strings"
2122

2223
"github.com/caddyserver/caddy/v2"
@@ -170,42 +171,66 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
170171
return nil, dispenser.Errf("the 'uri' subdirective is required")
171172
}
172173

173-
// set up handler for good responses; when a response
174-
// has 2xx status, then we will copy some headers from
175-
// the response onto the original request, and allow
176-
// handling to continue down the middleware chain,
177-
// by _not_ executing a terminal handler.
174+
// Set up handler for good responses; when a response has 2xx status,
175+
// then we will copy some headers from the response onto the original
176+
// request, and allow handling to continue down the middleware chain,
177+
// by _not_ executing a terminal handler. We must have at least one
178+
// route in the response handler, even if it's no-op, so that the
179+
// response handling logic in reverse_proxy doesn't skip this entry.
178180
goodResponseHandler := caddyhttp.ResponseHandler{
179181
Match: &caddyhttp.ResponseMatcher{
180182
StatusCode: []int{2},
181183
},
182-
Routes: []caddyhttp.Route{},
183-
}
184-
185-
handler := &headers.Handler{
186-
Request: &headers.HeaderOps{
187-
Set: http.Header{},
184+
Routes: []caddyhttp.Route{
185+
{
186+
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
187+
&caddyhttp.VarsMiddleware{},
188+
"handler",
189+
"vars",
190+
nil,
191+
)},
192+
},
188193
},
189194
}
190195

191-
// the list of headers to copy may be empty, but that's okay; we
192-
// need at least one handler in the routes for the response handling
193-
// logic in reverse_proxy to not skip this entry as empty.
194-
for from, to := range headersToCopy {
195-
handler.Request.Set.Set(to, "{http.reverse_proxy.header."+http.CanonicalHeaderKey(from)+"}")
196+
// Sort the headers so that the order in the JSON output is deterministic.
197+
sortedHeadersToCopy := make([]string, 0, len(headersToCopy))
198+
for k := range headersToCopy {
199+
sortedHeadersToCopy = append(sortedHeadersToCopy, k)
196200
}
201+
sort.Strings(sortedHeadersToCopy)
197202

198-
goodResponseHandler.Routes = append(
199-
goodResponseHandler.Routes,
200-
caddyhttp.Route{
203+
// Set up handlers to copy headers from the auth response onto the
204+
// original request. We use vars matchers to test that the placeholder
205+
// values aren't empty, because the header handler would not replace
206+
// placeholders which have no value.
207+
copyHeaderRoutes := []caddyhttp.Route{}
208+
for _, from := range sortedHeadersToCopy {
209+
to := http.CanonicalHeaderKey(headersToCopy[from])
210+
placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from)
211+
handler := &headers.Handler{
212+
Request: &headers.HeaderOps{
213+
Set: http.Header{
214+
to: []string{"{" + placeholderName + "}"},
215+
},
216+
},
217+
}
218+
copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{
219+
MatcherSetsRaw: []caddy.ModuleMap{{
220+
"not": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{
221+
"vars": h.JSON(caddyhttp.VarsMatcher{"{" + placeholderName + "}": []string{""}}),
222+
}}}),
223+
}},
201224
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
202225
handler,
203226
"handler",
204227
"headers",
205228
nil,
206229
)},
207-
},
208-
)
230+
})
231+
}
232+
233+
goodResponseHandler.Routes = append(goodResponseHandler.Routes, copyHeaderRoutes...)
209234

210235
// note that when a response has any other status than 2xx, then we
211236
// use the reverse proxy's default behaviour of copying the response

0 commit comments

Comments
 (0)