diff --git a/src/api.go b/src/api.go index eb99977..203fa01 100644 --- a/src/api.go +++ b/src/api.go @@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) { authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail) authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint) authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias) + authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname) authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials) authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS) @@ -192,6 +193,7 @@ func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) { authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer) authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange) authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing) + authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface) /* File Manager */ if *allowWebFileManager { authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList) diff --git a/src/go.mod b/src/go.mod index 3b0d5e4..804e62a 100644 --- a/src/go.mod +++ b/src/go.mod @@ -18,33 +18,16 @@ require ( github.com/microcosm-cc/bluemonday v1.0.26 github.com/monperrus/crawler-user-agents v1.1.0 github.com/shirou/gopsutil/v4 v4.25.1 + github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 golang.org/x/net v0.33.0 + golang.org/x/oauth2 v0.24.0 golang.org/x/text v0.21.0 ) require ( cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect - github.com/ebitengine/purego v0.8.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/snappy v0.0.1 // indirect - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect - github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect - github.com/peterhellberg/link v1.2.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/tjfoc/gmsm v1.4.1 // indirect - github.com/vultr/govultr/v3 v3.9.1 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.mongodb.org/mongo-driver v1.12.0 // indirect - golang.org/x/sys v0.28.0 // indirect -) - -require ( cloud.google.com/go/compute/metadata v0.6.0 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect @@ -53,6 +36,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect @@ -82,6 +66,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -94,6 +79,7 @@ require ( github.com/dnsimple/dnsimple-go v1.7.0 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.2 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -102,11 +88,14 @@ require ( github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-resty/resty/v2 v2.16.2 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect @@ -119,6 +108,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.128 // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -155,29 +145,36 @@ require ( github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/ovh/go-ovh v1.6.0 // indirect + github.com/peterhellberg/link v1.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/pquerna/otp v1.4.0 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/go-http v0.1.8 // indirect github.com/sacloud/iaas-api-go v1.14.0 // indirect github.com/sacloud/packages-go v0.0.10 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/softlayer/softlayer-go v1.1.7 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/stretchr/testify v1.10.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1065 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1065 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect github.com/transip/gotransip/v6 v6.26.0 // indirect github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect + github.com/vultr/govultr/v3 v3.9.1 // indirect github.com/yandex-cloud/go-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.mongodb.org/mongo-driver v1.12.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect @@ -187,8 +184,8 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.28.0 // indirect google.golang.org/api v0.214.0 // indirect diff --git a/src/main.go b/src/main.go index 18dd086..f577d50 100644 --- a/src/main.go +++ b/src/main.go @@ -37,6 +37,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "time" @@ -125,7 +126,13 @@ func main() { //Start the finalize sequences finalSequence() - SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort) + if strings.HasPrefix(*webUIPort, ":") { + //Bind to all interfaces, issue #672 + SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort) + } else { + SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://" + *webUIPort) + } + err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux)) if err != nil { diff --git a/src/mod/auth/sso/forward/const.go b/src/mod/auth/sso/forward/const.go index 164ef79..a4e935d 100644 --- a/src/mod/auth/sso/forward/const.go +++ b/src/mod/auth/sso/forward/const.go @@ -11,6 +11,7 @@ const ( DatabaseKeyResponseHeaders = "responseHeaders" DatabaseKeyResponseClientHeaders = "responseClientHeaders" DatabaseKeyRequestHeaders = "requestHeaders" + DatabaseKeyRequestIncludedCookies = "requestIncludedCookies" DatabaseKeyRequestExcludedCookies = "requestExcludedCookies" HeaderXForwardedProto = "X-Forwarded-Proto" diff --git a/src/mod/auth/sso/forward/forward.go b/src/mod/auth/sso/forward/forward.go index 16a84c9..44a2abb 100644 --- a/src/mod/auth/sso/forward/forward.go +++ b/src/mod/auth/sso/forward/forward.go @@ -3,7 +3,6 @@ package forward import ( "encoding/json" "io" - "net" "net/http" "strings" @@ -28,6 +27,10 @@ type AuthRouterOptions struct { // headers are copied. RequestHeaders []string + // RequestIncludedCookies is a list of cookie keys that if defined will be the only cookies sent in the request to + // the authorization server. + RequestIncludedCookies []string + // RequestExcludedCookies is a list of cookie keys that should be removed from every request sent to the upstream. RequestExcludedCookies []string @@ -47,16 +50,18 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter { //Read settings from database if available. options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address) - responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", "" + responseHeaders, responseClientHeaders, requestHeaders, requestIncludedCookies, requestExcludedCookies := "", "", "", "", "" options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders) options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders) options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders) + options.Database.Read(DatabaseTable, DatabaseKeyRequestIncludedCookies, &requestIncludedCookies) options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies) options.ResponseHeaders = strings.Split(responseHeaders, ",") options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",") options.RequestHeaders = strings.Split(requestHeaders, ",") + options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",") options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") return &AuthRouter{ @@ -82,11 +87,12 @@ func (ar *AuthRouter) HandleAPIOptions(w http.ResponseWriter, r *http.Request) { } func (ar *AuthRouter) handleOptionsGET(w http.ResponseWriter, r *http.Request) { - js, _ := json.Marshal(map[string]interface{}{ + js, _ := json.Marshal(map[string]any{ DatabaseKeyAddress: ar.options.Address, DatabaseKeyResponseHeaders: ar.options.ResponseHeaders, DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders, DatabaseKeyRequestHeaders: ar.options.RequestHeaders, + DatabaseKeyRequestIncludedCookies: ar.options.RequestIncludedCookies, DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies, }) @@ -108,6 +114,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request) responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders) responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders) requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders) + requestIncludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestIncludedCookies) requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies) // Write changes to runtime @@ -115,6 +122,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request) ar.options.ResponseHeaders = strings.Split(responseHeaders, ",") ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",") ar.options.RequestHeaders = strings.Split(requestHeaders, ",") + ar.options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",") ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") // Write changes to database @@ -122,6 +130,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request) 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, DatabaseKeyRequestIncludedCookies, requestIncludedCookies) ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies) utils.SendOK(w) @@ -136,60 +145,40 @@ func (ar *AuthRouter) handleOptionsMethodNotAllowed(w http.ResponseWriter, r *ht // HandleAuthProviderRouting is the internal handler for Forward Auth authentication. func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error { if ar.options.Address == "" { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - - ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil) - - return ErrInternalServerError + return ar.handle500Error(w, nil, "Address not set") } // Make a request to Authz Server to verify the request + // TODO: Add opt-in support for copying the request body to the forward auth request. Currently it's just an + // empty body which is usually fine in most instances. It's likely best to see if anyone wants this feature + // as I'm unaware of any specific forward auth implementation that needs it. req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil) if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - - ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err) - - return ErrInternalServerError + return ar.handle500Error(w, err, "Unable to create request") } - // TODO: Add opt-in support for copying the request body to the forward auth request. headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true) + headerCookieRedact(r, ar.options.RequestIncludedCookies, false) - // TODO: Add support for upstream headers. + // TODO: Add support for headers from upstream proxies. This will likely involve implementing some form of + // proxy specific trust system within Zoraxy. rSetForwardedHeaders(r, req) // Make the Authz Request. respForwarded, err := ar.client.Do(req) if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - - ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err) - - return ErrInternalServerError + return ar.handle500Error(w, err, "Unable to perform forwarded auth due to a request error") } defer respForwarded.Body.Close() - body, err := io.ReadAll(respForwarded.Body) - if err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - - ar.options.Logger.PrintAndLog(LogTitle, "Unable to read response to forward auth request", err) - - return ErrInternalServerError - } - // 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 len(ar.options.ResponseClientHeaders) != 0 { headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false) } - if len(ar.options.RequestExcludedCookies) != 0 { - // If the user has specified a list of cookies to be removed from the request, deterministically remove them. - headerCookieRedact(r, ar.options.RequestExcludedCookies) - } + headerCookieRedact(r, ar.options.RequestExcludedCookies, true) if len(ar.options.ResponseHeaders) != 0 { // Copy specific user-specified headers from the response of the forward auth request to the request sent to the @@ -197,138 +186,32 @@ func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.R headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false) } + // Return the request to the proxy for forwarding to the backend. return nil } - // Copy the response. + // Copy the unsuccessful response. headerCopyExcluded(respForwarded.Header, w.Header(), nil) w.WriteHeader(respForwarded.StatusCode) + + body, err := io.ReadAll(respForwarded.Body) + if err != nil { + return ar.handle500Error(w, err, "Unable to read response to forward auth request") + } + if _, err = w.Write(body); err != nil { - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - - ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err) - - return ErrInternalServerError + return ar.handle500Error(w, err, "Unable to write response") } return ErrUnauthorized } -func scheme(r *http.Request) string { - if r.TLS != nil { - return "https" - } +// handle500Error is func intended on factorizing a commonly repeated functional flow within this provider. +func (ar *AuthRouter) handle500Error(w http.ResponseWriter, err error, message string) error { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) - return "http" -} - -func headerCookieRedact(r *http.Request, excluded []string) { - original := r.Cookies() - - if len(original) == 0 { - return - } - - var cookies []string - - for _, cookie := range original { - if stringInSlice(cookie.Name, excluded) { - continue - } - - cookies = append(cookies, cookie.String()) - } - - r.Header.Set(HeaderCookie, strings.Join(cookies, "; ")) -} - -func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) { - for key, values := range original { - // We should never copy the headers in the below list. - if stringInSliceFold(key, doNotCopyHeaders) { - continue - } - - if stringInSliceFold(key, excludedHeaders) { - continue - } - - destination[key] = append(destination[key], values...) - } -} - -func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) { - if allIfEmpty && len(includedHeaders) == 0 { - headerCopyAll(original, destination) - } else { - headerCopyIncludedExact(original, destination, includedHeaders) - } -} - -func headerCopyAll(original, destination http.Header) { - for key, values := range original { - // We should never copy the headers in the below list, even if they're in the list provided by a user. - if stringInSliceFold(key, doNotCopyHeaders) { - continue - } - - 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 - } - - if values, ok := original[key]; ok { - destination[key] = append(destination[key], values...) - } - } -} - -func stringInSlice(needle string, haystack []string) bool { - if len(haystack) == 0 { - return false - } - - for _, v := range haystack { - if needle == v { - return true - } - } - - return false -} - -func stringInSliceFold(needle string, haystack []string) bool { - if len(haystack) == 0 { - return false - } - - for _, v := range haystack { - if strings.EqualFold(needle, v) { - return true - } - } - - return false -} - -func rSetForwardedHeaders(r, req *http.Request) { - if r.RemoteAddr != "" { - before, _, _ := strings.Cut(r.RemoteAddr, ":") - - if ip := net.ParseIP(before); ip != nil { - req.Header.Set(HeaderXForwardedFor, ip.String()) - } - } - - req.Header.Set(HeaderXForwardedMethod, r.Method) - req.Header.Set(HeaderXForwardedProto, scheme(r)) - req.Header.Set(HeaderXForwardedHost, r.Host) - req.Header.Set(HeaderXForwardedURI, r.URL.Path) + ar.options.Logger.PrintAndLog(LogTitle, message, err) + + return ErrInternalServerError } diff --git a/src/mod/auth/sso/forward/util.go b/src/mod/auth/sso/forward/util.go new file mode 100644 index 0000000..7ba9b3a --- /dev/null +++ b/src/mod/auth/sso/forward/util.go @@ -0,0 +1,137 @@ +package forward + +import ( + "net" + "net/http" + "strings" +) + +func scheme(r *http.Request) string { + if r.TLS != nil { + return "https" + } + + return "http" +} + +func headerCookieRedact(r *http.Request, names []string, exclude bool) { + if len(names) == 0 { + return + } + + original := r.Cookies() + + if len(original) == 0 { + return + } + + var cookies []string + + for _, cookie := range original { + if exclude && stringInSlice(cookie.Name, names) { + continue + } else if !exclude && !stringInSlice(cookie.Name, names) { + continue + } + + cookies = append(cookies, cookie.String()) + } + + value := strings.Join(cookies, "; ") + + r.Header.Set(HeaderCookie, value) + + return +} + +func headerCopyExcluded(original, destination http.Header, excludedHeaders []string) { + for key, values := range original { + // We should never copy the headers in the below list. + if stringInSliceFold(key, doNotCopyHeaders) { + continue + } + + if stringInSliceFold(key, excludedHeaders) { + continue + } + + destination[key] = append(destination[key], values...) + } +} + +func headerCopyIncluded(original, destination http.Header, includedHeaders []string, allIfEmpty bool) { + if allIfEmpty && len(includedHeaders) == 0 { + headerCopyAll(original, destination) + } else { + headerCopyIncludedExact(original, destination, includedHeaders) + } +} + +func headerCopyAll(original, destination http.Header) { + for key, values := range original { + // We should never copy the headers in the below list, even if they're in the list provided by a user. + if stringInSliceFold(key, doNotCopyHeaders) { + continue + } + + destination[key] = append(destination[key], values...) + } +} + +func headerCopyIncludedExact(original, destination http.Header, keys []string) { + for key, values := range original { + // We should never copy the headers in the below list, even if they're in the list provided by a user. + if stringInSliceFold(key, doNotCopyHeaders) { + continue + } + + if !stringInSliceFold(key, keys) { + continue + } + + destination[key] = append(destination[key], values...) + } +} + +func stringInSlice(needle string, haystack []string) bool { + if len(haystack) == 0 { + return false + } + + for _, v := range haystack { + if needle == v { + return true + } + } + + return false +} + +func stringInSliceFold(needle string, haystack []string) bool { + if len(haystack) == 0 { + return false + } + + for _, v := range haystack { + if strings.EqualFold(needle, v) { + return true + } + } + + return false +} + +func rSetForwardedHeaders(r, req *http.Request) { + if r.RemoteAddr != "" { + before, _, _ := strings.Cut(r.RemoteAddr, ":") + + if ip := net.ParseIP(before); ip != nil { + req.Header.Set(HeaderXForwardedFor, ip.String()) + } + } + + req.Header.Set(HeaderXForwardedMethod, r.Method) + req.Header.Set(HeaderXForwardedProto, scheme(r)) + req.Header.Set(HeaderXForwardedHost, r.Host) + req.Header.Set(HeaderXForwardedURI, r.URL.Path) +} diff --git a/src/mod/auth/sso/forward/util_test.go b/src/mod/auth/sso/forward/util_test.go new file mode 100644 index 0000000..abd0241 --- /dev/null +++ b/src/mod/auth/sso/forward/util_test.go @@ -0,0 +1,217 @@ +package forward + +import ( + "crypto/tls" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestScheme(t *testing.T) { + testCases := []struct { + name string + have *http.Request + expected string + }{ + { + "ShouldHandleDefault", + &http.Request{}, + "http", + }, + { + "ShouldHandleExplicit", + &http.Request{ + TLS: nil, + }, + "http", + }, + { + "ShouldHandleHTTPS", + &http.Request{ + TLS: &tls.ConnectionState{}, + }, + "https", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, scheme(tc.have)) + }) + } +} + +func TestHeaderCookieRedact(t *testing.T) { + testCases := []struct { + name string + have string + names []string + expectedInclude string + expectedExclude string + }{ + { + "ShouldHandleIncludeEmptyWithoutSettings", + "", + nil, + "", + "", + }, + { + "ShouldHandleIncludeEmptyWithSettings", + "", + []string{"include"}, + "", + "", + }, + { + "ShouldHandleValueWithoutSettings", + "include=value; exclude=value", + nil, + "include=value; exclude=value", + "include=value; exclude=value", + }, + { + "ShouldHandleValueWithSettings", + "include=value; exclude=value", + []string{"include"}, + "include=value", + "exclude=value", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var include, exclude *http.Request + + include, exclude = &http.Request{Header: http.Header{}}, &http.Request{Header: http.Header{}} + + if tc.have != "" { + include.Header.Set(HeaderCookie, tc.have) + exclude.Header.Set(HeaderCookie, tc.have) + } + + headerCookieRedact(include, tc.names, false) + + assert.Equal(t, tc.expectedInclude, include.Header.Get(HeaderCookie)) + + headerCookieRedact(exclude, tc.names, true) + + assert.Equal(t, tc.expectedExclude, exclude.Header.Get(HeaderCookie)) + }) + } +} + +func TestHeaderCopyExcluded(t *testing.T) { + testCases := []struct { + name string + original http.Header + excluded []string + expected http.Header + }{ + { + "ShouldHandleNoSettingsNoHeaders", + http.Header{}, + nil, + http.Header{}, + }, + { + "ShouldHandleNoSettingsWithHeaders", + http.Header{ + "Example": []string{"value", "other"}, + "Exclude": []string{"value", "other"}, + HeaderUpgrade: []string{"do", "not", "copy"}, + }, + nil, + http.Header{ + "Example": []string{"value", "other"}, + "Exclude": []string{"value", "other"}, + }, + }, + { + "ShouldHandleSettingsWithHeaders", + http.Header{ + "Example": []string{"value", "other"}, + "Exclude": []string{"value", "other"}, + HeaderUpgrade: []string{"do", "not", "copy"}, + }, + []string{"exclude"}, + http.Header{ + "Example": []string{"value", "other"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + headers := http.Header{} + + headerCopyExcluded(tc.original, headers, tc.excluded) + + assert.Equal(t, tc.expected, headers) + }) + } +} + +func TestHeaderCopyIncluded(t *testing.T) { + testCases := []struct { + name string + original http.Header + included []string + expected http.Header + expectedAll http.Header + }{ + { + "ShouldHandleNoSettingsNoHeaders", + http.Header{}, + nil, + http.Header{}, + http.Header{}, + }, + { + "ShouldHandleNoSettingsWithHeaders", + http.Header{ + "Example": []string{"value", "other"}, + "Include": []string{"value", "other"}, + HeaderUpgrade: []string{"do", "not", "copy"}, + }, + nil, + http.Header{}, + http.Header{ + "Example": []string{"value", "other"}, + "Include": []string{"value", "other"}, + }, + }, + { + "ShouldHandleSettingsWithHeaders", + http.Header{ + "Example": []string{"value", "other"}, + "Include": []string{"value", "other"}, + HeaderUpgrade: []string{"do", "not", "copy"}, + }, + []string{"include"}, + http.Header{ + "Include": []string{"value", "other"}, + }, + http.Header{ + "Include": []string{"value", "other"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + headers := http.Header{} + + headerCopyIncluded(tc.original, headers, tc.included, false) + + assert.Equal(t, tc.expected, headers) + + headers = http.Header{} + + headerCopyIncluded(tc.original, headers, tc.included, true) + + assert.Equal(t, tc.expectedAll, headers) + }) + } +} diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index eb0a489..4566769 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -70,8 +70,9 @@ type ResponseRewriteRuleSet struct { DownstreamHeaders [][]string /* Advance Usecase Options */ - HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) - NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) + HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) + NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) + DisableChunkedTransferEncoding bool //Disable chunked transfer encoding /* System Information Payload */ Version string //Version number of Zoraxy, use for X-Proxy-By @@ -287,7 +288,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version) //Fix proxmox transfer encoding bug if detected Proxmox Cookie - if domainsniff.IsProxmox(req) { + if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) { outreq.TransferEncoding = []string{"identity"} } diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index 36e22df..9ba8813 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -11,7 +11,6 @@ import ( "sort" "strings" - "imuslab.com/zoraxy/mod/dynamicproxy/domainsniff" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/dynamicproxy/rewrite" "imuslab.com/zoraxy/mod/netutils" @@ -186,16 +185,17 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe //Handle the request reverse proxy statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: selectedUpstream.OriginIpOrDomain, - OriginalHost: reqHostname, - UseTLS: selectedUpstream.RequireTLS, - NoCache: h.Parent.Option.NoCache, - PathPrefix: "", - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, - NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval, - Version: target.parent.Option.HostVersion, + ProxyDomain: selectedUpstream.OriginIpOrDomain, + OriginalHost: reqHostname, + UseTLS: selectedUpstream.RequireTLS, + NoCache: h.Parent.Option.NoCache, + PathPrefix: "", + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding, + HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, + NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval, + Version: target.parent.Option.HostVersion, }) //validate the error @@ -244,8 +244,8 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain) wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ SkipTLSValidation: target.SkipCertValidations, - SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility - CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir + SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility + CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders, Logger: h.Parent.Option.Logger, }) @@ -280,14 +280,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe //Handle the virtual directory reverse proxy request statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ - ProxyDomain: target.Domain, - OriginalHost: reqHostname, - UseTLS: target.RequireTLS, - PathPrefix: target.MatchingPath, - UpstreamHeaders: upstreamHeaders, - DownstreamHeaders: downstreamHeaders, - HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, - Version: target.parent.parent.Option.HostVersion, + ProxyDomain: target.Domain, + OriginalHost: reqHostname, + UseTLS: target.RequireTLS, + PathPrefix: target.MatchingPath, + UpstreamHeaders: upstreamHeaders, + DownstreamHeaders: downstreamHeaders, + DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding, + HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, + Version: target.parent.parent.Option.HostVersion, }) var dnsError *net.DNSError diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index 36eb39e..7a5b334 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -194,6 +194,9 @@ type ProxyEndpoint struct { //Uptime Monitor DisableUptimeMonitor bool //Disable uptime monitor for this endpoint + // Chunked Transfer Encoding + DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint + //Access Control AccessFilterUUID string //Access filter ID diff --git a/src/mod/webserv/handler.go b/src/mod/webserv/handler.go index 74fef4f..e040b1f 100644 --- a/src/mod/webserv/handler.go +++ b/src/mod/webserv/handler.go @@ -17,22 +17,24 @@ import ( */ type StaticWebServerStatus struct { - ListeningPort int - EnableDirectoryListing bool - WebRoot string - Running bool - EnableWebDirManager bool + ListeningPort int + EnableDirectoryListing bool + WebRoot string + Running bool + EnableWebDirManager bool + DisableListenToAllInterface bool } // Handle getting current static web server status func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) { listeningPortInt, _ := strconv.Atoi(ws.option.Port) currentStatus := StaticWebServerStatus{ - ListeningPort: listeningPortInt, - EnableDirectoryListing: ws.option.EnableDirectoryListing, - WebRoot: ws.option.WebRoot, - Running: ws.isRunning, - EnableWebDirManager: ws.option.EnableWebDirManager, + ListeningPort: listeningPortInt, + EnableDirectoryListing: ws.option.EnableDirectoryListing, + WebRoot: ws.option.WebRoot, + Running: ws.isRunning, + EnableWebDirManager: ws.option.EnableWebDirManager, + DisableListenToAllInterface: ws.option.DisableListenToAllInterface, } js, _ := json.Marshal(currentStatus) @@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re ws.option.EnableDirectoryListing = enableList utils.SendOK(w) } + +// Get or set disable listen to all interface settings +func (ws *WebServer) SetDisableListenToAllInterface(w http.ResponseWriter, r *http.Request) { + disableListen, err := utils.PostBool(r, "disable") + if err != nil { + utils.SendErrorResponse(w, "invalid setting given") + return + } + err = ws.option.Sysdb.Write("webserv", "disableListenToAllInterface", disableListen) + if err != nil { + utils.SendErrorResponse(w, "unable to save setting") + return + } + ws.option.DisableListenToAllInterface = disableListen + utils.SendOK(w) +} diff --git a/src/mod/webserv/webserv.go b/src/mod/webserv/webserv.go index efb8dc9..85214e4 100644 --- a/src/mod/webserv/webserv.go +++ b/src/mod/webserv/webserv.go @@ -25,13 +25,21 @@ import ( //go:embed templates/* var templates embed.FS +/* +WebServerOptions define the default option for the webserv +might get override by user settings loaded from db + +Any changes in here might need to also update the StaticWebServerStatus struct +in handler.go. See handler.go for more information. +*/ type WebServerOptions struct { - Port string //Port for listening - EnableDirectoryListing bool //Enable listing of directory - WebRoot string //Folder for stroing the static web folders - EnableWebDirManager bool //Enable web file manager to handle files in web directory - Logger *logger.Logger //System logger - Sysdb *database.Database //Database for storing configs + Port string //Port for listening + EnableDirectoryListing bool //Enable listing of directory + WebRoot string //Folder for stroing the static web folders + EnableWebDirManager bool //Enable web file manager to handle files in web directory + DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost + Logger *logger.Logger //System logger + Sysdb *database.Database //Database for storing configs } type WebServer struct { @@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() { ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList) ws.option.EnableDirectoryListing = enableDirList + //Set disable listen to all interface + disableListenToAll := ws.option.DisableListenToAllInterface + ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll) + ws.option.DisableListenToAllInterface = disableListenToAll + //Check the running state webservRunning := true ws.option.Sysdb.Read("webserv", "enabled", &webservRunning) @@ -156,8 +169,12 @@ func (ws *WebServer) Start() error { fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html"))) ws.mux.Handle("/", ws.fsMiddleware(fs)) + listenAddr := ":" + ws.option.Port + if ws.option.DisableListenToAllInterface { + listenAddr = "127.0.0.1:" + ws.option.Port + } ws.server = &http.Server{ - Addr: ":" + ws.option.Port, + Addr: listenAddr, Handler: ws.mux, } diff --git a/src/reverseproxy.go b/src/reverseproxy.go index abb4e79..aaccaaf 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -556,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { proxyRateLimit = 1000 } + // Disable chunked Encoding + disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc") + //Load the previous basic auth credentials from current proxy rules targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) if err != nil { @@ -596,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.UseStickySession = useStickySession newProxyEndpoint.DisableUptimeMonitor = disbleUtm + newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding newProxyEndpoint.Tags = tags //Prepare to replace the current routing rule @@ -673,6 +677,83 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) { utils.SendOK(w) } +func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + utils.SendErrorResponse(w, "Method not supported") + return + } + + originalRootnameOrMatchingDomain, err := utils.PostPara(r, "oldHostname") + if err != nil { + utils.SendErrorResponse(w, "Invalid original hostname given") + return + } + + newHostname, err := utils.PostPara(r, "newHostname") + if err != nil { + utils.SendErrorResponse(w, "Invalid new hostname given") + return + } + + originalRootnameOrMatchingDomain = strings.TrimSpace(originalRootnameOrMatchingDomain) + newHostname = strings.TrimSpace(newHostname) + if newHostname == "/" { + //Reserevd, reutrn error + utils.SendErrorResponse(w, "Invalid new hostname: system reserved path") + return + } + + //Check if the endpoint already exists + _, err = dynamicProxyRouter.LoadProxy(newHostname) + if err == nil { + //Endpoint already exists, return error + utils.SendErrorResponse(w, "Endpoint with this hostname already exists") + return + } + + //Clone, edit the endpoint and remove the original one + ept, err := dynamicProxyRouter.LoadProxy(originalRootnameOrMatchingDomain) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + newEndpoint := ept.Clone() + newEndpoint.RootOrMatchingDomain = newHostname + + //Prepare to replace the current routing rule + readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newEndpoint) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the old endpoint from runtime + err = dynamicProxyRouter.RemoveProxyEndpointByRootname(originalRootnameOrMatchingDomain) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Remove the config from file + err = RemoveReverseProxyConfig(originalRootnameOrMatchingDomain) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + //Add the new endpoint to runtime + dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule) + + //Save it to file + SaveReverseProxyConfig(newEndpoint) + + //Update uptime monitor targets + UpdateUptimeMonitorTargets() + + utils.SendOK(w) +} + func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) { ep, err := utils.PostPara(r, "ep") if err != nil { diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 484eb37..1809e1a 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -9,7 +9,7 @@ } .subdEntry td:not(.ignoremw){ - min-width: 200px; + min-width: 100px; } .httpProxyListTools{ @@ -20,10 +20,150 @@ cursor: pointer; } + th.no-sort{ + cursor: default !important; + } + .tag-select:hover{ text-decoration: underline; opacity: 0.8; } + + .inlineEditActionBtn{ + border: 0px solid transparent !important; + box-shadow: none !important; + background-color: transparent !important; + } + + body.darkTheme .ui.basic.small.icon.circular.button.inlineEditActionBtn{ + border: 0px solid transparent !important; + } + + /* Custom, non overlaying modal for proxy rule editing */ + #httprpEditModal{ + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 68vw; + height: 75vh; + background-color: var(--theme_bg_primary); + padding: 1.4em; + border-radius: .6em; + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2), 0px 8px 16px rgba(0, 0, 0, 0.2); + z-index: 8; + max-width: 840px; + } + + #httprpEditModal .rpconfig_content{ + height: 100%; + } + + #httprpEditModal .editor_side_wrapper{ + position: absolute; + top: 0; + right: 0; + height:100%; + width: 100%; + background-color: var(--theme_bg_primary); + } + + #httprpEditModal .wrapper_frame{ + width: 100%; + border: 1px solid var(--divider_color); + border-radius: 0.5em; + height: calc(100%); + } + + #httprpEditModal .editor_side_wrapper .wrapper_frame{ + height: calc(100% - 30px); + } + + #httprpEditModal .editor_back_button { + border: none; + background-color: transparent; + box-shadow: none; + color: var(--text_color); + font-size: 2.2em; + cursor: pointer; + padding-top: 20px; + } + + #httprpEditModal .editor_back_button:hover{ + opacity: 0.5; + } + + @media screen and (max-width: 1024px) { + #httprpEditModal { + width: 85vw; + } + } + + @media screen and (max-width: 768px) { + #httprpEditModal { + height: 80vh; + border-radius: 0; + overflow-y: scroll; + overflow-x: hidden; + width: 100%; + } + + /* Override the ui grid */ + #httprpEditModalSideMenu{ + width: 16% !important; + } + + #httprpEditModalContentWindow{ + width: 84% !important; + } + + #httprpEditModal{ + padding-left: 0; + padding-right: 0; + } + + .editorSideMenuText{ + display:none; + } + + .hrpedit_menu_item{ + height: 2.4em; + } + + .httpProxyEditClosePC{ + display:none !important; + } + .httpProxyEditCloseMobile{ + display:block !important; + position: absolute; + left: 1.25em !important; + bottom: 1em; + } + } + + @media screen and (min-width: 769px) { + .httpProxyEditClosePC{ + display:block !important; + } + .httpProxyEditCloseMobile{ + display:none !important; + } + } + + #httprpEditDarkenLayer { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 7; + backdrop-filter: blur(3px); + } + + #httprpEditModalWrapper { + display: none; /* Hidden by default */ + }
- +
+ - - - + + @@ -76,7 +216,273 @@

+ +
+
+
+ +
+
+ +
+
+

+ + +

+
+ + + +
+
+ +
+
+
+
+ + +
+
+ +
+
+ +
+
+ Enabled Upstreams +
+ +
+ +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ +
+
+ List of Virtual Directories +
+ +
+
+ +
+
+ +
+ +
+ +
+
+

Work In Progress
+ Please use the outer-most menu TLS / SSL tab for now.

+
+ +
+
+ +
+ +
+ +
+ +
+ +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+ +
+ + +

+
+ + +
+
+
+ +
+ +
+
+ + +
+
+
+
+
+ + diff --git a/src/web/components/rproot.html b/src/web/components/rproot.html index 0c39b43..c8f22d8 100644 --- a/src/web/components/rproot.html +++ b/src/web/components/rproot.html @@ -1,7 +1,7 @@

Default Site

-

Default routing options for inbound traffic (previously called Proxy Root)

+

Default routing options for inbound traffic

@@ -209,14 +209,13 @@ }) } - //Set the new proxy root option + //Set the new proxy root (aka default site) option function setProxyRoot(btn=undefined){ var newpr = $("#proxyRoot").val(); if (newpr.trim() == "" && currentDefaultSiteOption == 0){ //Fill in the web server info newpr = "127.0.0.1:" + $("#webserv_listenPort").val(); $("#proxyRoot").val(newpr); - } var rootReqTls = $("#rootReqTLS")[0].checked; diff --git a/src/web/components/sso.html b/src/web/components/sso.html index 605ccdd..d0cadd6 100644 --- a/src/web/components/sso.html +++ b/src/web/components/sso.html @@ -25,6 +25,7 @@
@@ -42,26 +43,43 @@
- Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied.
- Example: Remote-User,Remote-Groups,Remote-Email,Remote-Name
+ + Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied.
+ Example: Remote-User,Remote-Groups,Remote-Email,Remote-Name +
- Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied.
- Example: Set-Cookie,WWW-Authenticate
+ + Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied.
+ Example: Set-Cookie,WWW-Authenticate +
- Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied.
- Example: Cookie,Authorization
+ + Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied.
+ Recommendation: Generally it's recommended to leave this blank or use the below example for predictable results.
+ Example: Accept,X-Requested-With,Cookie,Authorization,Proxy-Authorization +
+
+
+ + + + Comma separated list of case-sensitive cookie names to copy from the original request to the request made to the authorization server. If not set all cookies are included. This allows omitting all cookies not required by the authorization server.
+ Example: authelia_session,another_session +
- Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded.
- Example: authelia_session,another_session
+ + Comma separated list of case-sensitive cookie names to exclude from the request made to the backend application. If not set no cookies are excluded. This allows omitting the cookie intended only for the authorization server.
+ Example: authelia_session,another_session +
@@ -132,6 +150,7 @@ $('#forwardAuthResponseHeaders').val(data.responseHeaders.join(",")); $('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(",")); $('#forwardAuthRequestHeaders').val(data.requestHeaders.join(",")); + $('#forwardAuthRequestIncludedCookies').val(data.requestIncludedCookies.join(",")); $('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(",")); }, error: function(jqXHR, textStatus, errorThrown) { @@ -170,6 +189,7 @@ const responseHeaders = $('#forwardAuthResponseHeaders').val(); const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val(); const requestHeaders = $('#forwardAuthRequestHeaders').val(); + const requestIncludedCookies = $('#forwardAuthRequestIncludedCookies').val(); const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').val(); console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`); diff --git a/src/web/components/webserv.html b/src/web/components/webserv.html index 8d099b6..31e98bd 100644 --- a/src/web/components/webserv.html +++ b/src/web/components/webserv.html @@ -29,6 +29,13 @@ If this folder do not contains any index files, list the directory of this folder.
+
+
+ + + When disabled, the web server will only listen to localhost (127.0.0.1) and only reachable via reverse proxy rules. +
+
@@ -136,6 +143,13 @@ $("#webserv_dirManager").remove(); } + if (!data.DisableListenToAllInterface){ + //Options on UI is flipped + $("#webserv_enableAllInterfaces").parent().checkbox("set checked"); + }else{ + $("#webserv_enableAllInterfaces").parent().checkbox("set unchecked"); + } + $("#webserv_listenPort").val(data.ListeningPort); updateWebServLinkExample(data.ListeningPort); @@ -178,6 +192,23 @@ } }) }); + + $("#webserv_enableAllInterfaces").off("change").on("change", function(){ + let disable = !$(this)[0].checked; + $.cjax({ + url: "/api/webserv/disableListenAllInterface", + method: "POST", + data: {"disable": disable}, + success: function(data){ + if (data.error != undefined){ + msgbox(data.error, false); + }else{ + msgbox("Listening interface setting updated"); + } + } + }) + }); + $("#webserv_listenPort").off("change").on("change", function(){ let newPort = $(this).val(); diff --git a/src/web/darktheme.css b/src/web/darktheme.css index ca8bc75..f1b9925 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -184,7 +184,8 @@ body.darkTheme .ui.input input::placeholder { body.darkTheme .ui.label, body.darkTheme .ui.label .detail, body.darkTheme .ui.label .icon { - color: #ffffff !important; + background-color: var(--buttom_toggle_disabled); + color: var(--text_color) !important; } body.darkTheme .advanceoptions .title { diff --git a/src/web/index.html b/src/web/index.html index 27cd438..d87cb00 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -120,7 +120,7 @@
- +
diff --git a/src/web/snippet/aliasEditor.html b/src/web/snippet/aliasEditor.html index ec8b092..3da1bcf 100644 --- a/src/web/snippet/aliasEditor.html +++ b/src/web/snippet/aliasEditor.html @@ -25,7 +25,7 @@

Enter alias hostname or wildcard matching keywords for

-
Host Destination Virtual DirectoryTagsAdvanced SettingsActionsTags
+
@@ -161,7 +161,7 @@ } $("#inlineEditTable").append(` - + `); }); diff --git a/src/web/snippet/basicAuthEditor.html b/src/web/snippet/basicAuthEditor.html index 88be65b..6ac2f0a 100644 --- a/src/web/snippet/basicAuthEditor.html +++ b/src/web/snippet/basicAuthEditor.html @@ -14,18 +14,11 @@
-
-
- Basic Auth Settings -
-
-
-

Basic Auth Credential

Enter the username and password for allowing them to access this proxy endpoint

-
Alias Hostname
${domainLink}
+
@@ -56,7 +49,7 @@

Authentication Exclusion Paths

Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.

-
Username
+
@@ -86,10 +79,6 @@ /public/res/far/boo/

-
-
- -




@@ -232,7 +221,7 @@ data.forEach(function(rule){ $("#exclusionPaths").append(` - + `); }) } @@ -261,7 +250,7 @@ var row = '' + '' + '' + - '' + + '' + ''; tableBody.append(row); diff --git a/src/web/snippet/customHeaders.html b/src/web/snippet/customHeaders.html index fe2af1e..79df202 100644 --- a/src/web/snippet/customHeaders.html +++ b/src/web/snippet/customHeaders.html @@ -44,7 +44,7 @@ Security Headers
-
Path Prefix
${rule.PathPrefix}
' + username + '' + password + '
+
diff --git a/src/web/snippet/tagEditor.html b/src/web/snippet/tagEditor.html index d5801fb..d29f266 100644 --- a/src/web/snippet/tagEditor.html +++ b/src/web/snippet/tagEditor.html @@ -22,7 +22,7 @@

Tags currently applied to this host name / proxy rule

-
+
Key
@@ -155,6 +155,10 @@ function addSelectedTags() { let tags = $('#tagsInput').val().split(',').map(tag => tag.trim()); + if (tags.length == 0 || (tags.length == 1 && tags[0] == "")){ + parent.msgbox("Please enter at least one tag", false); + return; + } tags.forEach(tag => { if (tag && !tagAlreadyExistsInTable(tag)) { addTagRow(tag); @@ -201,8 +205,8 @@ const row = ``;
${tag}
-