System arch optimization

- Optimized types and definitions
- Moved shutdown seq to start.go file
- Moved authelia to auth/sso module
- Added different auth types support (wip)
- Updated proxy config structure
- Added v3.1.4 to v3.1.5 auto upgrade utilities
- Fixed #426
- Optimized status page UI
- Added options to disable uptime montior in config
This commit is contained in:
Toby Chui
2024-12-12 20:49:53 +08:00
parent 9e95d84627
commit bb0f55018c
23 changed files with 689 additions and 215 deletions

View File

@@ -84,16 +84,18 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
//SSO Interception Mode
if sep.UseSSOIntercept {
allowPass := h.Parent.Option.SSOHandler.ServeForwardAuth(w, r)
if !allowPass {
h.Parent.Option.Logger.LogHTTPRequest(r, "sso-x", 307)
return
/*
if sep.AuthenticationProvider.SSOInterceptMode {
allowPass := h.Parent.Option.SSOHandler.ServeForwardAuth(w, r)
if !allowPass {
h.Parent.Option.Logger.LogHTTPRequest(r, "sso-x", 307)
return
}
}
}
*/
//Validate basic auth
if sep.RequireBasicAuth {
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
err := h.handleBasicAuthRouting(w, r, sep)
if err != nil {
h.Parent.Option.Logger.LogHTTPRequest(r, "host", 401)
@@ -108,7 +110,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//Virtual directory routing rule found. Route via vdir mode
h.vdirRequest(w, r, targetProxyEndpoint)
return
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
} else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyTypeRoot {
potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil && !potentialProxtEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint
@@ -180,7 +182,7 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
//Virtual directory routing rule found. Route via vdir mode
h.vdirRequest(w, r, targetProxyEndpoint)
return
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
} else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyTypeRoot {
potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
//Missing tailing slash. Redirect to target proxy endpoint

View File

@@ -1,57 +0,0 @@
package dynamicproxy
import (
"errors"
"fmt"
"net/http"
"net/url"
)
func (h *ProxyHandler) handleAutheliaAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
err := handleAutheliaAuth(w, r, pe)
if err != nil {
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
}
return err
}
func handleAutheliaAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
client := &http.Client{}
// TODO: provide authelia url by config variable
req, err := http.NewRequest("POST", "https://authelia.mydomain.com/api/verify", nil)
if err != nil {
pe.parent.Option.Logger.PrintAndLog("Authelia", "Unable to create request", err)
w.WriteHeader(401)
return errors.New("unauthorized")
}
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
req.Header.Add("X-Original-URL", fmt.Sprintf("%s://%s", scheme, r.Host))
// Copy cookies from the incoming request
for _, cookie := range r.Cookies() {
req.AddCookie(cookie)
}
resp, err := client.Do(req)
if err != nil {
pe.parent.Option.Logger.PrintAndLog("Authelia", "Unable to verify", err)
w.WriteHeader(401)
return errors.New("unauthorized")
}
if resp.StatusCode != 200 {
// TODO: provide authelia url by config variable
redirectURL := "https://authelia.mydomain.com/?rd=" + url.QueryEscape(scheme+"://"+r.Host+r.URL.String()) + "&rm=" + r.Method
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
return errors.New("unauthorized")
}
return nil
}

View File

@@ -26,9 +26,9 @@ func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Req
// Handle basic auth logic
// do not write to http.ResponseWriter if err return is not nil (already handled by this function)
func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
if len(pe.BasicAuthExceptionRules) > 0 {
if len(pe.AuthenticationProvider.BasicAuthExceptionRules) > 0 {
//Check if the current path matches the exception rules
for _, exceptionRule := range pe.BasicAuthExceptionRules {
for _, exceptionRule := range pe.AuthenticationProvider.BasicAuthExceptionRules {
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
//This path is excluded from basic auth
return nil
@@ -46,7 +46,7 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
//Check for the credentials to see if there is one matching
hashedPassword := auth.Hash(p)
matchingFound := false
for _, cred := range pe.BasicAuthCredentials {
for _, cred := range pe.AuthenticationProvider.BasicAuthCredentials {
if u == cred.Username && hashedPassword == cred.PasswordHash {
matchingFound = true

View File

@@ -144,7 +144,7 @@ func (router *Router) StartProxyService() error {
}
//Validate basic auth
if sep.RequireBasicAuth {
if sep.AuthenticationProvider.AuthMethod == AuthMethodBasic {
err := handleBasicAuth(w, r, sep)
if err != nil {
return
@@ -161,8 +161,8 @@ func (router *Router) StartProxyService() error {
ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: originalHostHeader,
UseTLS: selectedUpstream.RequireTLS,
HostHeaderOverwrite: sep.RequestHostOverwrite,
NoRemoveHopByHop: sep.DisableHopByHopHeaderRemoval,
HostHeaderOverwrite: sep.HeaderRewriteRules.RequestHostOverwrite,
NoRemoveHopByHop: sep.HeaderRewriteRules.DisableHopByHopHeaderRemoval,
PathPrefix: "",
Version: sep.parent.Option.HostVersion,
})

View File

@@ -27,7 +27,7 @@ import (
// Check if a user define header exists in this endpoint, ignore case
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
for _, header := range ep.UserDefinedHeaders {
for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders {
if strings.EqualFold(header.Key, key) {
return true
}
@@ -38,13 +38,13 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
// Remvoe a user defined header from the list
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
newHeaderList := []*rewrite.UserDefinedHeader{}
for _, header := range ep.UserDefinedHeaders {
for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders {
if !strings.EqualFold(header.Key, key) {
newHeaderList = append(newHeaderList, header)
}
}
ep.UserDefinedHeaders = newHeaderList
ep.HeaderRewriteRules.UserDefinedHeaders = newHeaderList
return nil
}
@@ -56,7 +56,7 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *rewrite.UserDefined
}
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
ep.HeaderRewriteRules.UserDefinedHeaders = append(ep.HeaderRewriteRules.UserDefinedHeaders, newHeaderRule)
return nil
}
@@ -123,9 +123,9 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
return nil, err
}
if ep.ProxyType == ProxyType_Root {
if ep.ProxyType == ProxyTypeRoot {
parentRouter.Root = readyRoutingRule
} else if ep.ProxyType == ProxyType_Host {
} else if ep.ProxyType == ProxyTypeHost {
ep.Remove()
parentRouter.AddProxyRouteToRuntime(readyRoutingRule)
} else {

View File

@@ -143,9 +143,11 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
}
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
Logger: h.Parent.Option.Logger,
SkipTLSValidation: selectedUpstream.SkipCertValidations,
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
CopyAllHeaders: true,
UserDefinedHeaders: target.HeaderRewriteRules.UserDefinedHeaders,
Logger: h.Parent.Option.Logger,
})
wspHandler.ServeHTTP(w, r)
return
@@ -160,15 +162,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
}
//Populate the user-defined headers with the values from the request
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.UserDefinedHeaders)
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.HeaderRewriteRules.UserDefinedHeaders)
//Build downstream and upstream header rules
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
UserDefinedHeaders: rewrittenUserDefinedHeaders,
HSTSMaxAge: target.HSTSMaxAge,
HSTSMaxAge: target.HeaderRewriteRules.HSTSMaxAge,
HSTSIncludeSubdomains: target.ContainsWildcardName(true),
EnablePermissionPolicyHeader: target.EnablePermissionPolicyHeader,
PermissionPolicy: target.PermissionPolicy,
EnablePermissionPolicyHeader: target.HeaderRewriteRules.EnablePermissionPolicyHeader,
PermissionPolicy: target.HeaderRewriteRules.PermissionPolicy,
})
//Handle the request reverse proxy
@@ -180,8 +182,8 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
PathPrefix: "",
UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: target.RequestHostOverwrite,
NoRemoveHopByHop: target.DisableHopByHopHeaderRemoval,
HostHeaderOverwrite: target.HeaderRewriteRules.RequestHostOverwrite,
NoRemoveHopByHop: target.HeaderRewriteRules.DisableHopByHopHeaderRemoval,
Version: target.parent.Option.HostVersion,
})
@@ -221,9 +223,11 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
}
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
Logger: h.Parent.Option.Logger,
SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
CopyAllHeaders: true,
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
Logger: h.Parent.Option.Logger,
})
wspHandler.ServeHTTP(w, r)
return
@@ -238,15 +242,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
}
//Populate the user-defined headers with the values from the request
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.parent.UserDefinedHeaders)
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.parent.HeaderRewriteRules.UserDefinedHeaders)
//Build downstream and upstream header rules, use the parent (subdomain) endpoint's headers
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
UserDefinedHeaders: rewrittenUserDefinedHeaders,
HSTSMaxAge: target.parent.HSTSMaxAge,
HSTSMaxAge: target.parent.HeaderRewriteRules.HSTSMaxAge,
HSTSIncludeSubdomains: target.parent.ContainsWildcardName(true),
EnablePermissionPolicyHeader: target.parent.EnablePermissionPolicyHeader,
PermissionPolicy: target.parent.PermissionPolicy,
EnablePermissionPolicyHeader: target.parent.HeaderRewriteRules.EnablePermissionPolicyHeader,
PermissionPolicy: target.parent.HeaderRewriteRules.PermissionPolicy,
})
//Handle the virtual directory reverse proxy request
@@ -257,7 +261,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
PathPrefix: target.MatchingPath,
UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: target.parent.RequestHostOverwrite,
HostHeaderOverwrite: target.parent.HeaderRewriteRules.RequestHostOverwrite,
Version: target.parent.parent.Option.HostVersion,
})

View File

@@ -19,10 +19,12 @@ import (
"imuslab.com/zoraxy/mod/tlscert"
)
type ProxyType int
const (
ProxyType_Root = 0
ProxyType_Host = 1
ProxyType_Vdir = 2
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
)
type ProxyHandler struct {
@@ -53,14 +55,14 @@ type RouterOption struct {
/* Router Object */
type Router struct {
Option *RouterOption
ProxyEndpoints *sync.Map
Running bool
Root *ProxyEndpoint
mux http.Handler
server *http.Server
tlsListener net.Listener
ProxyEndpoints *sync.Map //Map of ProxyEndpoint objects, each ProxyEndpoint object is a routing rule that handle incoming requests
Running bool //If the router is running
Root *ProxyEndpoint //Root proxy endpoint, default site
mux http.Handler //HTTP handler
server *http.Server //HTTP server
tlsListener net.Listener //TLS listener, handle SNI routing
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
routingRules []*RoutingRule
routingRules []*RoutingRule //Special routing rules, handle high priority routing like ACME request handling
tlsRedirectStop chan bool //Stop channel for tls redirection server
rateLimterStop chan bool //Stop channel for rate limiter
@@ -99,9 +101,42 @@ type VirtualDirectoryEndpoint struct {
parent *ProxyEndpoint `json:"-"`
}
// 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 Provider
TODO: Move these into a dedicated module
*/
type AuthMethod int
const (
AuthMethodNone AuthMethod = iota //No authentication required
AuthMethodBasic //Basic Auth
AuthMethodAuthelia //Authelia
AuthMethodOauth2 //Oauth2
)
type AuthenticationProvider struct {
AuthMethod AuthMethod //The authentication method to use
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
}
// A proxy endpoint record, a general interface for handling inbound routing
type ProxyEndpoint struct {
ProxyType int //The type of this proxy, see const def
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
@@ -117,23 +152,18 @@ type ProxyEndpoint struct {
VirtualDirectories []*VirtualDirectoryEndpoint
//Custom Headers
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
HeaderRewriteRules *HeaderRewriteRules
//Authentication
RequireBasicAuth bool //Set to true to request basic auth before proxy
BasicAuthCredentials []*BasicAuthCredentials //Basic auth credentials
BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
UseSSOIntercept bool //Allow SSO to intercept this endpoint and provide authentication via Oauth2 credentials
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