1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00

forwardauth: Skip copying missing response headers (#6608)

This commit is contained in:
Francis Lavoie 2024-11-04 16:58:53 -05:00 committed by GitHub
parent 00f948c605
commit 05cfb121ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 267 additions and 34 deletions

View file

@ -1,6 +1,6 @@
app.example.com { app.example.com {
forward_auth authelia:9091 { forward_auth authelia:9091 {
uri /api/verify?rd=https://authelia.example.com uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
} }
@ -39,6 +39,13 @@ app.example.com {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@ -47,19 +54,104 @@ app.example.com {
"set": { "set": {
"Remote-Email": [ "Remote-Email": [
"{http.reverse_proxy.header.Remote-Email}" "{http.reverse_proxy.header.Remote-Email}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Email}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Groups": [ "Remote-Groups": [
"{http.reverse_proxy.header.Remote-Groups}" "{http.reverse_proxy.header.Remote-Groups}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Groups}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-Name": [ "Remote-Name": [
"{http.reverse_proxy.header.Remote-Name}" "{http.reverse_proxy.header.Remote-Name}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-Name}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"Remote-User": [ "Remote-User": [
"{http.reverse_proxy.header.Remote-User}" "{http.reverse_proxy.header.Remote-User}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.Remote-User}": [
""
]
}
}
]
}
] ]
} }
] ]
@ -80,7 +172,7 @@ app.example.com {
}, },
"rewrite": { "rewrite": {
"method": "GET", "method": "GET",
"uri": "/api/verify?rd=https://authelia.example.com" "uri": "/api/authz/forward-auth"
}, },
"upstreams": [ "upstreams": [
{ {

View file

@ -28,6 +28,13 @@ forward_auth localhost:9000 {
] ]
}, },
"routes": [ "routes": [
{
"handle": [
{
"handler": "vars"
}
]
},
{ {
"handle": [ "handle": [
{ {
@ -36,22 +43,131 @@ forward_auth localhost:9000 {
"set": { "set": {
"1": [ "1": [
"{http.reverse_proxy.header.A}" "{http.reverse_proxy.header.A}"
], ]
"3": [ }
"{http.reverse_proxy.header.C}" }
], }
"5": [ ],
"{http.reverse_proxy.header.E}" "match": [
], {
"not": [
{
"vars": {
"{http.reverse_proxy.header.A}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"B": [ "B": [
"{http.reverse_proxy.header.B}" "{http.reverse_proxy.header.B}"
], ]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.B}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"3": [
"{http.reverse_proxy.header.C}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.C}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"D": [ "D": [
"{http.reverse_proxy.header.D}" "{http.reverse_proxy.header.D}"
] ]
} }
} }
} }
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.D}": [
""
]
}
}
]
}
]
},
{
"handle": [
{
"handler": "headers",
"request": {
"set": {
"5": [
"{http.reverse_proxy.header.E}"
]
}
}
}
],
"match": [
{
"not": [
{
"vars": {
"{http.reverse_proxy.header.E}": [
""
]
}
}
]
}
] ]
} }
] ]

View file

@ -17,6 +17,7 @@ package forwardauth
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"sort"
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
@ -170,42 +171,66 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
return nil, dispenser.Errf("the 'uri' subdirective is required") return nil, dispenser.Errf("the 'uri' subdirective is required")
} }
// set up handler for good responses; when a response // Set up handler for good responses; when a response has 2xx status,
// has 2xx status, then we will copy some headers from // then we will copy some headers from the response onto the original
// the response onto the original request, and allow // request, and allow handling to continue down the middleware chain,
// handling to continue down the middleware chain, // by _not_ executing a terminal handler. We must have at least one
// by _not_ executing a terminal handler. // route in the response handler, even if it's no-op, so that the
// response handling logic in reverse_proxy doesn't skip this entry.
goodResponseHandler := caddyhttp.ResponseHandler{ goodResponseHandler := caddyhttp.ResponseHandler{
Match: &caddyhttp.ResponseMatcher{ Match: &caddyhttp.ResponseMatcher{
StatusCode: []int{2}, StatusCode: []int{2},
}, },
Routes: []caddyhttp.Route{}, Routes: []caddyhttp.Route{
} {
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
handler := &headers.Handler{ &caddyhttp.VarsMiddleware{},
Request: &headers.HeaderOps{ "handler",
Set: http.Header{}, "vars",
nil,
)},
},
}, },
} }
// the list of headers to copy may be empty, but that's okay; we // Sort the headers so that the order in the JSON output is deterministic.
// need at least one handler in the routes for the response handling sortedHeadersToCopy := make([]string, 0, len(headersToCopy))
// logic in reverse_proxy to not skip this entry as empty. for k := range headersToCopy {
for from, to := range headersToCopy { sortedHeadersToCopy = append(sortedHeadersToCopy, k)
handler.Request.Set.Set(to, "{http.reverse_proxy.header."+http.CanonicalHeaderKey(from)+"}")
} }
sort.Strings(sortedHeadersToCopy)
goodResponseHandler.Routes = append( // Set up handlers to copy headers from the auth response onto the
goodResponseHandler.Routes, // original request. We use vars matchers to test that the placeholder
caddyhttp.Route{ // values aren't empty, because the header handler would not replace
// placeholders which have no value.
copyHeaderRoutes := []caddyhttp.Route{}
for _, from := range sortedHeadersToCopy {
to := http.CanonicalHeaderKey(headersToCopy[from])
placeholderName := "http.reverse_proxy.header." + http.CanonicalHeaderKey(from)
handler := &headers.Handler{
Request: &headers.HeaderOps{
Set: http.Header{
to: []string{"{" + placeholderName + "}"},
},
},
}
copyHeaderRoutes = append(copyHeaderRoutes, caddyhttp.Route{
MatcherSetsRaw: []caddy.ModuleMap{{
"not": h.JSON(caddyhttp.MatchNot{MatcherSetsRaw: []caddy.ModuleMap{{
"vars": h.JSON(caddyhttp.VarsMatcher{"{" + placeholderName + "}": []string{""}}),
}}}),
}},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject( HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
handler, handler,
"handler", "handler",
"headers", "headers",
nil, nil,
)}, )},
}, })
) }
goodResponseHandler.Routes = append(goodResponseHandler.Routes, copyHeaderRoutes...)
// note that when a response has any other status than 2xx, then we // note that when a response has any other status than 2xx, then we
// use the reverse proxy's default behaviour of copying the response // use the reverse proxy's default behaviour of copying the response