Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 12, 07:00 UTC
forwardauth: Skip copying missing response headers (#6608)
Francis Lavoie committed
Nov 4, 2024 at 16:58 UTC
05cfb121ec3f214c0e45206c188f34bad4d4eb8c
3 files changed
+267
-34
caddytest/integration/caddyfile_adapt/forward_auth_authelia.caddyfiletest
+97
-5
@@ -1,6 +1,6 @@
1
app.example.com {
2
forward_auth authelia:9091 {
3
- uri /api/verify?rd=https://authelia.example.com
3
+ uri /api/authz/forward-auth
4
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
5
}
6
@@ -39,6 +39,13 @@ app.example.com {
39
]
40
},
41
"routes": [
42
+ {
43
+ "handle": [
44
+ {
45
+ "handler": "vars"
46
+ }
47
+ ]
48
+ },
49
{
50
"handle": [
51
{
@@ -47,19 +54,104 @@ app.example.com {
54
"set": {
55
"Remote-Email": [
56
"{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": {
82
"Remote-Groups": [
83
"{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": {
109
"Remote-Name": [
110
"{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": {
136
"Remote-User": [
137
"{http.reverse_proxy.header.Remote-User}"
138
]
139
}
140
}
141
}
142
+ ],
143
+ "match": [
144
+ {
145
+ "not": [
146
+ {
147
+ "vars": {
148
+ "{http.reverse_proxy.header.Remote-User}": [
149
+ ""
150
+ ]
151
+ }
152
+ }
153
+ ]
154
+ }
155
]
156
}
157
]
@@ -80,7 +172,7 @@ app.example.com {
172
},
173
"rewrite": {
174
"method": "GET",
83
- "uri": "/api/verify?rd=https://authelia.example.com"
175
+ "uri": "/api/authz/forward-auth"
176
},
177
"upstreams": [
178
{
caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.caddyfiletest
+124
-8
@@ -28,6 +28,13 @@ forward_auth localhost:9000 {
28
]
29
},
30
"routes": [
31
+ {
32
+ "handle": [
33
+ {
34
+ "handler": "vars"
35
+ }
36
+ ]
37
+ },
38
{
39
"handle": [
40
{
@@ -36,22 +43,131 @@ forward_auth localhost:9000 {
43
"set": {
44
"1": [
45
"{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": {
71
"B": [
72
"{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": {
125
"D": [
126
"{http.reverse_proxy.header.D}"
127
]
128
}
129
}
130
}
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
+ }
171
]
172
}
173
]
modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+46
-21
@@ -17,6 +17,7 @@ package forwardauth
17
import (
18
"encoding/json"
19
"net/http"
20
+ "sort"
21
"strings"
22
23
"github.com/caddyserver/caddy/v2"
@@ -170,42 +171,66 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
171
return nil, dispenser.Errf("the 'uri' subdirective is required")
172
}
173
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.
180
goodResponseHandler := caddyhttp.ResponseHandler{
181
Match: &caddyhttp.ResponseMatcher{
182
StatusCode: []int{2},
183
},
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
+ },
193
},
194
}
195
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)
200
}
201
+ sort.Strings(sortedHeadersToCopy)
202
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
+ }},
224
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
225
handler,
226
"handler",
227
"headers",
228
nil,
229
)},
207
- },
208
- )
230
+ })
231
+ }
232
+
233
+ goodResponseHandler.Routes = append(goodResponseHandler.Routes, copyHeaderRoutes...)
234
235
// note that when a response has any other status than 2xx, then we
236
// use the reverse proxy's default behaviour of copying the response