@marcel91200 / caddy Mirror · read-only / commits / 05cfb121
Fork 0

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