diff --git a/src/mod/auth/sso/forward/const.go b/src/mod/auth/sso/forward/const.go index 8fd1351..164ef79 100644 --- a/src/mod/auth/sso/forward/const.go +++ b/src/mod/auth/sso/forward/const.go @@ -9,6 +9,8 @@ const ( DatabaseKeyAddress = "address" DatabaseKeyResponseHeaders = "responseHeaders" + DatabaseKeyResponseClientHeaders = "responseClientHeaders" + DatabaseKeyRequestHeaders = "requestHeaders" DatabaseKeyRequestExcludedCookies = "requestExcludedCookies" HeaderXForwardedProto = "X-Forwarded-Proto" diff --git a/src/mod/auth/sso/forward/forward.go b/src/mod/auth/sso/forward/forward.go index b9f23dc..3ef59ae 100644 --- a/src/mod/auth/sso/forward/forward.go +++ b/src/mod/auth/sso/forward/forward.go @@ -20,6 +20,14 @@ type AuthRouterOptions struct { // the request. ResponseHeaders []string + // ResponseClientHeaders is a list of headers to be copied from the response if provided by the forward auth + // endpoint to the response to the client. + ResponseClientHeaders []string + + // RequestHeaders is a list of headers to be copied from the request to the authorization server. If empty all + // headers are copied. + RequestHeaders []string + // RequestExcludedCookies is a list of cookie keys that should be removed from every request sent to the upstream. RequestExcludedCookies []string @@ -39,12 +47,16 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter { //Read settings from database if available. options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address) - responseHeaders, requestExcludedCookies := "", "" + responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", "" - options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders) - options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies) + options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders) + options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders) + options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders) + options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies) options.ResponseHeaders = strings.Split(responseHeaders, ",") + options.ResponseClientHeaders = strings.Split(responseHeaders, ",") + options.RequestHeaders = strings.Split(requestHeaders, ",") options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") return &AuthRouter{ @@ -73,6 +85,8 @@ func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) { js, _ := json.Marshal(map[string]interface{}{ DatabaseKeyAddress: ar.options.Address, DatabaseKeyResponseHeaders: ar.options.ResponseHeaders, + DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders, + DatabaseKeyRequestHeaders: ar.options.RequestHeaders, DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies, }) @@ -90,18 +104,23 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request) return } - // These are optional fields. + // These are optional fields and can be empty strings. responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders) + responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders) + requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders) requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies) // Write changes to runtime ar.options.Address = address ar.options.ResponseHeaders = strings.Split(responseHeaders, ",") + ar.options.RequestHeaders = strings.Split(requestHeaders, ",") ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") // Write changes to database ar.options.Database.Write(DatabaseTable, DatabaseKeyAddress, address) ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders) + ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders) + ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders) ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies) utils.SendOK(w) @@ -134,8 +153,7 @@ func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.R } // TODO: Add opt-in support for copying the request body to the forward auth request. - // TODO: Add support for customizing which headers are copied from the request to the forward auth request. - headerCopyExcluded(r.Header, req.Header, nil) + headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true) // TODO: Add support for upstream headers. rSetForwardedHeaders(r, req) @@ -163,16 +181,20 @@ func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.R // Responses within the 200-299 range are considered successful and allow the proxy to handle the request. if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices { - // TODO: Add support for copying response headers to the response (in the user agent), not just the request. + if len(ar.options.ResponseClientHeaders) != 0 { + headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false) + } - if len(ar.options.ResponseHeaders) != 0 { + if len(ar.options.RequestExcludedCookies) != 0 { // If the user has specified a list of cookies to be removed from the request, deterministically remove them. headerCookieRedact(r, ar.options.RequestExcludedCookies) } - // Copy specific user-specified headers from the response of the forward auth request to the request sent to the - // upstream server/next hop. - headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders) + if len(ar.options.ResponseHeaders) != 0 { + // Copy specific user-specified headers from the response of the forward auth request to the request sent to the + // upstream server/next hop. + headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false) + } return nil } @@ -235,18 +257,35 @@ func headerCopyExcluded(original, destination http.Header, excludedHeaders []str } } -func headerCopyIncluded(original, destination http.Header, includedHeaders []string) { +func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) { + if allIfEmpty && len(includedHeaders) == 0 { + headerCopyAll(original, destination) + } else { + headerCopyIncludedExact(original, destination, includedHeaders) + } +} + +func headerCopyAll(original, destination http.Header) { for key, values := range original { // We should never copy the headers in the below list, even if they're in the list provided by a user. if stringInSliceFold(key, doNotCopyHeaders) { continue } - if !stringInSliceFold(key, includedHeaders) { + destination[key] = append(destination[key], values...) + } +} + +func headerCopyIncludedExact(original, destination http.Header, keys []string) { + for _, key := range keys { + // We should never copy the headers in the below list, even if they're in the list provided by a user. + if stringInSliceFold(key, doNotCopyHeaders) { continue } - destination[key] = append(destination[key], values...) + if values, ok := original[key]; ok { + destination[key] = append(destination[key], values...) + } } } diff --git a/src/mod/dynamicproxy/default.go b/src/mod/dynamicproxy/default.go index eac6e21..aea7100 100644 --- a/src/mod/dynamicproxy/default.go +++ b/src/mod/dynamicproxy/default.go @@ -23,6 +23,8 @@ func GetDefaultAuthenticationProvider() *AuthenticationProvider { BasicAuthGroupIDs: []string{}, ForwardAuthURL: "", ForwardAuthResponseHeaders: []string{}, + ForwardAuthResponseClientHeaders: []string{}, + ForwardAuthRequestHeaders: []string{}, ForwardAuthRequestExcludedCookies: []string{}, } } diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 21eddd5..bf181f4 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -155,6 +155,8 @@ type AuthenticationProvider struct { /* Forward Auth Settings */ ForwardAuthURL string // Full URL of the Forward Auth endpoint. Example: https://auth.example.com/api/authz/forward-auth ForwardAuthResponseHeaders []string // List of headers to copy from the forward auth server response to the request. + ForwardAuthResponseClientHeaders []string // List of headers to copy from the forward auth server response to the client response. + ForwardAuthRequestHeaders []string // List of headers to copy from the original request to the auth server. If empty all are copied. ForwardAuthRequestExcludedCookies []string // List of cookies to exclude from the request after sending it to the forward auth server. } diff --git a/src/web/components/sso.html b/src/web/components/sso.html index 0440e33..a3d2f94 100644 --- a/src/web/components/sso.html +++ b/src/web/components/sso.html @@ -41,12 +41,22 @@
- Comma separated list of case-insensitive headers to copy from the authorization servers response, to the request to the backend. Example: Remote-User,Remote-Groups,Remote-Email,Remote-Name + Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. Example: Remote-User,Remote-Groups,Remote-Email,Remote-Name +
+
+ + + Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied. Example: Set-Cookie,WWW-Authenticate +
+
+ + + Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied. Example: Cookie,Authorization
- Comma separated list of case-sensitive cookie names to exclude from the request to the backend. Example: authelia_session,another_session + Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded. Example: authelia_session,another_session

@@ -65,6 +75,8 @@ success: function(data) { $('#forwardAuthAddress').val(data.address); $('#forwardAuthResponseHeaders').val(data.responseHeaders.join(",")); + $('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(",")); + $('#forwardAuthRequestHeaders').val(data.requestHeaders.join(",")); $('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(",")); }, error: function(jqXHR, textStatus, errorThrown) { @@ -76,9 +88,11 @@ function updateForwardAuthSettings() { const address = $('#forwardAuthAddress').val(); const responseHeaders = $('#forwardAuthResponseHeaders').val(); + const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val(); + const requestHeaders = $('#forwardAuthRequestHeaders').val(); const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').val(); - console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`); + console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`); $.cjax({ url: '/api/sso/forward-auth', @@ -86,6 +100,8 @@ data: { address: address, responseHeaders: responseHeaders, + responseClientHeaders: responseClientHeaders, + requestHeaders: requestHeaders, requestExcludedCookies: requestExcludedCookies }, success: function(data) {