mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
feat: forward auth
Add support for request headers and response client headers.
This commit is contained in:
parent
8f046a0b47
commit
3f1c50c009
@ -9,6 +9,8 @@ const (
|
|||||||
|
|
||||||
DatabaseKeyAddress = "address"
|
DatabaseKeyAddress = "address"
|
||||||
DatabaseKeyResponseHeaders = "responseHeaders"
|
DatabaseKeyResponseHeaders = "responseHeaders"
|
||||||
|
DatabaseKeyResponseClientHeaders = "responseClientHeaders"
|
||||||
|
DatabaseKeyRequestHeaders = "requestHeaders"
|
||||||
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
|
||||||
|
|
||||||
HeaderXForwardedProto = "X-Forwarded-Proto"
|
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||||
|
@ -20,6 +20,14 @@ type AuthRouterOptions struct {
|
|||||||
// the request.
|
// the request.
|
||||||
ResponseHeaders []string
|
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 is a list of cookie keys that should be removed from every request sent to the upstream.
|
||||||
RequestExcludedCookies []string
|
RequestExcludedCookies []string
|
||||||
|
|
||||||
@ -39,12 +47,16 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
|
|||||||
//Read settings from database if available.
|
//Read settings from database if available.
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
|
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
|
||||||
|
|
||||||
responseHeaders, requestExcludedCookies := "", ""
|
responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", ""
|
||||||
|
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders)
|
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders)
|
||||||
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
|
||||||
|
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
|
||||||
|
|
||||||
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
|
options.ResponseClientHeaders = strings.Split(responseHeaders, ",")
|
||||||
|
options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
return &AuthRouter{
|
return &AuthRouter{
|
||||||
@ -73,6 +85,8 @@ func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) {
|
|||||||
js, _ := json.Marshal(map[string]interface{}{
|
js, _ := json.Marshal(map[string]interface{}{
|
||||||
DatabaseKeyAddress: ar.options.Address,
|
DatabaseKeyAddress: ar.options.Address,
|
||||||
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
|
||||||
|
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
|
||||||
|
DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
|
||||||
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -90,18 +104,23 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are optional fields.
|
// These are optional fields and can be empty strings.
|
||||||
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
||||||
|
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
|
||||||
|
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
|
||||||
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
|
||||||
|
|
||||||
// Write changes to runtime
|
// Write changes to runtime
|
||||||
ar.options.Address = address
|
ar.options.Address = address
|
||||||
ar.options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
ar.options.ResponseHeaders = strings.Split(responseHeaders, ",")
|
||||||
|
ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
|
||||||
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
|
||||||
|
|
||||||
// Write changes to database
|
// Write changes to database
|
||||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyAddress, address)
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyAddress, address)
|
||||||
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseHeaders, responseHeaders)
|
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)
|
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
|
||||||
|
|
||||||
utils.SendOK(w)
|
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 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.
|
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true)
|
||||||
headerCopyExcluded(r.Header, req.Header, nil)
|
|
||||||
|
|
||||||
// TODO: Add support for upstream headers.
|
// TODO: Add support for upstream headers.
|
||||||
rSetForwardedHeaders(r, req)
|
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.
|
// 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 {
|
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.
|
// If the user has specified a list of cookies to be removed from the request, deterministically remove them.
|
||||||
headerCookieRedact(r, ar.options.RequestExcludedCookies)
|
headerCookieRedact(r, ar.options.RequestExcludedCookies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy specific user-specified headers from the response of the forward auth request to the request sent to the
|
if len(ar.options.ResponseHeaders) != 0 {
|
||||||
// upstream server/next hop.
|
// Copy specific user-specified headers from the response of the forward auth request to the request sent to the
|
||||||
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders)
|
// upstream server/next hop.
|
||||||
|
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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 {
|
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.
|
// 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) {
|
if stringInSliceFold(key, doNotCopyHeaders) {
|
||||||
continue
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
destination[key] = append(destination[key], values...)
|
if values, ok := original[key]; ok {
|
||||||
|
destination[key] = append(destination[key], values...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ func GetDefaultAuthenticationProvider() *AuthenticationProvider {
|
|||||||
BasicAuthGroupIDs: []string{},
|
BasicAuthGroupIDs: []string{},
|
||||||
ForwardAuthURL: "",
|
ForwardAuthURL: "",
|
||||||
ForwardAuthResponseHeaders: []string{},
|
ForwardAuthResponseHeaders: []string{},
|
||||||
|
ForwardAuthResponseClientHeaders: []string{},
|
||||||
|
ForwardAuthRequestHeaders: []string{},
|
||||||
ForwardAuthRequestExcludedCookies: []string{},
|
ForwardAuthRequestExcludedCookies: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,8 @@ type AuthenticationProvider struct {
|
|||||||
/* Forward Auth Settings */
|
/* Forward Auth Settings */
|
||||||
ForwardAuthURL string // Full URL of the Forward Auth endpoint. Example: https://auth.example.com/api/authz/forward-auth
|
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.
|
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.
|
ForwardAuthRequestExcludedCookies []string // List of cookies to exclude from the request after sending it to the forward auth server.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +41,22 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
<label for="forwardAuthResponseHeaders">Response Headers</label>
|
||||||
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
|
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
|
||||||
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response, to the request to the backend. <strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small>
|
<small>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. <strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
|
||||||
|
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
|
||||||
|
<small>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. <strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></small>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="forwardAuthRequestHeaders">Request Headers</label>
|
||||||
|
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers">
|
||||||
|
<small>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. <strong>Example:</strong> <code>Cookie,Authorization</code></small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
|
||||||
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
|
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
|
||||||
<small>Comma separated list of case-sensitive cookie names to exclude from the request to the backend. <strong>Example:</strong> <code>authelia_session,another_session</code></small>
|
<small>Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded. <strong>Example:</strong> <code>authelia_session,another_session</code></small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><br />
|
</div><br />
|
||||||
@ -65,6 +75,8 @@
|
|||||||
success: function(data) {
|
success: function(data) {
|
||||||
$('#forwardAuthAddress').val(data.address);
|
$('#forwardAuthAddress').val(data.address);
|
||||||
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
|
||||||
|
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
|
||||||
|
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
|
||||||
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
|
||||||
},
|
},
|
||||||
error: function(jqXHR, textStatus, errorThrown) {
|
error: function(jqXHR, textStatus, errorThrown) {
|
||||||
@ -76,9 +88,11 @@
|
|||||||
function updateForwardAuthSettings() {
|
function updateForwardAuthSettings() {
|
||||||
const address = $('#forwardAuthAddress').val();
|
const address = $('#forwardAuthAddress').val();
|
||||||
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
const responseHeaders = $('#forwardAuthResponseHeaders').val();
|
||||||
|
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
|
||||||
|
const requestHeaders = $('#forwardAuthRequestHeaders').val();
|
||||||
const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').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({
|
$.cjax({
|
||||||
url: '/api/sso/forward-auth',
|
url: '/api/sso/forward-auth',
|
||||||
@ -86,6 +100,8 @@
|
|||||||
data: {
|
data: {
|
||||||
address: address,
|
address: address,
|
||||||
responseHeaders: responseHeaders,
|
responseHeaders: responseHeaders,
|
||||||
|
responseClientHeaders: responseClientHeaders,
|
||||||
|
requestHeaders: requestHeaders,
|
||||||
requestExcludedCookies: requestExcludedCookies
|
requestExcludedCookies: requestExcludedCookies
|
||||||
},
|
},
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user