Merge pull request #693 from tobychui/v3.2.3

- Added new HTTP proxy UI
- Added inbound host name edit function
- Merged SSO implementations
- Added disable chunked transfer encoding checkbox
This commit is contained in:
Toby Chui 2025-06-15 21:54:29 +08:00 committed by GitHub
commit eb07917c14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1505 additions and 549 deletions

View File

@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail) authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint) authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias) authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials) authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS) 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/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange) authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing) authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface)
/* File Manager */ /* File Manager */
if *allowWebFileManager { if *allowWebFileManager {
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList) authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)

View File

@ -18,33 +18,16 @@ require (
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/monperrus/crawler-user-agents v1.1.0 github.com/monperrus/crawler-user-agents v1.1.0
github.com/shirou/gopsutil/v4 v4.25.1 github.com/shirou/gopsutil/v4 v4.25.1
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
golang.org/x/net v0.33.0 golang.org/x/net v0.33.0
golang.org/x/oauth2 v0.24.0
golang.org/x/text v0.21.0 golang.org/x/text v0.21.0
) )
require ( require (
cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // 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 cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // 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/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/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/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 v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.22 // 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/aws-sdk-go-v2/service/sts v1.33.3 // indirect
github.com/aws/smithy-go v1.22.1 // indirect github.com/aws/smithy-go v1.22.1 // indirect
github.com/aymerick/douceur v0.2.0 // 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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // 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/dnsimple/dnsimple-go v1.7.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units 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/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // 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-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.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-resty/resty/v2 v2.16.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-json v0.10.4 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // 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/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // 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-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // 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/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect github.com/infobloxopen/infoblox-go-client v1.1.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // 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/nzdjb/go-metaname v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.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/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/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/pquerna/otp v1.4.0 // indirect
github.com/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect
github.com/sacloud/go-http v0.1.8 // indirect github.com/sacloud/go-http v0.1.8 // indirect
github.com/sacloud/iaas-api-go v1.14.0 // indirect github.com/sacloud/iaas-api-go v1.14.0 // indirect
github.com/sacloud/packages-go v0.0.10 // indirect github.com/sacloud/packages-go v0.0.10 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 // 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/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // 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/softlayer-go v1.1.7 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.6.0 // 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/common v1.0.1065 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod 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/transip/gotransip/v6 v6.26.0 // indirect
github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect
github.com/vinyldns/go-vinyldns v0.9.16 // 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-genproto v0.0.0-20241220122821-aeb3b05efd1c // indirect
github.com/yandex-cloud/go-sdk v0.0.0-20241220131134-2393e243c134 // 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/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.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 go.uber.org/ratelimit v0.3.0 // indirect
golang.org/x/crypto v0.31.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/mod v0.22.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/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.8.0 // indirect golang.org/x/time v0.8.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.28.0 // indirect
google.golang.org/api v0.214.0 // indirect google.golang.org/api v0.214.0 // indirect

View File

@ -37,6 +37,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
@ -125,7 +126,13 @@ func main() {
//Start the finalize sequences //Start the finalize sequences
finalSequence() finalSequence()
if strings.HasPrefix(*webUIPort, ":") {
//Bind to all interfaces, issue #672
SystemWideLogger.Println(SYSTEM_NAME + " started. Visit control panel at http://localhost" + *webUIPort) 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)) err = http.ListenAndServe(*webUIPort, csrfMiddleware(webminPanelMux))
if err != nil { if err != nil {

View File

@ -11,6 +11,7 @@ const (
DatabaseKeyResponseHeaders = "responseHeaders" DatabaseKeyResponseHeaders = "responseHeaders"
DatabaseKeyResponseClientHeaders = "responseClientHeaders" DatabaseKeyResponseClientHeaders = "responseClientHeaders"
DatabaseKeyRequestHeaders = "requestHeaders" DatabaseKeyRequestHeaders = "requestHeaders"
DatabaseKeyRequestIncludedCookies = "requestIncludedCookies"
DatabaseKeyRequestExcludedCookies = "requestExcludedCookies" DatabaseKeyRequestExcludedCookies = "requestExcludedCookies"
HeaderXForwardedProto = "X-Forwarded-Proto" HeaderXForwardedProto = "X-Forwarded-Proto"

View File

@ -3,7 +3,6 @@ package forward
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"net"
"net/http" "net/http"
"strings" "strings"
@ -28,6 +27,10 @@ type AuthRouterOptions struct {
// headers are copied. // headers are copied.
RequestHeaders []string 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 is a list of cookie keys that should be removed from every request sent to the upstream.
RequestExcludedCookies []string RequestExcludedCookies []string
@ -47,16 +50,18 @@ func NewAuthRouter(options *AuthRouterOptions) *AuthRouter {
//Read settings from database if available. //Read settings from database if available.
options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address) options.Database.Read(DatabaseTable, DatabaseKeyAddress, &options.Address)
responseHeaders, responseClientHeaders, requestHeaders, requestExcludedCookies := "", "", "", "" responseHeaders, responseClientHeaders, requestHeaders, requestIncludedCookies, requestExcludedCookies := "", "", "", "", ""
options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders) options.Database.Read(DatabaseTable, DatabaseKeyResponseHeaders, &responseHeaders)
options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders) options.Database.Read(DatabaseTable, DatabaseKeyResponseClientHeaders, &responseClientHeaders)
options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders) options.Database.Read(DatabaseTable, DatabaseKeyRequestHeaders, &requestHeaders)
options.Database.Read(DatabaseTable, DatabaseKeyRequestIncludedCookies, &requestIncludedCookies)
options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies) options.Database.Read(DatabaseTable, DatabaseKeyRequestExcludedCookies, &requestExcludedCookies)
options.ResponseHeaders = strings.Split(responseHeaders, ",") options.ResponseHeaders = strings.Split(responseHeaders, ",")
options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",") options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
options.RequestHeaders = strings.Split(requestHeaders, ",") options.RequestHeaders = strings.Split(requestHeaders, ",")
options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
return &AuthRouter{ 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) { 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, DatabaseKeyAddress: ar.options.Address,
DatabaseKeyResponseHeaders: ar.options.ResponseHeaders, DatabaseKeyResponseHeaders: ar.options.ResponseHeaders,
DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders, DatabaseKeyResponseClientHeaders: ar.options.ResponseClientHeaders,
DatabaseKeyRequestHeaders: ar.options.RequestHeaders, DatabaseKeyRequestHeaders: ar.options.RequestHeaders,
DatabaseKeyRequestIncludedCookies: ar.options.RequestIncludedCookies,
DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies, DatabaseKeyRequestExcludedCookies: ar.options.RequestExcludedCookies,
}) })
@ -108,6 +114,7 @@ func (ar *AuthRouter) handleOptionsPOST(w http.ResponseWriter, r *http.Request)
responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders) responseHeaders, _ := utils.PostPara(r, DatabaseKeyResponseHeaders)
responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders) responseClientHeaders, _ := utils.PostPara(r, DatabaseKeyResponseClientHeaders)
requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders) requestHeaders, _ := utils.PostPara(r, DatabaseKeyRequestHeaders)
requestIncludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestIncludedCookies)
requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies) requestExcludedCookies, _ := utils.PostPara(r, DatabaseKeyRequestExcludedCookies)
// Write changes to runtime // 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.ResponseHeaders = strings.Split(responseHeaders, ",")
ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",") ar.options.ResponseClientHeaders = strings.Split(responseClientHeaders, ",")
ar.options.RequestHeaders = strings.Split(requestHeaders, ",") ar.options.RequestHeaders = strings.Split(requestHeaders, ",")
ar.options.RequestIncludedCookies = strings.Split(requestIncludedCookies, ",")
ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",") ar.options.RequestExcludedCookies = strings.Split(requestExcludedCookies, ",")
// Write changes to database // 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, DatabaseKeyResponseHeaders, responseHeaders)
ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders) ar.options.Database.Write(DatabaseTable, DatabaseKeyResponseClientHeaders, responseClientHeaders)
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders) ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestHeaders, requestHeaders)
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestIncludedCookies, requestIncludedCookies)
ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies) ar.options.Database.Write(DatabaseTable, DatabaseKeyRequestExcludedCookies, requestExcludedCookies)
utils.SendOK(w) 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. // HandleAuthProviderRouting is the internal handler for Forward Auth authentication.
func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error { func (ar *AuthRouter) HandleAuthProviderRouting(w http.ResponseWriter, r *http.Request) error {
if ar.options.Address == "" { if ar.options.Address == "" {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return ar.handle500Error(w, nil, "Address not set")
ar.options.Logger.PrintAndLog(LogTitle, "Address not set", nil)
return ErrInternalServerError
} }
// Make a request to Authz Server to verify the request // 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) req, err := http.NewRequest(http.MethodGet, ar.options.Address, nil)
if err != nil { if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return ar.handle500Error(w, err, "Unable to create request")
ar.options.Logger.PrintAndLog(LogTitle, "Unable to create request", err)
return ErrInternalServerError
} }
// TODO: Add opt-in support for copying the request body to the forward auth request.
headerCopyIncluded(r.Header, req.Header, ar.options.RequestHeaders, true) 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) rSetForwardedHeaders(r, req)
// Make the Authz Request. // Make the Authz Request.
respForwarded, err := ar.client.Do(req) respForwarded, err := ar.client.Do(req)
if err != nil { if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return ar.handle500Error(w, err, "Unable to perform forwarded auth due to a request error")
ar.options.Logger.PrintAndLog(LogTitle, "Unable to perform forwarded auth due to a request error", err)
return ErrInternalServerError
} }
defer respForwarded.Body.Close() 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. // Responses within the 200-299 range are considered successful and allow the proxy to handle the request.
if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices { if respForwarded.StatusCode >= http.StatusOK && respForwarded.StatusCode < http.StatusMultipleChoices {
if len(ar.options.ResponseClientHeaders) != 0 { if len(ar.options.ResponseClientHeaders) != 0 {
headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false) headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseClientHeaders, false)
} }
if len(ar.options.RequestExcludedCookies) != 0 { headerCookieRedact(r, ar.options.RequestExcludedCookies, true)
// If the user has specified a list of cookies to be removed from the request, deterministically remove them.
headerCookieRedact(r, ar.options.RequestExcludedCookies)
}
if len(ar.options.ResponseHeaders) != 0 { 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 // 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) headerCopyIncluded(respForwarded.Header, w.Header(), ar.options.ResponseHeaders, false)
} }
// Return the request to the proxy for forwarding to the backend.
return nil return nil
} }
// Copy the response. // Copy the unsuccessful response.
headerCopyExcluded(respForwarded.Header, w.Header(), nil) headerCopyExcluded(respForwarded.Header, w.Header(), nil)
w.WriteHeader(respForwarded.StatusCode) 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 { if _, err = w.Write(body); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return ar.handle500Error(w, err, "Unable to write response")
ar.options.Logger.PrintAndLog(LogTitle, "Unable to write response", err)
return ErrInternalServerError
} }
return ErrUnauthorized return ErrUnauthorized
} }
func scheme(r *http.Request) string { // handle500Error is func intended on factorizing a commonly repeated functional flow within this provider.
if r.TLS != nil { func (ar *AuthRouter) handle500Error(w http.ResponseWriter, err error, message string) error {
return "https" http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return "http" ar.options.Logger.PrintAndLog(LogTitle, message, err)
}
return ErrInternalServerError
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)
} }

View File

@ -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)
}

View File

@ -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)
})
}
}

View File

@ -72,6 +72,7 @@ type ResponseRewriteRuleSet struct {
/* Advance Usecase Options */ /* Advance Usecase Options */
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding
/* System Information Payload */ /* System Information Payload */
Version string //Version number of Zoraxy, use for X-Proxy-By 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) rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
//Fix proxmox transfer encoding bug if detected Proxmox Cookie //Fix proxmox transfer encoding bug if detected Proxmox Cookie
if domainsniff.IsProxmox(req) { if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) {
outreq.TransferEncoding = []string{"identity"} outreq.TransferEncoding = []string{"identity"}
} }

View File

@ -11,7 +11,6 @@ import (
"sort" "sort"
"strings" "strings"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite" "imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/netutils"
@ -193,6 +192,7 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
PathPrefix: "", PathPrefix: "",
UpstreamHeaders: upstreamHeaders, UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders, DownstreamHeaders: downstreamHeaders,
DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding,
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval, NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
Version: target.parent.Option.HostVersion, Version: target.parent.Option.HostVersion,
@ -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) h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations, SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility SkipOriginCheck: true, //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 CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders, UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
Logger: h.Parent.Option.Logger, Logger: h.Parent.Option.Logger,
}) })
@ -286,6 +286,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
PathPrefix: target.MatchingPath, PathPrefix: target.MatchingPath,
UpstreamHeaders: upstreamHeaders, UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders, DownstreamHeaders: downstreamHeaders,
DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding,
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
Version: target.parent.parent.Option.HostVersion, Version: target.parent.parent.Option.HostVersion,
}) })

View File

@ -194,6 +194,9 @@ type ProxyEndpoint struct {
//Uptime Monitor //Uptime Monitor
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
// Chunked Transfer Encoding
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
//Access Control //Access Control
AccessFilterUUID string //Access filter ID AccessFilterUUID string //Access filter ID

View File

@ -22,6 +22,7 @@ type StaticWebServerStatus struct {
WebRoot string WebRoot string
Running bool Running bool
EnableWebDirManager bool EnableWebDirManager bool
DisableListenToAllInterface bool
} }
// Handle getting current static web server status // Handle getting current static web server status
@ -33,6 +34,7 @@ func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
WebRoot: ws.option.WebRoot, WebRoot: ws.option.WebRoot,
Running: ws.isRunning, Running: ws.isRunning,
EnableWebDirManager: ws.option.EnableWebDirManager, EnableWebDirManager: ws.option.EnableWebDirManager,
DisableListenToAllInterface: ws.option.DisableListenToAllInterface,
} }
js, _ := json.Marshal(currentStatus) js, _ := json.Marshal(currentStatus)
@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
ws.option.EnableDirectoryListing = enableList ws.option.EnableDirectoryListing = enableList
utils.SendOK(w) 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)
}

View File

@ -25,11 +25,19 @@ import (
//go:embed templates/* //go:embed templates/*
var templates embed.FS 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 { type WebServerOptions struct {
Port string //Port for listening Port string //Port for listening
EnableDirectoryListing bool //Enable listing of directory EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders WebRoot string //Folder for stroing the static web folders
EnableWebDirManager bool //Enable web file manager to handle files in web directory 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 Logger *logger.Logger //System logger
Sysdb *database.Database //Database for storing configs Sysdb *database.Database //Database for storing configs
} }
@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() {
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList) ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
ws.option.EnableDirectoryListing = 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 //Check the running state
webservRunning := true webservRunning := true
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning) 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"))) fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
ws.mux.Handle("/", ws.fsMiddleware(fs)) 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{ ws.server = &http.Server{
Addr: ":" + ws.option.Port, Addr: listenAddr,
Handler: ws.mux, Handler: ws.mux,
} }

View File

@ -556,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
proxyRateLimit = 1000 proxyRateLimit = 1000
} }
// Disable chunked Encoding
disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc")
//Load the previous basic auth credentials from current proxy rules //Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil { if err != nil {
@ -596,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.RateLimit = proxyRateLimit
newProxyEndpoint.UseStickySession = useStickySession newProxyEndpoint.UseStickySession = useStickySession
newProxyEndpoint.DisableUptimeMonitor = disbleUtm newProxyEndpoint.DisableUptimeMonitor = disbleUtm
newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
newProxyEndpoint.Tags = tags newProxyEndpoint.Tags = tags
//Prepare to replace the current routing rule //Prepare to replace the current routing rule
@ -673,6 +677,83 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
utils.SendOK(w) 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) { func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
ep, err := utils.PostPara(r, "ep") ep, err := utils.PostPara(r, "ep")
if err != nil { if err != nil {

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<div class="standardContainer"> <div class="standardContainer">
<div class="ui basic segment"> <div class="ui basic segment">
<h2>Default Site</h2> <h2>Default Site</h2>
<p>Default routing options for inbound traffic (previously called Proxy Root)</p> <p>Default routing options for inbound traffic</p>
<div class="ui form"> <div class="ui form">
<div class="grouped fields"> <div class="grouped fields">
<label>What to show when Zoraxy is hit with an unknown Host?</label> <label>What to show when Zoraxy is hit with an unknown Host?</label>
@ -209,14 +209,13 @@
}) })
} }
//Set the new proxy root option //Set the new proxy root (aka default site) option
function setProxyRoot(btn=undefined){ function setProxyRoot(btn=undefined){
var newpr = $("#proxyRoot").val(); var newpr = $("#proxyRoot").val();
if (newpr.trim() == "" && currentDefaultSiteOption == 0){ if (newpr.trim() == "" && currentDefaultSiteOption == 0){
//Fill in the web server info //Fill in the web server info
newpr = "127.0.0.1:" + $("#webserv_listenPort").val(); newpr = "127.0.0.1:" + $("#webserv_listenPort").val();
$("#proxyRoot").val(newpr); $("#proxyRoot").val(newpr);
} }
var rootReqTls = $("#rootReqTLS")[0].checked; var rootReqTls = $("#rootReqTLS")[0].checked;

View File

@ -25,6 +25,7 @@
<ul> <ul>
<li><a href="https://www.authelia.com" rel=”noopener noreferrer target="_blank">Authelia</a></li> <li><a href="https://www.authelia.com" rel=”noopener noreferrer target="_blank">Authelia</a></li>
<li><a href="https://goauthentik.io/" rel=”noopener noreferrer target="_blank">Authentik</a></li> <li><a href="https://goauthentik.io/" rel=”noopener noreferrer target="_blank">Authentik</a></li>
<li><a href="https://oauth2-proxy.github.io/oauth2-proxy/" rel=”noopener noreferrer target="_blank">OAuth2 Proxy</a></li>
</ul> </ul>
<form class="ui form" action="#" id="forwardAuthSettings"> <form class="ui form" action="#" id="forwardAuthSettings">
<div class="field"> <div class="field">
@ -42,26 +43,43 @@
<div class="field"> <div class="field">
<label for="forwardAuthResponseHeaders">Response Headers</label> <label for="forwardAuthResponseHeaders">Response Headers</label>
<input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers"> <input type="text" id="forwardAuthResponseHeaders" name="forwardAuthResponseHeaders" placeholder="Enter Forward Auth Response Headers">
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. <br> <small>
<strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code></small> Comma separated list of case-insensitive headers to copy from the authorization servers response to the request sent to the backend. If not set no headers are copied. <br>
<strong>Example:</strong> <code>Remote-User,Remote-Groups,Remote-Email,Remote-Name</code>
</small>
</div> </div>
<div class="field"> <div class="field">
<label for="forwardAuthResponseClientHeaders">Response Client Headers</label> <label for="forwardAuthResponseClientHeaders">Response Client Headers</label>
<input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers"> <input type="text" id="forwardAuthResponseClientHeaders" name="forwardAuthResponseClientHeaders" placeholder="Enter Forward Auth Response Client Headers">
<small>Comma separated list of case-insensitive headers to copy from the authorization servers response to the response sent to the client. If not set no headers are copied. <br> <small>
<strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code></small> Comma separated list of case-insensitive headers to copy from the authorization servers response to the <b><i>response sent to the client</i></b>. If not set no headers are copied. <br>
<strong>Example:</strong> <code>Set-Cookie,WWW-Authenticate</code>
</small>
</div> </div>
<div class="field"> <div class="field">
<label for="forwardAuthRequestHeaders">Request Headers</label> <label for="forwardAuthRequestHeaders">Request Headers</label>
<input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers"> <input type="text" id="forwardAuthRequestHeaders" name="forwardAuthRequestHeaders" placeholder="Enter Forward Auth Request Headers">
<small>Comma separated list of case-insensitive headers to copy from the original request to the request made to the authorization server. If not set all headers are copied. <br> <small>
<strong>Example:</strong> <code>Cookie,Authorization</code></small> Comma separated list of case-insensitive headers to copy from the original request to the <b><i>request made to the authorization server</i></b>. If not set all headers are copied. <br>
<strong>Recommendation:</strong> Generally it's recommended to leave this blank or use the below example for predictable results. <br>
<strong>Example:</strong> <code>Accept,X-Requested-With,Cookie,Authorization,Proxy-Authorization</code>
</small>
</div>
<div class="field">
<label for="forwardAuthRequestIncludedCookies">Request Included Cookies</label>
<input type="text" id="forwardAuthRequestIncludedCookies" name="forwardAuthRequestIncludedCookies" placeholder="Enter Forward Auth Request Included Cookies">
<small>
Comma separated list of case-sensitive cookie names to copy from the original request to the <b><i>request made to the authorization server</i></b>. If not set all cookies are included. This allows omitting all cookies not required by the authorization server.<br>
<strong>Example:</strong> <code>authelia_session,another_session</code>
</small>
</div> </div>
<div class="field"> <div class="field">
<label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label> <label for="forwardAuthRequestExcludedCookies">Request Excluded Cookies</label>
<input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies"> <input type="text" id="forwardAuthRequestExcludedCookies" name="forwardAuthRequestExcludedCookies" placeholder="Enter Forward Auth Request Excluded Cookies">
<small>Comma separated list of case-sensitive cookie names to exclude from the request to the backend. If not set no cookies are excluded. <br> <small>
<strong>Example:</strong> <code>authelia_session,another_session</code></small> Comma separated list of case-sensitive cookie names to exclude from the <b><i>request made to the backend application</i></b>. If not set no cookies are excluded. This allows omitting the cookie intended only for the authorization server.<br>
<strong>Example:</strong> <code>authelia_session,another_session</code>
</small>
</div> </div>
</div> </div>
</div> </div>
@ -132,6 +150,7 @@
$('#forwardAuthResponseHeaders').val(data.responseHeaders.join(",")); $('#forwardAuthResponseHeaders').val(data.responseHeaders.join(","));
$('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(",")); $('#forwardAuthResponseClientHeaders').val(data.responseClientHeaders.join(","));
$('#forwardAuthRequestHeaders').val(data.requestHeaders.join(",")); $('#forwardAuthRequestHeaders').val(data.requestHeaders.join(","));
$('#forwardAuthRequestIncludedCookies').val(data.requestIncludedCookies.join(","));
$('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(",")); $('#forwardAuthRequestExcludedCookies').val(data.requestExcludedCookies.join(","));
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
@ -170,6 +189,7 @@
const responseHeaders = $('#forwardAuthResponseHeaders').val(); const responseHeaders = $('#forwardAuthResponseHeaders').val();
const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val(); const responseClientHeaders = $('#forwardAuthResponseClientHeaders').val();
const requestHeaders = $('#forwardAuthRequestHeaders').val(); const requestHeaders = $('#forwardAuthRequestHeaders').val();
const requestIncludedCookies = $('#forwardAuthRequestIncludedCookies').val();
const requestExcludedCookies = $('#forwardAuthRequestExcludedCookies').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}.`); console.log(`Updating Forward Auth settings. Address: ${address}. Response Headers: ${responseHeaders}. Response Client Headers: ${responseClientHeaders}. Request Headers: ${requestHeaders}. Request Excluded Cookies: ${requestExcludedCookies}.`);

View File

@ -29,6 +29,13 @@
<small>If this folder do not contains any index files, list the directory of this folder.</small> <small>If this folder do not contains any index files, list the directory of this folder.</small>
</div> </div>
</div> </div>
<div class="inline field">
<div class="ui toggle checkbox">
<input id="webserv_enableAllInterfaces" type="checkbox" class="hidden">
<label>Listening to All Interfaces</label>
<small>When disabled, the web server will only listen to localhost (127.0.0.1) and only reachable via reverse proxy rules.</small>
</div>
</div>
<div class="field"> <div class="field">
<label>Document Root Folder</label> <label>Document Root Folder</label>
<input id="webserv_docRoot" type="text" readonly="true"> <input id="webserv_docRoot" type="text" readonly="true">
@ -136,6 +143,13 @@
$("#webserv_dirManager").remove(); $("#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); $("#webserv_listenPort").val(data.ListeningPort);
updateWebServLinkExample(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(){ $("#webserv_listenPort").off("change").on("change", function(){
let newPort = $(this).val(); let newPort = $(this).val();

View File

@ -184,7 +184,8 @@ body.darkTheme .ui.input input::placeholder {
body.darkTheme .ui.label, body.darkTheme .ui.label,
body.darkTheme .ui.label .detail, body.darkTheme .ui.label .detail,
body.darkTheme .ui.label .icon { body.darkTheme .ui.label .icon {
color: #ffffff !important; background-color: var(--buttom_toggle_disabled);
color: var(--text_color) !important;
} }
body.darkTheme .advanceoptions .title { body.darkTheme .advanceoptions .title {

View File

@ -120,7 +120,7 @@
<!-- Create Rules --> <!-- Create Rules -->
<div id="rules" class="functiontab" target="rules.html"></div> <div id="rules" class="functiontab" target="rules.html"></div>
<!-- Set proxy root --> <!-- Set default site -->
<div id="setroot" class="functiontab" target="rproot.html"></div> <div id="setroot" class="functiontab" target="rproot.html"></div>
<!-- Set TLS cert --> <!-- Set TLS cert -->

View File

@ -25,7 +25,7 @@
<div class="scrolling content ui form"> <div class="scrolling content ui form">
<div id="inlineEditBasicAuthCredentials" class="field"> <div id="inlineEditBasicAuthCredentials" class="field">
<p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p> <p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
<table class="ui very basic compacted unstackable celled table"> <table class="ui basic very compact unstackable celled table">
<thead> <thead>
<tr> <tr>
<th>Alias Hostname</th> <th>Alias Hostname</th>
@ -161,7 +161,7 @@
} }
$("#inlineEditTable").append(`<tr> $("#inlineEditTable").append(`<tr>
<td>${domainLink}</td> <td>${domainLink}</td>
<td><button class="ui basic button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red remove icon"></i> Remove</button></td> <td><button class="ui basic mini circular icon button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red trash icon"></i></button></td>
</tr>`); </tr>`);
}); });

View File

@ -14,18 +14,11 @@
<script src="../script/darktheme.js"></script> <script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header">
<div class="content">
Basic Auth Settings
<div class="sub header" id="epname"></div>
</div>
</div>
<div class="ui divider"></div>
<h3 class="ui header">Basic Auth Credential</h3> <h3 class="ui header">Basic Auth Credential</h3>
<div class="scrolling content ui form"> <div class="scrolling content ui form">
<div id="inlineEditBasicAuthCredentials" class="field"> <div id="inlineEditBasicAuthCredentials" class="field">
<p>Enter the username and password for allowing them to access this proxy endpoint</p> <p>Enter the username and password for allowing them to access this proxy endpoint</p>
<table class="ui very basic compacted unstackable celled table"> <table class="ui basic very compacted unstackable celled table">
<thead> <thead>
<tr> <tr>
<th>Username</th> <th>Username</th>
@ -56,7 +49,7 @@
<h3 class="ui header">Authentication Exclusion Paths</h3> <h3 class="ui header">Authentication Exclusion Paths</h3>
<div class="scrolling content ui form"> <div class="scrolling content ui form">
<p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p> <p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p>
<table class="ui very basic compacted unstackable celled table"> <table class="ui basic very compacted unstackable celled table">
<thead> <thead>
<tr> <tr>
<th>Path Prefix</th> <th>Path Prefix</th>
@ -86,10 +79,6 @@
<code>/public/res/far/boo/</code></p> <code>/public/res/far/boo/</code></p>
</div> </div>
</div> </div>
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
</div> </div>
<br><br><br><br> <br><br><br><br>
@ -232,7 +221,7 @@
data.forEach(function(rule){ data.forEach(function(rule){
$("#exclusionPaths").append(` <tr> $("#exclusionPaths").append(` <tr>
<td>${rule.PathPrefix}</td> <td>${rule.PathPrefix}</td>
<td><button class="ui red basic mini icon button" onclick="removeExceptionPath(this);" prefix="${rule.PathPrefix}"><i class="ui red times icon"></i></button></td> <td><button class="ui red basic mini circular icon button" onclick="removeExceptionPath(this);" prefix="${rule.PathPrefix}"><i class="ui red times icon"></i></button></td>
</tr>`); </tr>`);
}) })
} }
@ -261,7 +250,7 @@
var row = '<tr>' + var row = '<tr>' +
'<td>' + username + '</td>' + '<td>' + username + '</td>' +
'<td>' + password + '</td>' + '<td>' + password + '</td>' +
'<td><button class="ui basic button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' + '<td><button class="ui basic tiny circular button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
'</tr>'; '</tr>';
tableBody.append(row); tableBody.append(row);

View File

@ -44,7 +44,7 @@
<a class="item narrowpadding" data-tab="security">Security Headers</a> <a class="item narrowpadding" data-tab="security">Security Headers</a>
</div> </div>
<div class="ui tab basic segment active" data-tab="customheaders"> <div class="ui tab basic segment active" data-tab="customheaders">
<table class="ui very basic compacted unstackable celled table"> <table class="ui basic very compacted unstackable celled table">
<thead> <thead>
<tr> <tr>
<th>Key</th> <th>Key</th>

View File

@ -22,7 +22,7 @@
<br> <br>
<div class="ui container"> <div class="ui container">
<p>Tags currently applied to this host name / proxy rule</p> <p>Tags currently applied to this host name / proxy rule</p>
<div style="max-height: 300px; overflow-y: scroll;"> <div>
<table class="ui compact basic unstackable celled table"> <table class="ui compact basic unstackable celled table">
<thead> <thead>
<tr> <tr>
@ -155,6 +155,10 @@
function addSelectedTags() { function addSelectedTags() {
let tags = $('#tagsInput').val().split(',').map(tag => tag.trim()); 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 => { tags.forEach(tag => {
if (tag && !tagAlreadyExistsInTable(tag)) { if (tag && !tagAlreadyExistsInTable(tag)) {
addTagRow(tag); addTagRow(tag);
@ -201,8 +205,8 @@
const row = `<tr class="tagEntry" value="${tag}"> const row = `<tr class="tagEntry" value="${tag}">
<td><div class="ui circular label tag-color" style="background-color: ${getTagColorByName(tag)};"></div> ${tag}</td> <td><div class="ui circular label tag-color" style="background-color: ${getTagColorByName(tag)};"></div> ${tag}</td>
<td> <td>
<button title="Delete Tag" class="ui circular mini red basic icon button" onclick="removeTag('${tag}')"> <button title="Delete Tag" class="ui circular mini basic button" onclick="removeTag('${tag}')">
<i class="trash icon"></i> <i class="red trash icon"></i> Delete
</button> </button>
</td> </td>
</tr>`; </tr>`;