mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-09-25 13:39:47 +02:00
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:
87
src/mod/auth/sso/authelia/authelia.go
Normal file
87
src/mod/auth/sso/authelia/authelia.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package authelia
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
AutheliaURL string //URL of the Authelia server, e.g. authelia.example.com
|
||||
UseHTTPS bool //Whether to use HTTPS for the Authelia server
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type AutheliaHandler struct {
|
||||
options *Options
|
||||
}
|
||||
|
||||
func NewAutheliaAuthenticator(options *Options) *AutheliaHandler {
|
||||
return &AutheliaHandler{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleAutheliaAuthRouting is the handler for Authelia authentication, if the error is not nil, the request will be forwarded to the endpoint
|
||||
// Do not continue processing or write to the response writer if the error is not nil
|
||||
func (h *AutheliaHandler) HandleAutheliaAuthRouting(w http.ResponseWriter, r *http.Request, pe *dynamicproxy.ProxyEndpoint) error {
|
||||
err := h.handleAutheliaAuth(w, r)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *AutheliaHandler) handleAutheliaAuth(w http.ResponseWriter, r *http.Request) error {
|
||||
client := &http.Client{}
|
||||
|
||||
protocol := "http"
|
||||
if h.options.UseHTTPS {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
autheliaBaseURL := protocol + "://" + h.options.AutheliaURL
|
||||
//Remove tailing slash if any
|
||||
if autheliaBaseURL[len(autheliaBaseURL)-1] == '/' {
|
||||
autheliaBaseURL = autheliaBaseURL[:len(autheliaBaseURL)-1]
|
||||
}
|
||||
|
||||
//Make a request to Authelia to verify the request
|
||||
req, err := http.NewRequest("POST", autheliaBaseURL+"/api/verify", nil)
|
||||
if err != nil {
|
||||
h.options.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)
|
||||
}
|
||||
|
||||
// Making the verification request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
h.options.Logger.PrintAndLog("Authelia", "Unable to verify", err)
|
||||
w.WriteHeader(401)
|
||||
return errors.New("unauthorized")
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
redirectURL := autheliaBaseURL + "/?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
|
||||
}
|
@@ -41,8 +41,8 @@ func NewDB(path string) (*DB, error) {
|
||||
batch: leveldb.Batch{},
|
||||
}
|
||||
|
||||
//Create a ticker to flush data into disk every 5 seconds
|
||||
writeFlushTicker := time.NewTicker(5 * time.Second)
|
||||
//Create a ticker to flush data into disk every 1 seconds
|
||||
writeFlushTicker := time.NewTicker(1 * time.Second)
|
||||
writeFlushStop := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
})
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
})
|
||||
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package update
|
||||
|
||||
import v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||
import (
|
||||
v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||
v315 "imuslab.com/zoraxy/mod/update/v315"
|
||||
)
|
||||
|
||||
// Updater Core logic
|
||||
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||
@@ -10,6 +13,12 @@ func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if fromVersion == 314 && toVersion == 315 {
|
||||
//Updating from v3.1.4 to v3.1.5
|
||||
err := v315.UpdateFrom314To315()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
//ADD MORE VERSIONS HERE
|
||||
|
24
src/mod/update/updateutil/updateutil.go
Normal file
24
src/mod/update/updateutil/updateutil.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package updateutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Helper function to copy files
|
||||
func CopyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
return err
|
||||
}
|
50
src/mod/update/v315/typedef314.go
Normal file
50
src/mod/update/v315/typedef314.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package v315
|
||||
|
||||
import (
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
)
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type v314ProxyEndpoint struct {
|
||||
ProxyType int //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
|
||||
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
|
||||
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
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//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
|
||||
}
|
106
src/mod/update/v315/typedef315.go
Normal file
106
src/mod/update/v315/typedef315.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package v315
|
||||
|
||||
import (
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
)
|
||||
|
||||
type ProxyType int
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type AuthProvider int
|
||||
|
||||
const (
|
||||
AuthProviderNone AuthProvider = iota
|
||||
AuthProviderBasicAuth
|
||||
AuthProviderAuthelia
|
||||
AuthProviderOauth2
|
||||
)
|
||||
|
||||
type AuthenticationProvider struct {
|
||||
AuthProvider AuthProvider //The type of authentication provider
|
||||
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
|
||||
}
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type v315ProxyEndpoint 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
|
||||
|
||||
//Authentication
|
||||
AuthenticationProvider *AuthenticationProvider
|
||||
|
||||
// Rate Limiting
|
||||
RequireRateLimit bool
|
||||
RateLimit int64 // Rate limit in requests per second
|
||||
|
||||
//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
|
||||
}
|
124
src/mod/update/v315/v315.go
Normal file
124
src/mod/update/v315/v315.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package v315
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"imuslab.com/zoraxy/mod/update/updateutil"
|
||||
)
|
||||
|
||||
func UpdateFrom314To315() 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-314.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-314.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 := v314ProxyEndpoint{}
|
||||
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 := convertV314ToV315(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 convertV314ToV315(thisOldConfigStruct v314ProxyEndpoint) v315ProxyEndpoint {
|
||||
//Move old header and auth configs into struct
|
||||
newHeaderRewriteRules := HeaderRewriteRules{
|
||||
UserDefinedHeaders: thisOldConfigStruct.UserDefinedHeaders,
|
||||
RequestHostOverwrite: thisOldConfigStruct.RequestHostOverwrite,
|
||||
HSTSMaxAge: thisOldConfigStruct.HSTSMaxAge,
|
||||
EnablePermissionPolicyHeader: thisOldConfigStruct.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: thisOldConfigStruct.PermissionPolicy,
|
||||
DisableHopByHopHeaderRemoval: thisOldConfigStruct.DisableHopByHopHeaderRemoval,
|
||||
}
|
||||
|
||||
newAuthenticationProvider := AuthenticationProvider{
|
||||
RequireBasicAuth: thisOldConfigStruct.RequireBasicAuth,
|
||||
BasicAuthCredentials: thisOldConfigStruct.BasicAuthCredentials,
|
||||
BasicAuthExceptionRules: thisOldConfigStruct.BasicAuthExceptionRules,
|
||||
}
|
||||
|
||||
//Convert proxy type int to enum
|
||||
var newConfigProxyType ProxyType
|
||||
if thisOldConfigStruct.ProxyType == 0 {
|
||||
newConfigProxyType = ProxyTypeRoot
|
||||
} else if thisOldConfigStruct.ProxyType == 1 {
|
||||
newConfigProxyType = ProxyTypeHost
|
||||
} else if thisOldConfigStruct.ProxyType == 2 {
|
||||
newConfigProxyType = ProxyTypeVdir
|
||||
}
|
||||
|
||||
//Update the config struct
|
||||
thisNewConfigStruct := v315ProxyEndpoint{
|
||||
ProxyType: newConfigProxyType,
|
||||
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,
|
||||
RequireRateLimit: thisOldConfigStruct.RequireRateLimit,
|
||||
RateLimit: thisOldConfigStruct.RateLimit,
|
||||
AccessFilterUUID: thisOldConfigStruct.AccessFilterUUID,
|
||||
DefaultSiteOption: thisOldConfigStruct.DefaultSiteOption,
|
||||
DefaultSiteValue: thisOldConfigStruct.DefaultSiteValue,
|
||||
|
||||
//Append the new struct into the new config
|
||||
HeaderRewriteRules: &newHeaderRewriteRules,
|
||||
AuthenticationProvider: &newAuthenticationProvider,
|
||||
}
|
||||
|
||||
return thisNewConfigStruct
|
||||
}
|
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
)
|
||||
|
||||
@@ -56,9 +57,11 @@ type WebsocketProxy struct {
|
||||
|
||||
// Additional options for websocket proxy runtime
|
||||
type Options struct {
|
||||
SkipTLSValidation bool //Skip backend TLS validation
|
||||
SkipOriginCheck bool //Skip origin check
|
||||
Logger *logger.Logger //Logger, can be nil
|
||||
SkipTLSValidation bool //Skip backend TLS validation
|
||||
SkipOriginCheck bool //Skip origin check
|
||||
CopyAllHeaders bool //Copy all headers from incoming request to backend request
|
||||
UserDefinedHeaders []*rewrite.UserDefinedHeader //User defined headers
|
||||
Logger *logger.Logger //Logger, can be nil
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
@@ -78,7 +81,14 @@ func NewProxy(target *url.URL, options Options) *WebsocketProxy {
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
|
||||
|
||||
// Create a new websocket proxy
|
||||
wsprox := &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
|
||||
if options.CopyAllHeaders {
|
||||
wsprox.Director = DefaultDirector
|
||||
}
|
||||
|
||||
return wsprox
|
||||
}
|
||||
|
||||
// Utilities function for log printing
|
||||
@@ -90,6 +100,35 @@ func (w *WebsocketProxy) Println(messsage string, err error) {
|
||||
log.Println("[websocketproxy] [system:info]"+messsage, err)
|
||||
}
|
||||
|
||||
// DefaultDirector is the default implementation of Director, which copies
|
||||
// all headers from the incoming request to the outgoing request.
|
||||
func DefaultDirector(r *http.Request, h http.Header) {
|
||||
//Copy all header values from request to target header
|
||||
for k, vv := range r.Header {
|
||||
for _, v := range vv {
|
||||
h.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
for _, removePendingHeader := range []string{
|
||||
"Connection",
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te",
|
||||
"Trailers",
|
||||
"Transfer-Encoding",
|
||||
"Sec-WebSocket-Extensions",
|
||||
"Sec-WebSocket-Key",
|
||||
"Sec-WebSocket-Protocol",
|
||||
"Sec-WebSocket-Version",
|
||||
"Upgrade",
|
||||
} {
|
||||
h.Del(removePendingHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if w.Backend == nil {
|
||||
@@ -162,6 +201,15 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
w.Director(req, requestHeader)
|
||||
}
|
||||
|
||||
// Replace header variables and copy user-defined headers
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(req, w.Options.UserDefinedHeaders)
|
||||
upstreamHeaders, _ := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||
})
|
||||
for _, headerValuePair := range upstreamHeaders {
|
||||
requestHeader.Set(headerValuePair[0], headerValuePair[1])
|
||||
}
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
// together with the Forwarded headers we prepared above.
|
||||
// TODO: support multiplexing on the same backend connection instead of
|
||||
|
Reference in New Issue
Block a user