Added upgrader for forward-auth

- Added v3.2.1 to v3.2.2 upgrader for new forward auth logic
- Optimized css in sso html page
This commit is contained in:
Toby Chui 2025-05-10 22:57:13 +08:00
parent 70d95bd4e4
commit b9c609e413
5 changed files with 463 additions and 25 deletions

View File

@ -3,6 +3,7 @@ package update
import ( import (
v308 "imuslab.com/zoraxy/mod/update/v308" v308 "imuslab.com/zoraxy/mod/update/v308"
v315 "imuslab.com/zoraxy/mod/update/v315" v315 "imuslab.com/zoraxy/mod/update/v315"
v322 "imuslab.com/zoraxy/mod/update/v322"
) )
// Updater Core logic // Updater Core logic
@ -19,6 +20,12 @@ func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
} else if fromVersion == 321 && toVersion == 322 {
//Updating from v3.2.1 to v3.2.2
err := v322.UpdateFrom321To322()
if err != nil {
panic(err)
}
} }
//ADD MORE VERSIONS HERE //ADD MORE VERSIONS HERE

View File

@ -0,0 +1,141 @@
package v322
import (
"net/http"
"sync"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
)
type ProxyType int
// Pull from ratelimit.go
type RequestCountPerIpTable struct {
table sync.Map
}
// Pull from special.go
type RoutingRule struct {
ID string //ID of the routing rule
Enabled bool //If the routing rule enabled
UseSystemAccessControl bool //Pass access control check to system white/black list, set this to false to bypass white/black list
MatchRule func(r *http.Request) bool
RoutingHandler func(http.ResponseWriter, *http.Request)
}
const (
ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here
ProxyTypeHost //Host Proxy, match by host (domain) name
ProxyTypeVdir //Virtual Directory Proxy, match by path prefix
)
/* Basic Auth Related Data structure*/
// Auth credential for basic auth on certain endpoints
type BasicAuthCredentials struct {
Username string
PasswordHash string
}
// Auth credential for basic auth on certain endpoints
type BasicAuthUnhashedCredentials struct {
Username string
Password string
}
// Paths to exclude in basic auth enabled proxy handler
type BasicAuthExceptionRule struct {
PathPrefix string
}
/* Routing Rule Data Structures */
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
// program structure than directly using ProxyEndpoint
type VirtualDirectoryEndpoint struct {
MatchingPath string //Matching prefix of the request path, also act as key
Domain string //Domain or IP to proxy to
RequireTLS bool //Target domain require TLS
SkipCertValidations bool //Set to true to accept self signed certs
Disabled bool //If the rule is enabled
}
// Rules and settings for header rewriting
type HeaderRewriteRules struct {
UserDefinedHeaders []*rewrite.UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
RequestHostOverwrite string //If not empty, this domain will be used to overwrite the Host field in request header
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
PermissionPolicy *permissionpolicy.PermissionsPolicy //Permission policy header
DisableHopByHopHeaderRemoval bool //Do not remove hop-by-hop headers
}
/*
Authentication Providers
*/
const (
AuthMethodNone AuthMethod = iota //No authentication required
AuthMethodBasic //Basic Auth
AuthMethodAuthelia //Authelia
AuthMethodOauth2 //Oauth2
AuthMethodAuthentik
)
type AuthenticationProvider struct {
AuthMethod AuthMethod //The authentication method to use
/* Basic Auth Settings */
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
BasicAuthGroupIDs []string //Group IDs that are allowed to access this endpoint
/* Authelia Settings */
AutheliaURL string //URL of the Authelia server, leave empty to use global settings e.g. authelia.example.com
UseHTTPS bool //Whether to use HTTPS for the Authelia server
}
// A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpointv321 struct {
ProxyType ProxyType //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key
MatchingDomainAlias []string //A list of domains that alias to this rule
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
UseStickySession bool //Use stick session for load balancing
UseActiveLoadBalance bool //Use active loadbalancing, default passive
Disabled bool //If the rule is disabled
//Inbound TLS/SSL Related
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
//Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint
//Custom Headers
HeaderRewriteRules *HeaderRewriteRules
EnableWebsocketCustomHeaders bool //Enable custom headers for websocket connections as well (default only http reqiests)
//Authentication
AuthenticationProvider *AuthenticationProvider
// Rate Limiting
RequireRateLimit bool
RateLimit int64 // Rate limit in requests per second
//Uptime Monitor
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
//Access Control
AccessFilterUUID string //Access filter ID
//Fallback routing logic (Special Rule Sets Only)
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements
Tags []string // Tags for the proxy endpoint
}

View File

@ -0,0 +1,93 @@
package v322
import "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
/*
Authentication Provider in v3.2.2
The only change is the removal of the deprecated Authelia and Authentik SSO
provider, and the addition of the new Forward Auth provider.
Need to map all provider with ID = 4 into 2 and remove the old provider configs
*/
type AuthMethod int
/*
v3.2.1 Authentication Provider
const (
AuthMethodNone AuthMethod = iota //No authentication required
AuthMethodBasic //Basic Auth
AuthMethodAuthelia //Authelia => 2
AuthMethodOauth2 //Oauth2
AuthMethodAuthentik //Authentik => 4
)
v3.2.2 Authentication Provider
const (
AuthMethodNone AuthMethod = iota //No authentication required
AuthMethodBasic //Basic Auth
AuthMethodForward //Forward => 2
AuthMethodOauth2 //Oauth2
)
We need to merge both Authelia and Authentik into the Forward Auth provider, and remove
*/
//The updated structure of the authentication provider
type AuthenticationProviderV322 struct {
AuthMethod AuthMethod //The authentication method to use
/* Basic Auth Settings */
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
BasicAuthGroupIDs []string //Group IDs that are allowed to access this endpoint
/* 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.
}
// A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpointv322 struct {
ProxyType ProxyType //The type of this proxy, see const def
RootOrMatchingDomain string //Matching domain for host, also act as key
MatchingDomainAlias []string //A list of domains that alias to this rule
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
UseStickySession bool //Use stick session for load balancing
UseActiveLoadBalance bool //Use active loadbalancing, default passive
Disabled bool //If the rule is disabled
//Inbound TLS/SSL Related
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
//Virtual Directories
VirtualDirectories []*VirtualDirectoryEndpoint
//Custom Headers
HeaderRewriteRules *HeaderRewriteRules
EnableWebsocketCustomHeaders bool //Enable custom headers for websocket connections as well (default only http reqiests)
//Authentication
AuthenticationProvider *AuthenticationProviderV322
// Rate Limiting
RequireRateLimit bool
RateLimit int64 // Rate limit in requests per second
//Uptime Monitor
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
//Access Control
AccessFilterUUID string //Access filter ID
//Fallback routing logic (Special Rule Sets Only)
DefaultSiteOption int //Fallback routing logic options
DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements
Tags []string // Tags for the proxy endpoint
}

191
src/mod/update/v322/v322.go Normal file
View File

@ -0,0 +1,191 @@
package v322
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/update/updateutil"
)
// UpdateFrom321To322 updates proxy config files from v3.2.1 to v3.2.2
func UpdateFrom321To322() error {
// Load the configs
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
if err != nil {
return err
}
// Backup all the files
err = os.MkdirAll("./conf/proxy-321.old/", 0775)
if err != nil {
return err
}
for _, oldConfigFile := range oldConfigFiles {
// Extract the file name from the path
fileName := filepath.Base(oldConfigFile)
// Construct the backup file path
backupFile := filepath.Join("./conf/proxy-321.old/", fileName)
// Copy the file to the backup directory
err := updateutil.CopyFile(oldConfigFile, backupFile)
if err != nil {
return err
}
}
// Read the config into the old struct
for _, oldConfigFile := range oldConfigFiles {
configContent, err := os.ReadFile(oldConfigFile)
if err != nil {
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
continue
}
thisOldConfigStruct := ProxyEndpointv321{}
err = json.Unmarshal(configContent, &thisOldConfigStruct)
if err != nil {
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
continue
}
// Convert the old struct to the new struct
thisNewConfigStruct := convertV321ToV322(thisOldConfigStruct)
// Write the new config to file
newConfigContent, err := json.MarshalIndent(thisNewConfigStruct, "", " ")
if err != nil {
log.Println("Unable to marshal new config "+filepath.Base(oldConfigFile), err.Error())
continue
}
err = os.WriteFile(oldConfigFile, newConfigContent, 0664)
if err != nil {
log.Println("Unable to write new config "+filepath.Base(oldConfigFile), err.Error())
continue
}
}
return nil
}
func convertV321ToV322(thisOldConfigStruct ProxyEndpointv321) ProxyEndpointv322 {
// Merge both Authelia and Authentik into the Forward Auth provider, and remove the old provider configs
if thisOldConfigStruct.AuthenticationProvider == nil {
//Configs before v3.1.7 with no authentication provider
// Set the default authentication provider
thisOldConfigStruct.AuthenticationProvider = &AuthenticationProvider{
AuthMethod: AuthMethodNone, // Default to no authentication
BasicAuthCredentials: []*BasicAuthCredentials{},
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
BasicAuthGroupIDs: []string{},
AutheliaURL: "",
UseHTTPS: false,
}
} else {
//Override the old authentication provider with the new one
if thisOldConfigStruct.AuthenticationProvider.AuthMethod == AuthMethodAuthelia {
thisOldConfigStruct.AuthenticationProvider.AuthMethod = 2
} else if thisOldConfigStruct.AuthenticationProvider.AuthMethod == AuthMethodAuthentik {
thisOldConfigStruct.AuthenticationProvider.AuthMethod = 2
}
}
if thisOldConfigStruct.AuthenticationProvider.BasicAuthGroupIDs == nil {
//Create an empty basic auth group IDs array if it does not exist
thisOldConfigStruct.AuthenticationProvider.BasicAuthGroupIDs = []string{}
}
newAuthenticationProvider := AuthenticationProviderV322{
AuthMethod: AuthMethodNone, // Default to no authentication
//Fill in the empty arrays
BasicAuthCredentials: []*BasicAuthCredentials{},
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
BasicAuthGroupIDs: []string{},
ForwardAuthURL: "",
ForwardAuthResponseHeaders: []string{},
ForwardAuthResponseClientHeaders: []string{},
ForwardAuthRequestHeaders: []string{},
ForwardAuthRequestExcludedCookies: []string{},
}
// In theory the old config should have a matching itoa value that
// can be converted to the new config
js, err := json.Marshal(thisOldConfigStruct.AuthenticationProvider)
if err != nil {
fmt.Println("Unable to marshal authentication provider "+thisOldConfigStruct.RootOrMatchingDomain, err.Error())
fmt.Println("Using default authentication provider")
}
err = json.Unmarshal(js, &newAuthenticationProvider)
if err != nil {
fmt.Println("Unable to unmarshal authentication provider "+thisOldConfigStruct.RootOrMatchingDomain, err.Error())
fmt.Println("Using default authentication provider")
} else {
fmt.Println("Authentication provider for " + thisOldConfigStruct.RootOrMatchingDomain + " updated")
}
// Fill in any null values in the old config struct
// these are non-upgrader requires values that updates between v3.1.5 to v3.2.1
// will be in null state if not set by the user
if thisOldConfigStruct.VirtualDirectories == nil {
//Create an empty virtual directories array if it does not exist
thisOldConfigStruct.VirtualDirectories = []*VirtualDirectoryEndpoint{}
}
if thisOldConfigStruct.HeaderRewriteRules == nil {
//Create an empty header rewrite rules array if it does not exist
thisOldConfigStruct.HeaderRewriteRules = &HeaderRewriteRules{
UserDefinedHeaders: []*rewrite.UserDefinedHeader{},
RequestHostOverwrite: "",
HSTSMaxAge: 0,
EnablePermissionPolicyHeader: false,
PermissionPolicy: permissionpolicy.GetDefaultPermissionPolicy(),
DisableHopByHopHeaderRemoval: false,
}
}
if thisOldConfigStruct.Tags == nil {
//Create an empty tags array if it does not exist
thisOldConfigStruct.Tags = []string{}
}
if thisOldConfigStruct.MatchingDomainAlias == nil {
//Create an empty matching domain alias array if it does not exist
thisOldConfigStruct.MatchingDomainAlias = []string{}
}
// Update the config struct
thisNewConfigStruct := ProxyEndpointv322{
ProxyType: thisOldConfigStruct.ProxyType,
RootOrMatchingDomain: thisOldConfigStruct.RootOrMatchingDomain,
MatchingDomainAlias: thisOldConfigStruct.MatchingDomainAlias,
ActiveOrigins: thisOldConfigStruct.ActiveOrigins,
InactiveOrigins: thisOldConfigStruct.InactiveOrigins,
UseStickySession: thisOldConfigStruct.UseStickySession,
UseActiveLoadBalance: thisOldConfigStruct.UseActiveLoadBalance,
Disabled: thisOldConfigStruct.Disabled,
BypassGlobalTLS: thisOldConfigStruct.BypassGlobalTLS,
VirtualDirectories: thisOldConfigStruct.VirtualDirectories,
HeaderRewriteRules: thisOldConfigStruct.HeaderRewriteRules,
EnableWebsocketCustomHeaders: thisOldConfigStruct.EnableWebsocketCustomHeaders,
RequireRateLimit: thisOldConfigStruct.RequireRateLimit,
RateLimit: thisOldConfigStruct.RateLimit,
DisableUptimeMonitor: thisOldConfigStruct.DisableUptimeMonitor,
AccessFilterUUID: thisOldConfigStruct.AccessFilterUUID,
DefaultSiteOption: thisOldConfigStruct.DefaultSiteOption,
DefaultSiteValue: thisOldConfigStruct.DefaultSiteValue,
Tags: thisOldConfigStruct.Tags,
}
// Set the new authentication provider
thisNewConfigStruct.AuthenticationProvider = &newAuthenticationProvider
return thisNewConfigStruct
}

View File

@ -32,34 +32,40 @@
<input type="text" id="forwardAuthAddress" name="forwardAuthAddress" placeholder="Enter Forward Auth Address"> <input type="text" id="forwardAuthAddress" name="forwardAuthAddress" placeholder="Enter Forward Auth Address">
<small>The full remote address or URL of the authorization servers forward auth endpoint. <strong>Example:</strong> https://auth.example.com/authz/forward-auth</small> <small>The full remote address or URL of the authorization servers forward auth endpoint. <strong>Example:</strong> https://auth.example.com/authz/forward-auth</small>
</div> </div>
<div class="ui advancedSSOForwardAuthOptions accordion" style="margin-top:0.6em;"> <div class="ui basic segment advanceoptions" style="margin-top:0.6em;">
<div class="title"> <div class="ui advancedSSOForwardAuthOptions accordion">
<i class="dropdown icon"></i> <div class="title">
Advanced Options <i class="dropdown icon"></i>
</div> Advanced Options
<div class="content">
<div class="field">
<label for="forwardAuthResponseHeaders">Response Headers</label>
<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 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>
<div class="field"> <div class="content">
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label> <div class="field">
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers"> <label for="forwardAuthResponseHeaders">Response Headers</label>
<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> <input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
</div> <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. <br>
<div class="field"> <strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small>
<label for="forwardAuthRequestHeaders">Request Headers</label> </div>
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers"> <div class="field">
<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> <label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
</div> <input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
<div class="field"> <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. <br>
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label> <strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></small>
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies"> </div>
<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 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. <br>
<strong>Example:</strong> <code>Cookie,Authorization</code></small>
</div>
<div class="field">
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
<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. If not set no cookies are excluded. <br>
<strong>Example:</strong> <code>authelia_session,another_session</code></small>
</div>
</div> </div>
</div> </div>
</div><br /> </div>
<button class="ui basic button" onclick="event.preventDefault(); updateForwardAuthSettings();"><i class="green check icon"></i> Apply Change</button> <button class="ui basic button" onclick="event.preventDefault(); updateForwardAuthSettings();"><i class="green check icon"></i> Apply Change</button>
</form> </form>
</div> </div>