diff --git a/src/accesslist.go b/src/accesslist.go
index 46a4861..525da35 100644
--- a/src/accesslist.go
+++ b/src/accesslist.go
@@ -210,9 +210,10 @@ func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
}
resulst := []string{}
- if bltype == "country" {
+ switch bltype {
+ case "country":
resulst = rule.GetAllBlacklistedCountryCode()
- } else if bltype == "ip" {
+ case "ip":
resulst = rule.GetAllBlacklistedIp()
}
diff --git a/src/api.go b/src/api.go
index 27fc773..eea3047 100644
--- a/src/api.go
+++ b/src/api.go
@@ -74,11 +74,13 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
func RegisterTLSAPIs(authRouter *auth.RouterDef) {
//Global certificate settings
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
- authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest)
+ authRouter.HandleFunc("/api/cert/tlsMinVersion", handleSetTlsMinVersion)
authRouter.HandleFunc("/api/cert/resolve", handleCertTryResolve)
authRouter.HandleFunc("/api/cert/setPreferredCertificate", handleSetDomainPreferredCertificate)
//Certificate store functions
+ authRouter.HandleFunc("/api/cert/setDefault", tlsCertManager.SetCertAsDefault)
+ authRouter.HandleFunc("/api/cert/getCommonName", tlsCertManager.HandleGetCertCommonName)
authRouter.HandleFunc("/api/cert/upload", tlsCertManager.HandleCertUpload)
authRouter.HandleFunc("/api/cert/download", tlsCertManager.HandleCertDownload)
authRouter.HandleFunc("/api/cert/list", tlsCertManager.HandleListCertificate)
diff --git a/src/cert.go b/src/cert.go
index 62b4f66..3a8b52c 100644
--- a/src/cert.go
+++ b/src/cert.go
@@ -45,32 +45,49 @@ func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
}
}
-// Handle the GET and SET of reverse proxy TLS versions
-func handleSetTlsRequireLatest(w http.ResponseWriter, r *http.Request) {
- newState, err := utils.PostPara(r, "set")
- if err != nil {
- //GET
- var reqLatestTLS bool = false
- if sysdb.KeyExists("settings", "forceLatestTLS") {
- sysdb.Read("settings", "forceLatestTLS", &reqLatestTLS)
- }
-
- js, _ := json.Marshal(reqLatestTLS)
- utils.SendJSONResponse(w, string(js))
- } else {
- switch newState {
- case "true":
- sysdb.Write("settings", "forceLatestTLS", true)
- SystemWideLogger.Println("Updating minimum TLS version to v1.2 or above")
- dynamicProxyRouter.UpdateTLSVersion(true)
- case "false":
- sysdb.Write("settings", "forceLatestTLS", false)
- SystemWideLogger.Println("Updating minimum TLS version to v1.0 or above")
- dynamicProxyRouter.UpdateTLSVersion(false)
- default:
- utils.SendErrorResponse(w, "invalid state given")
- }
+func minTlsVersionStringToUint16(version string) uint16 {
+ // Update the setting
+ var tlsVersionUint16 uint16
+ switch version {
+ case "1.0":
+ tlsVersionUint16 = 0x0301
+ case "1.1":
+ tlsVersionUint16 = 0x0302
+ case "1.2":
+ tlsVersionUint16 = 0x0303
+ case "1.3":
+ tlsVersionUint16 = 0x0304
}
+ return tlsVersionUint16
+}
+
+// Handle the GET and SET of reverse proxy minimum TLS version
+func handleSetTlsMinVersion(w http.ResponseWriter, r *http.Request) {
+ newVersion, err := utils.PostPara(r, "set")
+ if err != nil {
+ // GET
+ var minTLSVersion string = "1.2" // Default to 1.2
+ if sysdb.KeyExists("settings", "minTLSVersion") {
+ sysdb.Read("settings", "minTLSVersion", &minTLSVersion)
+ }
+ js, _ := json.Marshal(minTLSVersion)
+ utils.SendJSONResponse(w, string(js))
+ return
+ }
+
+ // Validate input
+ allowed := map[string]bool{"1.0": true, "1.1": true, "1.2": true, "1.3": true}
+ if !allowed[newVersion] {
+ utils.SendErrorResponse(w, "invalid TLS version")
+ return
+ }
+
+ sysdb.Write("settings", "minTLSVersion", newVersion)
+ tlsVersionUint16 := minTlsVersionStringToUint16(newVersion)
+ // Update the setting
+ SystemWideLogger.PrintAndLog("TLS", "Updating minimum TLS version to v"+newVersion+" or above", nil)
+ dynamicProxyRouter.SetTlsMinVersion(tlsVersionUint16)
+ utils.SendOK(w)
}
func handleCertTryResolve(w http.ResponseWriter, r *http.Request) {
diff --git a/src/def.go b/src/def.go
index 8f7e0cb..01a5909 100644
--- a/src/def.go
+++ b/src/def.go
@@ -44,7 +44,7 @@ import (
const (
/* Build Constants */
SYSTEM_NAME = "Zoraxy"
- SYSTEM_VERSION = "3.2.7"
+ SYSTEM_VERSION = "3.2.8"
DEVELOPMENT_BUILD = false
/* System Constants */
diff --git a/src/go.mod b/src/go.mod
index eab53e0..6d354b2 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -17,6 +17,7 @@ require (
github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.26
github.com/monperrus/crawler-user-agents v1.1.0
+ github.com/pires/go-proxyproto v0.8.1
github.com/shirou/gopsutil/v4 v4.25.1
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
diff --git a/src/go.sum b/src/go.sum
index 9e8da09..dd2019c 100644
--- a/src/go.sum
+++ b/src/go.sum
@@ -610,6 +610,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c=
github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc=
+github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
+github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
diff --git a/src/mod/acme/acme.go b/src/mod/acme/acme.go
index e38415a..54baacb 100644
--- a/src/mod/acme/acme.go
+++ b/src/mod/acme/acme.go
@@ -27,6 +27,7 @@ import (
"github.com/go-acme/lego/v4/registration"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/info/logger"
+ "imuslab.com/zoraxy/mod/netutils"
"imuslab.com/zoraxy/mod/utils"
)
@@ -432,18 +433,18 @@ func (a *ACMEHandler) HandleGetExpiredDomains(w http.ResponseWriter, r *http.Req
// to renew the certificate, and sends a JSON response indicating the result of the renewal process.
func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
domainPara, err := utils.PostPara(r, "domains")
-
+
//Clean each domain
cleanedDomains := []string{}
- if (domainPara != "") {
+ if domainPara != "" {
for _, d := range strings.Split(domainPara, ",") {
// Apply normalization on each domain
- nd, err := NormalizeDomain(d)
+ nd, err := netutils.NormalizeDomain(d)
if err != nil {
utils.SendErrorResponse(w, jsonEscape(err.Error()))
return
- }
- cleanedDomains = append(cleanedDomains, nd)
+ }
+ cleanedDomains = append(cleanedDomains, nd)
}
}
@@ -507,7 +508,6 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
dns = true
}
-
// Default propagation timeout is 300 seconds
propagationTimeout := 300
if dns {
@@ -549,7 +549,6 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
a.Logf("Could not extract SANs from PEM, using domainPara only", err)
}
-
// Extract DNS servers from the request
var dnsServers []string
dnsServersPara, err := utils.PostPara(r, "dnsServers")
diff --git a/src/mod/acme/utils.go b/src/mod/acme/utils.go
index 0a2c3e3..df05f2e 100644
--- a/src/mod/acme/utils.go
+++ b/src/mod/acme/utils.go
@@ -7,8 +7,6 @@ import (
"fmt"
"os"
"time"
- "strings"
- "unicode"
)
// Get the issuer name from pem file
@@ -42,8 +40,6 @@ func ExtractDomains(certBytes []byte) ([]string, error) {
return []string{}, errors.New("decode cert bytes failed")
}
-
-
func ExtractIssuerName(certBytes []byte) (string, error) {
// Parse the PEM block
block, _ := pem.Decode(certBytes)
@@ -73,9 +69,9 @@ func ExtractDomainsFromPEM(pemFilePath string) ([]string, error) {
certBytes, err := os.ReadFile(pemFilePath)
if err != nil {
- return nil, err
+ return nil, err
}
- domains,err := ExtractDomains(certBytes)
+ domains, err := ExtractDomains(certBytes)
if err != nil {
return nil, err
}
@@ -116,48 +112,3 @@ func CertExpireSoon(certBytes []byte, numberOfDays int) bool {
}
return false
}
-
-
-// NormalizeDomain cleans and validates a domain string.
-// - Trims spaces around the domain
-// - Converts to lowercase
-// - Removes trailing dot (FQDN canonicalization)
-// - Checks that the domain conforms to standard rules:
-// * Each label ≤ 63 characters
-// * Only letters, digits, and hyphens
-// * Labels do not start or end with a hyphen
-// * Full domain ≤ 253 characters
-// Returns an empty string if the domain is invalid.
-func NormalizeDomain(d string) (string, error) {
- d = strings.TrimSpace(d)
- d = strings.ToLower(d)
- d = strings.TrimSuffix(d, ".")
-
- if len(d) == 0 {
- return "", errors.New("domain is empty")
- }
- if len(d) > 253 {
- return "", errors.New("domain exceeds 253 characters")
- }
-
- labels := strings.Split(d, ".")
- for _, label := range labels {
- if len(label) == 0 {
- return "", errors.New("Domain '" + d + "' not valid: Empty label")
- }
- if len(label) > 63 {
- return "", errors.New("Domain not valid: label exceeds 63 characters")
- }
-
- for i, r := range label {
- if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-') {
- return "", errors.New("Domain '" + d + "' not valid: Invalid character '" + string(r) + "' in label")
- }
- if (i == 0 || i == len(label)-1) && r == '-' {
- return "", errors.New("Domain '" + d + "' not valid: label '"+ label +"' starts or ends with hyphen")
- }
- }
- }
-
- return d, nil
-}
diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go
index e0b16bb..a9b1aa7 100644
--- a/src/mod/dynamicproxy/Server.go
+++ b/src/mod/dynamicproxy/Server.go
@@ -92,7 +92,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
//Plugin routing
-
if h.Parent.Option.PluginManager != nil && h.Parent.Option.PluginManager.HandleRoute(w, r, sep.Tags) {
//Request handled by subroute
return
diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go
index 9b6157e..dcdfbf2 100644
--- a/src/mod/dynamicproxy/dpcore/dpcore.go
+++ b/src/mod/dynamicproxy/dpcore/dpcore.go
@@ -438,7 +438,15 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) (in
if !strings.Contains(host, ":") {
host += ":443"
}
- serverName := req.URL.Hostname()
+ serverName := ""
+ //if p.Transport != nil {
+ // if tr, ok := p.Transport.(*http.Transport); ok && tr.TLSClientConfig != nil && tr.TLSClientConfig.ServerName != "" {
+ // serverName = tr.TLSClientConfig.ServerName
+ // }
+ //}
+ if serverName == "" {
+ serverName = req.URL.Hostname()
+ }
// Connect with SNI offload
tlsConfig := &tls.Config{
diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go
index 39443e5..c4421c1 100644
--- a/src/mod/dynamicproxy/dynamicproxy.go
+++ b/src/mod/dynamicproxy/dynamicproxy.go
@@ -48,8 +48,8 @@ func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
// Update TLS Version in runtime. Will restart proxy server if running.
// Set this to true to force TLS 1.2 or above
-func (router *Router) UpdateTLSVersion(requireLatest bool) {
- router.Option.ForceTLSLatest = requireLatest
+func (router *Router) SetTlsMinVersion(minTlsVersion uint16) {
+ router.Option.MinTLSVersion = minTlsVersion
router.Restart()
}
@@ -77,9 +77,9 @@ func (router *Router) StartProxyService() error {
return errors.New("reverse proxy router root not set")
}
- minVersion := tls.VersionTLS10
- if router.Option.ForceTLSLatest {
- minVersion = tls.VersionTLS12
+ minVersion := tls.VersionTLS12 //Default to TLS 1.2
+ if router.Option.MinTLSVersion != 0 {
+ minVersion = int(router.Option.MinTLSVersion)
}
config := &tls.Config{
diff --git a/src/mod/dynamicproxy/endpoints.go b/src/mod/dynamicproxy/endpoints.go
index 9a18fc1..58031c3 100644
--- a/src/mod/dynamicproxy/endpoints.go
+++ b/src/mod/dynamicproxy/endpoints.go
@@ -272,6 +272,11 @@ func (ep *ProxyEndpoint) Remove() error {
return nil
}
+// Check if the proxy endpoint is enabled
+func (ep *ProxyEndpoint) IsEnabled() bool {
+ return !ep.Disabled
+}
+
// Write changes to runtime without respawning the proxy handler
// use prepare -> remove -> add if you change anything in the endpoint
// that effects the proxy routing src / dest
diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go
index c1bb15e..7d3f669 100644
--- a/src/mod/dynamicproxy/proxyRequestHandler.go
+++ b/src/mod/dynamicproxy/proxyRequestHandler.go
@@ -12,6 +12,7 @@ import (
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
+ "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/netutils"
"imuslab.com/zoraxy/mod/statistic"
@@ -95,27 +96,47 @@ func (router *Router) GetProxyEndpointFromHostname(hostname string) *ProxyEndpoi
return targetSubdomainEndpoint
}
-// Clearn URL Path (without the http:// part) replaces // in a URL to /
-func (router *Router) clearnURL(targetUrlOPath string) string {
- return strings.ReplaceAll(targetUrlOPath, "//", "/")
-}
-
// Rewrite URL rewrite the prefix part of a virtual directory URL with /
func (router *Router) rewriteURL(rooturl string, requestURL string) string {
rewrittenURL := requestURL
rewrittenURL = strings.TrimPrefix(rewrittenURL, strings.TrimSuffix(rooturl, "/"))
if strings.Contains(rewrittenURL, "//") {
- rewrittenURL = router.clearnURL(rewrittenURL)
+ rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
}
return rewrittenURL
}
+// upstreamHostSwap check if this loopback to one of the proxy rule in the system. If yes, do a shortcut target swap
+// this prevents unnecessary external DNS lookup and connection, return true if swapped and request is already handled
+// by the loopback handler. Only continue if return is false
+func (h *ProxyHandler) upstreamHostSwap(w http.ResponseWriter, r *http.Request, selectedUpstream *loadbalance.Upstream) bool {
+ upstreamHostname := selectedUpstream.OriginIpOrDomain
+ if strings.Contains(upstreamHostname, ":") {
+ upstreamHostname = strings.Split(upstreamHostname, ":")[0]
+ }
+ loopbackProxyEndpoint := h.Parent.GetProxyEndpointFromHostname(upstreamHostname)
+ if loopbackProxyEndpoint != nil {
+ //This is a loopback request. Swap the target to the loopback target
+ //h.Parent.Option.Logger.PrintAndLog("proxy", "Detected a loopback request to self. Swap the target to "+loopbackProxyEndpoint.RootOrMatchingDomain, nil)
+ if loopbackProxyEndpoint.IsEnabled() {
+ h.hostRequest(w, r, loopbackProxyEndpoint)
+ } else {
+ //Endpoint disabled, return 503
+ http.ServeFile(w, r, "./web/rperror.html")
+ h.Parent.logRequest(r, false, 521, "host-http", r.Host, upstreamHostname)
+ }
+ return true
+ }
+ return false
+}
+
// Handle host request
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host)
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
reqHostname := r.Host
+
/* Load balancing */
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
if err != nil {
@@ -125,6 +146,12 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
return
}
+ /* Upstream Host Swap (use to detect loopback to self) */
+ if h.upstreamHostSwap(w, r, selectedUpstream) {
+ //Request handled by the loopback handler
+ return
+ }
+
/* WebSocket automatic proxy */
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go
index 2f6528d..3caf5b3 100644
--- a/src/mod/dynamicproxy/typedef.go
+++ b/src/mod/dynamicproxy/typedef.go
@@ -49,7 +49,7 @@ type RouterOption struct {
HostVersion string //The version of Zoraxy, use for heading mod
Port int //Incoming port
UseTls bool //Use TLS to serve incoming requsts
- ForceTLSLatest bool //Force TLS1.2 or above
+ MinTLSVersion uint16 //Minimum TLS version
NoCache bool //Force set Cache-Control: no-store
ListenOnPort80 bool //Enable port 80 http listener
ForceHttpsRedirect bool //Force redirection of http to https endpoint
diff --git a/src/mod/netutils/ipmatch.go b/src/mod/netutils/ipmatch.go
index a0c65a1..5df5595 100644
--- a/src/mod/netutils/ipmatch.go
+++ b/src/mod/netutils/ipmatch.go
@@ -13,6 +13,25 @@ import (
CIDR and IPv4 / v6 validations
*/
+// Get the requester IP without trusting any proxy headers
+func GetRequesterIPUntrusted(r *http.Request) string {
+ // If the request is from an untrusted IP, we should not trust the X-Real-IP and X-Forwarded-For headers
+ ip := r.RemoteAddr
+ // Trim away the port number
+ reqHost, _, err := net.SplitHostPort(ip)
+ if err == nil {
+ ip = reqHost
+ }
+
+ // Check if the IP is a valid IPv4 or IPv6 address
+ parsedIP := net.ParseIP(ip)
+ if parsedIP == nil {
+ return ""
+ }
+ return ip
+}
+
+// Get the requester IP, trust the X-Real-IP and X-Forwarded-For headers
func GetRequesterIP(r *http.Request) string {
ip := r.Header.Get("X-Real-Ip")
if ip == "" {
diff --git a/src/mod/netutils/netutils.go b/src/mod/netutils/netutils.go
index 46ebcd4..6890b99 100644
--- a/src/mod/netutils/netutils.go
+++ b/src/mod/netutils/netutils.go
@@ -2,10 +2,13 @@ package netutils
import (
"encoding/json"
+ "errors"
"fmt"
"net"
"net/http"
"strconv"
+ "strings"
+ "unicode"
"github.com/likexian/whois"
"imuslab.com/zoraxy/mod/utils"
@@ -167,3 +170,53 @@ func CheckIfPortOccupied(portNumber int) bool {
listener.Close()
return false
}
+
+// NormalizeDomain cleans and validates a domain string.
+// - Trims spaces around the domain
+// - Converts to lowercase
+// - Removes trailing dot (FQDN canonicalization)
+// - Checks that the domain conforms to standard rules:
+// - Each label ≤ 63 characters
+// - Only letters, digits, and hyphens
+// - Labels do not start or end with a hyphen
+// - Full domain ≤ 253 characters
+//
+// Returns an empty string if the domain is invalid.
+func NormalizeDomain(d string) (string, error) {
+ d = strings.TrimSpace(d)
+ d = strings.ToLower(d)
+ d = strings.TrimSuffix(d, ".")
+
+ if len(d) == 0 {
+ return "", errors.New("domain is empty")
+ }
+ if len(d) > 253 {
+ return "", errors.New("domain exceeds 253 characters")
+ }
+
+ labels := strings.Split(d, ".")
+ for index, label := range labels {
+ if index == 0 {
+ if len(label) == 1 && label == "*" {
+ continue
+ }
+ }
+ if len(label) == 0 {
+ return "", errors.New("Domain '" + d + "' not valid: Empty label")
+ }
+ if len(label) > 63 {
+ return "", errors.New("Domain not valid: label exceeds 63 characters")
+ }
+
+ for i, r := range label {
+ if !unicode.IsLetter(r) && !unicode.IsDigit(r) && r != '-' {
+ return "", errors.New("Domain '" + d + "' not valid: Invalid character '" + string(r) + "' in label")
+ }
+ if (i == 0 || i == len(label)-1) && r == '-' {
+ return "", errors.New("Domain '" + d + "' not valid: label '" + label + "' starts or ends with hyphen")
+ }
+ }
+ }
+
+ return d, nil
+}
diff --git a/src/mod/streamproxy/handler.go b/src/mod/streamproxy/handler.go
index 147100e..72a593e 100644
--- a/src/mod/streamproxy/handler.go
+++ b/src/mod/streamproxy/handler.go
@@ -47,19 +47,19 @@ func (m *Manager) HandleAddProxyConfig(w http.ResponseWriter, r *http.Request) {
useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP")
- useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol")
+ ProxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion")
enableLogging, _ := utils.PostBool(r, "enableLogging")
//Create the target config
newConfigUUID := m.NewConfig(&ProxyRelayOptions{
- Name: name,
- ListeningAddr: strings.TrimSpace(listenAddr),
- ProxyAddr: strings.TrimSpace(proxyAddr),
- Timeout: timeout,
- UseTCP: useTCP,
- UseUDP: useUDP,
- UseProxyProtocol: useProxyProtocol,
- EnableLogging: enableLogging,
+ Name: name,
+ ListeningAddr: strings.TrimSpace(listenAddr),
+ ProxyAddr: strings.TrimSpace(proxyAddr),
+ Timeout: timeout,
+ UseTCP: useTCP,
+ UseUDP: useUDP,
+ ProxyProtocolVersion: convertIntToProxyProtocolVersion(ProxyProtocolVersion),
+ EnableLogging: enableLogging,
})
js, _ := json.Marshal(newConfigUUID)
@@ -79,7 +79,7 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
proxyAddr, _ := utils.PostPara(r, "proxyAddr")
useTCP, _ := utils.PostBool(r, "useTCP")
useUDP, _ := utils.PostBool(r, "useUDP")
- useProxyProtocol, _ := utils.PostBool(r, "useProxyProtocol")
+ proxyProtocolVersion, _ := utils.PostInt(r, "proxyProtocolVersion")
enableLogging, _ := utils.PostBool(r, "enableLogging")
newTimeoutStr, _ := utils.PostPara(r, "timeout")
@@ -94,15 +94,15 @@ func (m *Manager) HandleEditProxyConfigs(w http.ResponseWriter, r *http.Request)
// Create a new ProxyRuleUpdateConfig with the extracted parameters
newConfig := &ProxyRuleUpdateConfig{
- InstanceUUID: configUUID,
- NewName: newName,
- NewListeningAddr: listenAddr,
- NewProxyAddr: proxyAddr,
- UseTCP: useTCP,
- UseUDP: useUDP,
- UseProxyProtocol: useProxyProtocol,
- EnableLogging: enableLogging,
- NewTimeout: newTimeout,
+ InstanceUUID: configUUID,
+ NewName: newName,
+ NewListeningAddr: listenAddr,
+ NewProxyAddr: proxyAddr,
+ UseTCP: useTCP,
+ UseUDP: useUDP,
+ ProxyProtocolVersion: proxyProtocolVersion,
+ EnableLogging: enableLogging,
+ NewTimeout: newTimeout,
}
// Call the EditConfig method to modify the configuration
diff --git a/src/mod/streamproxy/streamproxy.go b/src/mod/streamproxy/streamproxy.go
index c473e33..599c71f 100644
--- a/src/mod/streamproxy/streamproxy.go
+++ b/src/mod/streamproxy/streamproxy.go
@@ -15,50 +15,59 @@ import (
)
/*
- TCP Proxy
+ Stream Proxy
Forward port from one port to another
Also accept active connection and passive
connection
*/
+// ProxyProtocolVersion enum type
+type ProxyProtocolVersion int
+
+const (
+ ProxyProtocolDisabled ProxyProtocolVersion = 0
+ ProxyProtocolV1 ProxyProtocolVersion = 1
+ ProxyProtocolV2 ProxyProtocolVersion = 2
+)
+
type ProxyRelayOptions struct {
- Name string
- ListeningAddr string
- ProxyAddr string
- Timeout int
- UseTCP bool
- UseUDP bool
- UseProxyProtocol bool
- EnableLogging bool
+ Name string
+ ListeningAddr string
+ ProxyAddr string
+ Timeout int
+ UseTCP bool
+ UseUDP bool
+ ProxyProtocolVersion ProxyProtocolVersion
+ EnableLogging bool
}
// ProxyRuleUpdateConfig is used to update the proxy rule config
type ProxyRuleUpdateConfig struct {
- InstanceUUID string //The target instance UUID to update
- NewName string //New name for the instance, leave empty for no change
- NewListeningAddr string //New listening address, leave empty for no change
- NewProxyAddr string //New proxy target address, leave empty for no change
- UseTCP bool //Enable TCP proxy, default to false
- UseUDP bool //Enable UDP proxy, default to false
- UseProxyProtocol bool //Enable Proxy Protocol, default to false
- EnableLogging bool //Enable Logging TCP/UDP Message, default to true
- NewTimeout int //New timeout for the connection, leave -1 for no change
+ InstanceUUID string //The target instance UUID to update
+ NewName string //New name for the instance, leave empty for no change
+ NewListeningAddr string //New listening address, leave empty for no change
+ NewProxyAddr string //New proxy target address, leave empty for no change
+ UseTCP bool //Enable TCP proxy, default to false
+ UseUDP bool //Enable UDP proxy, default to false
+ ProxyProtocolVersion int //Enable Proxy Protocol v1/v2, default to disabled
+ EnableLogging bool //Enable Logging TCP/UDP Message, default to true
+ NewTimeout int //New timeout for the connection, leave -1 for no change
}
type ProxyRelayInstance struct {
/* Runtime Config */
- UUID string //A UUIDv4 representing this config
- Name string //Name of the config
- Running bool //Status, read only
- AutoStart bool //If the service suppose to started automatically
- ListeningAddress string //Listening Address, usually 127.0.0.1:port
- ProxyTargetAddr string //Proxy target address
- UseTCP bool //Enable TCP proxy
- UseUDP bool //Enable UDP proxy
- UseProxyProtocol bool //Enable Proxy Protocol
- EnableLogging bool //Enable logging for ProxyInstance
- Timeout int //Timeout for connection in sec
+ UUID string //A UUIDv4 representing this config
+ Name string //Name of the config
+ Running bool //Status, read only
+ AutoStart bool //If the service suppose to started automatically
+ ListeningAddress string //Listening Address, usually 127.0.0.1:port
+ ProxyTargetAddr string //Proxy target address
+ UseTCP bool //Enable TCP proxy
+ UseUDP bool //Enable UDP proxy
+ ProxyProtocolVersion ProxyProtocolVersion //Proxy Protocol v1/v2
+ EnableLogging bool //Enable logging for ProxyInstance
+ Timeout int //Timeout for connection in sec
/* Internal */
tcpStopChan chan bool //Stop channel for TCP listener
@@ -178,7 +187,7 @@ func (m *Manager) NewConfig(config *ProxyRelayOptions) string {
ProxyTargetAddr: config.ProxyAddr,
UseTCP: config.UseTCP,
UseUDP: config.UseUDP,
- UseProxyProtocol: config.UseProxyProtocol,
+ ProxyProtocolVersion: config.ProxyProtocolVersion,
EnableLogging: config.EnableLogging,
Timeout: config.Timeout,
tcpStopChan: nil,
@@ -203,6 +212,30 @@ func (m *Manager) GetConfigByUUID(configUUID string) (*ProxyRelayInstance, error
return nil, errors.New("config not found")
}
+// ConvertIntToProxyProtocolVersion converts an int to ProxyProtocolVersion type
+func convertIntToProxyProtocolVersion(v int) ProxyProtocolVersion {
+ switch v {
+ case 1:
+ return ProxyProtocolV1
+ case 2:
+ return ProxyProtocolV2
+ default:
+ return ProxyProtocolDisabled
+ }
+}
+
+// convertProxyProtocolVersionToInt converts ProxyProtocolVersion type back to int
+func convertProxyProtocolVersionToInt(v ProxyProtocolVersion) int {
+ switch v {
+ case ProxyProtocolV1:
+ return 1
+ case ProxyProtocolV2:
+ return 2
+ default:
+ return 0
+ }
+}
+
// Edit the config based on config UUID, leave empty for unchange fields
func (m *Manager) EditConfig(newConfig *ProxyRuleUpdateConfig) error {
// Find the config with the specified UUID
@@ -224,7 +257,7 @@ func (m *Manager) EditConfig(newConfig *ProxyRuleUpdateConfig) error {
foundConfig.UseTCP = newConfig.UseTCP
foundConfig.UseUDP = newConfig.UseUDP
- foundConfig.UseProxyProtocol = newConfig.UseProxyProtocol
+ foundConfig.ProxyProtocolVersion = convertIntToProxyProtocolVersion(newConfig.ProxyProtocolVersion)
foundConfig.EnableLogging = newConfig.EnableLogging
if newConfig.NewTimeout != -1 {
diff --git a/src/mod/streamproxy/tcpprox.go b/src/mod/streamproxy/tcpprox.go
index d16ad8d..07807f0 100644
--- a/src/mod/streamproxy/tcpprox.go
+++ b/src/mod/streamproxy/tcpprox.go
@@ -11,6 +11,8 @@ import (
"sync"
"sync/atomic"
"time"
+
+ proxyproto "github.com/pires/go-proxyproto"
)
func isValidIP(ip string) bool {
@@ -44,20 +46,22 @@ func (c *ProxyRelayInstance) connCopy(conn1 net.Conn, conn2 net.Conn, wg *sync.W
wg.Done()
}
-func writeProxyProtocolHeaderV1(dst net.Conn, src net.Conn) error {
+func WriteProxyProtocolHeader(dst net.Conn, src net.Conn, version ProxyProtocolVersion) error {
clientAddr, ok1 := src.RemoteAddr().(*net.TCPAddr)
proxyAddr, ok2 := src.LocalAddr().(*net.TCPAddr)
if !ok1 || !ok2 {
return errors.New("invalid TCP address for proxy protocol")
}
- header := fmt.Sprintf("PROXY TCP4 %s %s %d %d\r\n",
- clientAddr.IP.String(),
- proxyAddr.IP.String(),
- clientAddr.Port,
- proxyAddr.Port)
+ header := proxyproto.Header{
+ Version: byte(convertProxyProtocolVersionToInt(version)),
+ Command: proxyproto.PROXY,
+ TransportProtocol: proxyproto.TCPv4,
+ SourceAddr: clientAddr,
+ DestinationAddr: proxyAddr,
+ }
- _, err := dst.Write([]byte(header))
+ _, err := header.WriteTo(dst)
return err
}
@@ -161,9 +165,9 @@ func (c *ProxyRelayInstance) Port2host(allowPort string, targetAddress string, s
}
c.LogMsg("[→] connect target address ["+targetAddress+"] success.", nil)
- if c.UseProxyProtocol {
+ if c.ProxyProtocolVersion != ProxyProtocolDisabled {
c.LogMsg("[+] write proxy protocol header to target address ["+targetAddress+"]", nil)
- err = writeProxyProtocolHeaderV1(target, conn)
+ err = WriteProxyProtocolHeader(target, conn, c.ProxyProtocolVersion)
if err != nil {
c.LogMsg("[x] Write proxy protocol header failed: "+err.Error(), nil)
target.Close()
diff --git a/src/mod/streamproxy/udpprox.go b/src/mod/streamproxy/udpprox.go
index dc761df..5125263 100644
--- a/src/mod/streamproxy/udpprox.go
+++ b/src/mod/streamproxy/udpprox.go
@@ -1,11 +1,14 @@
package streamproxy
import (
+ "bytes"
"errors"
"log"
"net"
"strings"
"time"
+
+ proxyproto "github.com/pires/go-proxyproto"
)
/*
@@ -82,6 +85,24 @@ func (c *ProxyRelayInstance) CloseAllUDPConnections() {
})
}
+// Write Proxy Protocol v2 header to UDP connection
+func WriteProxyProtocolHeaderUDP(conn *net.UDPConn, srcAddr, dstAddr *net.UDPAddr) error {
+ header := proxyproto.Header{
+ Version: byte(convertProxyProtocolVersionToInt(ProxyProtocolV2)),
+ Command: proxyproto.PROXY,
+ TransportProtocol: proxyproto.UDPv4,
+ SourceAddr: srcAddr,
+ DestinationAddr: dstAddr,
+ }
+ var buf bytes.Buffer
+ _, err := header.WriteTo(&buf)
+ if err != nil {
+ return err
+ }
+ _, err = conn.Write(buf.Bytes())
+ return err
+}
+
func (c *ProxyRelayInstance) ForwardUDP(address1, address2 string, stopChan chan bool) error {
//By default the incoming listen Address is int
//We need to add the loopback address into it
@@ -142,6 +163,10 @@ func (c *ProxyRelayInstance) ForwardUDP(address1, address2 string, stopChan chan
// Fire up routine to manage new connection
go c.RunUDPConnectionRelay(conn, lisener)
+ // Send Proxy Protocol header if enabled
+ if c.ProxyProtocolVersion == ProxyProtocolV2 {
+ _ = WriteProxyProtocolHeaderUDP(conn.ServerConn, cliaddr, targetAddr)
+ }
} else {
c.LogMsg("[UDP] Found connection for client "+saddr, nil)
conn = rawConn.(*udpClientServerConn)
diff --git a/src/mod/tlscert/handler.go b/src/mod/tlscert/handler.go
index 5e9cc7b..099c6a3 100644
--- a/src/mod/tlscert/handler.go
+++ b/src/mod/tlscert/handler.go
@@ -75,6 +75,50 @@ func (m *Manager) HandleCertDownload(w http.ResponseWriter, r *http.Request) {
}
}
+// Set the selected certificate as the default / fallback certificate
+func (m *Manager) SetCertAsDefault(w http.ResponseWriter, r *http.Request) {
+ certname, err := utils.PostPara(r, "certname")
+ if err != nil {
+ utils.SendErrorResponse(w, "invalid certname given")
+ return
+ }
+
+ //Check if the previous default cert exists. If yes, get its hostname from cert contents
+ defaultPubKey := filepath.Join(m.CertStore, "default.key")
+ defaultPriKey := filepath.Join(m.CertStore, "default.pem")
+ if utils.FileExists(defaultPubKey) && utils.FileExists(defaultPriKey) {
+ //Move the existing default cert to its original name
+ certBytes, err := os.ReadFile(defaultPriKey)
+ if err == nil {
+ block, _ := pem.Decode(certBytes)
+ if block != nil {
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err == nil {
+ os.Rename(defaultPubKey, filepath.Join(m.CertStore, domainToFilename(cert.Subject.CommonName, "key")))
+ os.Rename(defaultPriKey, filepath.Join(m.CertStore, domainToFilename(cert.Subject.CommonName, "pem")))
+ }
+ }
+ }
+ }
+
+ //Check if the cert exists
+ certname = filepath.Base(certname) //prevent path escape
+ pubKey := filepath.Join(filepath.Join(m.CertStore), certname+".key")
+ priKey := filepath.Join(filepath.Join(m.CertStore), certname+".pem")
+ if utils.FileExists(pubKey) && utils.FileExists(priKey) {
+ os.Rename(pubKey, filepath.Join(m.CertStore, "default.key"))
+ os.Rename(priKey, filepath.Join(m.CertStore, "default.pem"))
+ utils.SendOK(w)
+
+ //Update cert list
+ m.UpdateLoadedCertList()
+
+ } else {
+ utils.SendErrorResponse(w, "invalid key-pairs: private key or public key not found in key store")
+ return
+ }
+}
+
// Handle upload of the certificate
func (m *Manager) HandleCertUpload(w http.ResponseWriter, r *http.Request) {
// check if request method is POST
@@ -124,6 +168,13 @@ func (m *Manager) HandleCertUpload(w http.ResponseWriter, r *http.Request) {
defer file.Close()
// create file in upload directory
+ // Read file contents for validation
+ fileBytes, err := io.ReadAll(file)
+ if err != nil {
+ http.Error(w, "Failed to read file", http.StatusBadRequest)
+ return
+ }
+
os.MkdirAll(m.CertStore, 0775)
f, err := os.Create(filepath.Join(m.CertStore, overWriteFilename))
if err != nil {
@@ -138,6 +189,11 @@ func (m *Manager) HandleCertUpload(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}
+ _, err = f.Write(fileBytes)
+ if err != nil {
+ http.Error(w, "Failed to save file", http.StatusInternalServerError)
+ return
+ }
//Update cert list
m.UpdateLoadedCertList()
@@ -215,11 +271,13 @@ func (m *Manager) HandleListCertificate(w http.ResponseWriter, r *http.Request)
showDate, _ := utils.GetBool(r, "date")
if showDate {
type CertInfo struct {
- Domain string
+ Domain string // Domain name of the certificate
+ Filename string // Filename that stores the certificate
LastModifiedDate string
ExpireDate string
RemainingDays int
- UseDNS bool
+ UseDNS bool // Whether this cert is obtained via DNS challenge
+ IsFallback bool // Whether this cert is the fallback/default cert
}
results := []*CertInfo{}
@@ -248,7 +306,7 @@ func (m *Manager) HandleListCertificate(w http.ResponseWriter, r *http.Request)
if err == nil {
certExpireTime = cert.NotAfter.Format("2006-01-02 15:04:05")
- duration := cert.NotAfter.Sub(time.Now())
+ duration := time.Until(cert.NotAfter)
// Convert the duration to days
expiredIn = int(duration.Hours() / 24)
@@ -262,12 +320,23 @@ func (m *Manager) HandleListCertificate(w http.ResponseWriter, r *http.Request)
useDNSValidation = certInfo.UseDNS
}
+ certDomain := ""
+ block, _ := pem.Decode(certBtyes)
+ if block != nil {
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err == nil {
+ certDomain = cert.Subject.CommonName
+ }
+ }
+
thisCertInfo := CertInfo{
- Domain: filename,
+ Domain: certDomain,
+ Filename: filename,
LastModifiedDate: modifiedTime,
ExpireDate: certExpireTime,
RemainingDays: expiredIn,
UseDNS: useDNSValidation,
+ IsFallback: (filename == "default"), //TODO: figure out a better implementation
}
results = append(results, &thisCertInfo)
@@ -350,3 +419,25 @@ func (m *Manager) HandleSelfSignCertGenerate(w http.ResponseWriter, r *http.Requ
}
utils.SendOK(w)
}
+
+// Extract the common name from a PEM encoded certificate
+func (m *Manager) HandleGetCertCommonName(w http.ResponseWriter, r *http.Request) {
+ certContents, err := utils.PostPara(r, "cert")
+ if err != nil {
+ utils.SendErrorResponse(w, "Certificate content not provided")
+ return
+ }
+ block, _ := pem.Decode([]byte(certContents))
+ if block == nil {
+ utils.SendErrorResponse(w, "Failed to decode PEM block")
+ return
+ }
+ cert, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ utils.SendErrorResponse(w, "Failed to parse certificate: "+err.Error())
+ return
+ }
+
+ js, _ := json.Marshal(cert.Subject.CommonName)
+ utils.SendJSONResponse(w, string(js))
+}
diff --git a/src/mod/tlscert/helper.go b/src/mod/tlscert/helper.go
index 0704723..31edfa6 100644
--- a/src/mod/tlscert/helper.go
+++ b/src/mod/tlscert/helper.go
@@ -29,21 +29,6 @@ func getCertPairs(certFiles []string) []string {
return result
}
-// Get the cloest subdomain certificate from a list of domains
-func matchClosestDomainCertificate(subdomain string, domains []string) string {
- var matchingDomain string = ""
- maxLength := 0
-
- for _, domain := range domains {
- if strings.HasSuffix(subdomain, "."+domain) && len(domain) > maxLength {
- matchingDomain = domain
- maxLength = len(domain)
- }
- }
-
- return matchingDomain
-}
-
// Convert a domain name to a filename format
func domainToFilename(domain string, ext string) string {
// Replace wildcard '*' with '_'
@@ -52,6 +37,10 @@ func domainToFilename(domain string, ext string) string {
domain = "_" + strings.TrimPrefix(domain, "*")
}
+ if strings.HasPrefix(".", ext) {
+ ext = strings.TrimPrefix(ext, ".")
+ }
+
// Add .pem extension
ext = strings.TrimPrefix(ext, ".") // Ensure ext does not start with a dot
return domain + "." + ext
diff --git a/src/mod/uptime/uptime.go b/src/mod/uptime/uptime.go
index 7d31da0..1cf897c 100644
--- a/src/mod/uptime/uptime.go
+++ b/src/mod/uptime/uptime.go
@@ -211,7 +211,6 @@ func getWebsiteStatus(url string) (int, error) {
}
resp, err := client.Do(req)
- //resp, err := client.Get(url)
if err != nil {
//Try replace the http with https and vise versa
rewriteURL := ""
diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go
index 2fe1ffd..21d2e40 100644
--- a/src/mod/utils/utils.go
+++ b/src/mod/utils/utils.go
@@ -199,4 +199,4 @@ func ValidateListeningAddress(address string) bool {
}
return true
-}
\ No newline at end of file
+}
diff --git a/src/reverseproxy.go b/src/reverseproxy.go
index a978d65..9eea778 100644
--- a/src/reverseproxy.go
+++ b/src/reverseproxy.go
@@ -58,13 +58,9 @@ func ReverseProxyInit() {
SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
}
- forceLatestTLSVersion := false
- sysdb.Read("settings", "forceLatestTLS", &forceLatestTLSVersion)
- if forceLatestTLSVersion {
- SystemWideLogger.Println("Force latest TLS mode enabled. Minimum TLS LS version is set to v1.2")
- } else {
- SystemWideLogger.Println("Force latest TLS mode disabled. Minimum TLS version is set to v1.0")
- }
+ minTLSVersion := "1.2" // default
+ sysdb.Read("settings", "minTLSVersion", &minTLSVersion)
+ SystemWideLogger.Println("Minimum TLS version set to v" + minTLSVersion)
developmentMode := false
sysdb.Read("settings", "devMode", &developmentMode)
@@ -106,7 +102,7 @@ func ReverseProxyInit() {
HostVersion: SYSTEM_VERSION,
Port: inboundPort,
UseTls: useTls,
- ForceTLSLatest: forceLatestTLSVersion,
+ MinTLSVersion: minTlsVersionStringToUint16(minTLSVersion),
NoCache: developmentMode,
ListenOnPort80: listenOnPort80,
ForceHttpsRedirect: forceHttpsRedirect,
@@ -125,6 +121,7 @@ func ReverseProxyInit() {
DevelopmentMode: *development_build,
Logger: SystemWideLogger,
})
+
if err != nil {
SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)
return
@@ -238,6 +235,13 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
if bypassGlobalTLS == "" {
bypassGlobalTLS = "false"
+
+ }
+
+ // Enable uptime monitor?
+ enableUtm, err := utils.PostBool(r, "enableUtm")
+ if err != nil {
+ enableUtm = true
}
useBypassGlobalTLS := bypassGlobalTLS == "true"
@@ -410,7 +414,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
RequireRateLimit: requireRateLimit,
RateLimit: int64(proxyRateLimit),
- Tags: tags,
+ Tags: tags,
+ DisableUptimeMonitor: !enableUtm,
}
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
diff --git a/src/web/components/cert.html b/src/web/components/cert.html
index bb35c34..7063b29 100644
--- a/src/web/components/cert.html
+++ b/src/web/components/cert.html
@@ -7,7 +7,12 @@
.valid.certdate{
color: #31c071;
}
+
+ #certifiedDomainList .ui.basic.button{
+ margin-top: 0.1em;
+ }
+
TLS / SSL Certificates
@@ -21,7 +26,7 @@
-
+
Match the server name with your CN/DNS entry in certificate for faster resolve time
@@ -59,23 +64,25 @@
Current list of loaded certificates
-
-
+
+
-
Domain
-
Last Update
-
Expire At
-
DNS Challenge
-
Renew
-
Remove
+
+
Domain
+
Filename
+
Last Update
+
Expire At
+
Fallback
+
DNS Challenge
+
Actions
-
+
@@ -103,6 +110,8 @@
+
We will be removing the fallback certificate section soon.
+ Please use "Set Fallback" button in the certificate list above to set the fallback certificate.
- Domain
- Example of domain matching keyword:
- aroz.org Any acess requesting aroz.org will be proxy to the IP address below
-
- Subdomain
- Example of subdomain matching keyword:
- s1.aroz.org Any request starting with s1.aroz.org will be proxy to the IP address below
-
- Wildcard
- Example of wildcard matching keyword:
- *.aroz.org Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.
-
-
www.aroz.org
-
foo.bar.aroz.org
+
+
+
+ Matching Keyword Examples
+
+
+ Domain
+ Example of domain matching keyword:
+ aroz.org Any acess requesting aroz.org will be proxy to the IP address below
+
+ Subdomain
+ Example of subdomain matching keyword:
+ s1.aroz.org Any request starting with s1.aroz.org will be proxy to the IP address below
+
+ Wildcard
+ Example of wildcard matching keyword:
+ *.aroz.org Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.
+
+
www.aroz.org
+
foo.bar.aroz.org
+
+
+
+
+
+
+ Remote Target Require TLS
+
+
+ Upstream TLS Requirement
+
+ When you enable Proxy Target require TLS Connection, it means the upstream server (the target you are proxying to) requires a secure (HTTPS) connection.
+ This does not affect whether clients connect to this proxy endpoint using HTTP or HTTPS.
+
Client connects to mydomain.com (HTTP or HTTPS, depending on your proxy setup)
+
Proxy forwards requests to example.com:443 using HTTPS
+
+
+ Use this option if your upstream server only accepts secure connections.
+ If your upstream uses a self-signed certificate, check the Ignore TLS/SSL Verification Error option in Advance Settings.
+
+
+
+
+
+ What is Sticky Session?
+
+
+ Sticky Session (Session Affinity)
+
+ Sticky session ensures that requests from the same client are always forwarded to the same upstream server. This is useful for applications that store session data locally and require the client to consistently connect to the same backend.
+
+
+ How to Add Multiple Upstreams
+
+
Go to HTTP Proxy in the sidebar.
+
Click Edit on your proxy rule.
+
Use the Upstreams section to add more upstream endpoints for load balancing.
+
+
+ Sticky session will only work if you have more than one upstream endpoint configured.
+
+
-
+
@@ -204,6 +267,7 @@
let accessRuleToUse = $("#newProxyRuleAccessFilter").val();
let useStickySessionLB = $("#useStickySessionLB")[0].checked;
let tags = $("#proxyTags").val().trim();
+ let enableUtm = $("#enableUtm")[0].checked;
if (rootname.trim() == ""){
$("#rootname").parent().addClass("error");
@@ -238,6 +302,7 @@
access: accessRuleToUse,
stickysess: useStickySessionLB,
tags: tags,
+ enableUtm: enableUtm,
},
success: function(data){
if (data.error != undefined){
diff --git a/src/web/components/status.html b/src/web/components/status.html
index 09910ea..b91fa1d 100644
--- a/src/web/components/status.html
+++ b/src/web/components/status.html
@@ -110,10 +110,19 @@
Advance Settings
-
-
-
+
+
+
+
+ Minimum TLS Version
+ (Enhance security, but may not be compatible with legacy browsers)
+
+
@@ -466,31 +475,28 @@
initHTTPtoHTTPSRedirectSetting();
function initTlsVersionSetting(){
- $.get("/api/cert/tlsRequireLatest", function(data){
- if (data == true){
- $("#tlsMinVer").checkbox("set checked");
- }else{
- $("#tlsMinVer").checkbox("set unchecked");
+ $.get("/api/cert/tlsMinVersion", function(data){
+ // Set dropdown value
+ if (data && typeof data === "string") {
+ $("#tlsVersionSelect").val(data);
}
-
- //Bind events to the checkbox
- $("#tlsMinVer").find("input").on("change", function(){
- let thisValue = $("#tlsMinVer").checkbox("is checked");
+ // Bind change event
+ $("#tlsVersionSelect").off("change").on("change", function(){
+ var selectedVersion = $(this).val();
$.cjax({
- url: "/api/cert/tlsRequireLatest",
- data: {"set": thisValue},
+ url: "/api/cert/tlsMinVersion",
method: "POST",
+ data: {set: selectedVersion},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 5000);
}else{
- msgbox("TLS Version Setting Updated");
+ msgbox("TLS minimum version updated");
}
}
- })
+ });
});
});
-
}
initTlsVersionSetting();
diff --git a/src/web/components/streamprox.html b/src/web/components/streamprox.html
index bddac54..16cf0eb 100644
--- a/src/web/components/streamprox.html
+++ b/src/web/components/streamprox.html
@@ -74,14 +74,6 @@
Forward UDP request on this listening socket
-
-
-
-
-
-
@@ -90,6 +82,18 @@
+
+
+
+ Select Proxy Protocol v1 / v2 to use (if any)
+
+ Proxy Protocol V1 is not supported for UDP. The proxy protocol header will not be included in UDP packets if selected.
+