mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-10-24 03:24:10 +02:00
Merge branch 'main' of https://github.com/tobychui/zoraxy
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
67
src/cert.go
67
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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 := ""
|
||||
|
||||
@@ -199,4 +199,4 @@ func ValidateListeningAddress(address string) bool {
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
.valid.certdate{
|
||||
color: #31c071;
|
||||
}
|
||||
|
||||
#certifiedDomainList .ui.basic.button{
|
||||
margin-top: 0.1em;
|
||||
}
|
||||
</style>
|
||||
<script src="script/jsrsasign-all-min.js"></script>
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>TLS / SSL Certificates</h2>
|
||||
@@ -21,7 +26,7 @@
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>Server Name (Domain)</label>
|
||||
<input type="text" id="certdomain" placeholder="example.com / blog.example.com">
|
||||
<input type="text" id="certdomain" placeholder="">
|
||||
<small><i class="exclamation circle yellow icon"></i> Match the server name with your CN/DNS entry in certificate for faster resolve time</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -59,23 +64,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<p>Current list of loaded certificates</p>
|
||||
<div tourstep="certTable">
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em;">
|
||||
<div tourstep="certTable" style="width: 100%; overflow-x: auto; padding-bottom: 1em;">
|
||||
<div style="min-width: 960px; max-width: 100%; ">
|
||||
<table class="ui unstackable basic celled table">
|
||||
<thead>
|
||||
<tr><th>Domain</th>
|
||||
<th>Last Update</th>
|
||||
<th>Expire At</th>
|
||||
<th>DNS Challenge</th>
|
||||
<th class="no-sort">Renew</th>
|
||||
<th class="no-sort">Remove</th>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Filename</th>
|
||||
<th>Last Update</th>
|
||||
<th>Expire At</th>
|
||||
<th>Fallback</th>
|
||||
<th>DNS Challenge</th>
|
||||
<th class="no-sort">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody id="certifiedDomainList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<button class="ui basic button" onclick="initManagedDomainCertificateList();"><i class="green refresh icon"></i> Refresh List</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
@@ -103,6 +110,8 @@
|
||||
<button class="ui basic grey button" onclick="uploadPublicKey();"><i class="globe icon"></i> Public Key</button>
|
||||
<button class="ui basic button" onclick="uploadPrivateKey();"><i class="grey lock icon"></i> Private Key</button>
|
||||
</div>
|
||||
<div class="ui yellow message"><i class="exclamation triangle icon"></i> We will be removing the fallback certificate section soon. <br>
|
||||
Please use "<i class="ui blue home icon"></i>Set Fallback" button in the certificate list above to set the fallback certificate.</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div tourstep="acmeSettings">
|
||||
@@ -150,6 +159,58 @@
|
||||
|
||||
$("#defaultCA").dropdown();
|
||||
|
||||
function getPossibleCommonNameFromSelectedCertificate(){
|
||||
const fileInput = document.getElementById('pubkeySelector');
|
||||
const file = fileInput.files[0];
|
||||
if (!file) {
|
||||
msgbox("No certificate file selected", false, 4000);
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const certContent = e.target.result;
|
||||
$.cjax({
|
||||
url: '/api/cert/getCommonName',
|
||||
method: 'POST',
|
||||
data: { cert: certContent },
|
||||
success: function(data) {
|
||||
if (data.error !== undefined) {
|
||||
//Ignore error
|
||||
$("#certdomain").attr("placeholder", "");
|
||||
} else if (data) {
|
||||
if (typeof data === "string" && data.startsWith("*.")) {
|
||||
data = data.substring(2);
|
||||
}
|
||||
$("#certdomain").attr("placeholder", data);
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
//Ignore error
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function setSelectedCertAsFallbackCertificate(certDomain){
|
||||
$.cjax({
|
||||
url: '/api/cert/setDefault',
|
||||
method: 'POST',
|
||||
data: { certname: certDomain },
|
||||
success: function(data) {
|
||||
if (data.error !== undefined) {
|
||||
msgbox(data.error, false, 5000);
|
||||
} else {
|
||||
msgbox('Fallback certificate set successfully!');
|
||||
initManagedDomainCertificateList();
|
||||
initDefaultKeypairCheck();
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
msgbox('Failed to set fallback certificate', false, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Renew certificate by button press
|
||||
function renewCertificate(domain, dns, btn=undefined){
|
||||
@@ -378,17 +439,22 @@
|
||||
});
|
||||
data.forEach(entry => {
|
||||
let isExpired = entry.RemainingDays <= 0;
|
||||
let entryDomainRenewKey = entry.Domain;
|
||||
let entryDomainRenewKey = entry.Filename;
|
||||
if (entryDomainRenewKey.includes("_.")){
|
||||
entryDomainRenewKey = entryDomainRenewKey.replace("_.","*.");
|
||||
}
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td><a style="cursor: pointer;" title="Download certificate" onclick="handleCertDownload('${entry.Domain}');">${entry.Domain}</a></td>
|
||||
<td><a style="cursor: pointer;" title="Download certificate" onclick="handleCertDownload('${entry.Filename}');">${entry.Domain}</a></td>
|
||||
<td>${entry.Filename}</td>
|
||||
<td>${entry.LastModifiedDate}</td>
|
||||
<td class="${isExpired?"expired":"valid"} certdate">${entry.ExpireDate} (${!isExpired?entry.RemainingDays+" days left":"Expired"})</td>
|
||||
<td>${entry.IsFallback?"<i class='green check icon'></i>":""}</td>
|
||||
<td><i class="${entry.UseDNS?"green check": "red times"} icon"></i></td>
|
||||
<td><button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entryDomainRenewKey}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button></td>
|
||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||
<td>
|
||||
<button title="Set as Default / Fallback Certificate" class="ui mini basic button ${(entry.IsFallback?"disabled":"")} " onclick="setSelectedCertAsFallbackCertificate('${entry.Filename}');"><i class="ui blue home icon"></i> Set Fallback</button>
|
||||
<button title="Renew Certificate" class="ui mini basic icon button renewButton" onclick="renewCertificate('${entryDomainRenewKey}', '${entry.UseDNS}', this);"><i class="ui green refresh icon"></i></button>
|
||||
<button title="Delete key-pair" class="ui mini basic icon red button" onclick="deleteCertificate('${entry.Filename}');"><i class="ui red trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
|
||||
@@ -413,7 +479,7 @@
|
||||
document.getElementById('pubkeySelector').value = '';
|
||||
document.getElementById('prikeySelector').value = '';
|
||||
document.getElementById('certdomain').value = '';
|
||||
|
||||
$("#certdomain").attr("placeholder", "");
|
||||
uploadPendingPublicKey = undefined;
|
||||
uploadPendingPrivateKey = undefined;
|
||||
|
||||
@@ -439,8 +505,17 @@
|
||||
function handleDomainKeysUpload(callback=undefined){
|
||||
let domain = $("#certdomain").val();
|
||||
if (domain.trim() == ""){
|
||||
msgbox("Missing domain", false, 5000);
|
||||
return;
|
||||
//Check if placeholder has value
|
||||
if ($("#certdomain").attr("placeholder").trim() != ""){
|
||||
domain = $("#certdomain").attr("placeholder").trim();
|
||||
}else{
|
||||
domain = undefined;
|
||||
}
|
||||
|
||||
if (domain == undefined || domain.trim() == "") {
|
||||
msgbox("Missing domain", false, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') {
|
||||
const publicKeyForm = new FormData();
|
||||
@@ -493,6 +568,7 @@
|
||||
const file = event.target.files[0];
|
||||
if (ktype == "pub"){
|
||||
uploadPendingPublicKey = file;
|
||||
getPossibleCommonNameFromSelectedCertificate();
|
||||
}else if (ktype == "pri"){
|
||||
uploadPendingPrivateKey = file;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,13 @@
|
||||
<input type="checkbox" id="useStickySessionLB">
|
||||
<label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="enableUtm" checked>
|
||||
<label>Enable uptime monitor<br><small>Automatically check upstream status and switch to another if offline</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Tags</label>
|
||||
@@ -168,22 +175,78 @@
|
||||
</div>
|
||||
<div class="six wide column">
|
||||
<div class="ui basic segment rulesInstructions">
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
|
||||
Example of domain matching keyword:<br>
|
||||
<code>aroz.org</code> <br>Any acess requesting aroz.org will be proxy to the IP address below<br>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
|
||||
Example of subdomain matching keyword:<br>
|
||||
<code>s1.aroz.org</code> <br>Any request starting with s1.aroz.org will be proxy to the IP address below<br>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
|
||||
Example of wildcard matching keyword:<br>
|
||||
<code>*.aroz.org</code> <br>Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.<br>
|
||||
<div class="ui list">
|
||||
<div class="item"><code>www.aroz.org</code></div>
|
||||
<div class="item"><code>foo.bar.aroz.org</code></div>
|
||||
<div class="ui fluid styled accordion" id="matchingKeywordExamplesAccordion" style="background-color: transparent !important;">
|
||||
<div class="title active" style="color: white;">
|
||||
<i class="dropdown icon"></i>
|
||||
Matching Keyword Examples
|
||||
</div>
|
||||
<div class="content active">
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
|
||||
Example of domain matching keyword:<br>
|
||||
<code>aroz.org</code> <br>Any acess requesting aroz.org will be proxy to the IP address below<br>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
|
||||
Example of subdomain matching keyword:<br>
|
||||
<code>s1.aroz.org</code> <br>Any request starting with s1.aroz.org will be proxy to the IP address below<br>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
|
||||
Example of wildcard matching keyword:<br>
|
||||
<code>*.aroz.org</code> <br>Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.<br>
|
||||
<div class="ui list">
|
||||
<div class="item"><code>www.aroz.org</code></div>
|
||||
<div class="item"><code>foo.bar.aroz.org</code></div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="title" style="color: white;">
|
||||
<i class="dropdown icon"></i>
|
||||
Remote Target Require TLS
|
||||
</div>
|
||||
<div class="content">
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui green lock icon"></i> Upstream TLS Requirement</span><br>
|
||||
<p>
|
||||
When you enable <b>Proxy Target require TLS Connection</b>, it means the <b>upstream server</b> (the target you are proxying to) requires a secure (HTTPS) connection.<br>
|
||||
<b>This does not affect whether clients connect to this proxy endpoint using HTTP or HTTPS.</b>
|
||||
</p>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Example</span><br>
|
||||
<code>Matching Keyword: mydomain.com<br>
|
||||
Target: example.com:443 (TLS enabled)</code><br>
|
||||
<ul>
|
||||
<li>Client connects to <b>mydomain.com</b> (HTTP or HTTPS, depending on your proxy setup)</li>
|
||||
<li>Proxy forwards requests to <b>example.com:443</b> using <b>HTTPS</b></li>
|
||||
</ul>
|
||||
<small>
|
||||
Use this option if your upstream server only accepts secure connections.<br>
|
||||
If your upstream uses a self-signed certificate, check the <b>Ignore TLS/SSL Verification Error</b> option in Advance Settings.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="title" style="color: white;">
|
||||
<i class="dropdown icon"></i>
|
||||
What is Sticky Session?
|
||||
</div>
|
||||
<div class="content">
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui green sync icon"></i> Sticky Session (Session Affinity)</span><br>
|
||||
<p>
|
||||
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.<br>
|
||||
</p>
|
||||
<div class="ui divider"></div>
|
||||
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> How to Add Multiple Upstreams</span><br>
|
||||
<ul>
|
||||
<li>Go to <b>HTTP Proxy</b> in the sidebar.</li>
|
||||
<li>Click <b>Edit</b> on your proxy rule.</li>
|
||||
<li>Use the <b>Upstreams</b> section to add more upstream endpoints for load balancing.</li>
|
||||
</ul>
|
||||
<small>
|
||||
Sticky session will only work if you have more than one upstream endpoint configured.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<script>
|
||||
$('#matchingKeywordExamplesAccordion').accordion();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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){
|
||||
|
||||
@@ -110,10 +110,19 @@
|
||||
Advance Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="tlsMinVer" class="ui toggle notloopbackOnly tlsEnabledOnly checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox">
|
||||
<label>Force TLS v1.2 or above<br>
|
||||
<small>(Enhance security, but not compatible with legacy browsers)</small></label>
|
||||
<div id="tlsMinVer" class="ui notloopbackOnly tlsEnabledOnly" style="margin-top: 0.6em;">
|
||||
<div style="display: flex; align-items: center; gap: 1em;">
|
||||
<select id="tlsVersionSelect" class="ui dropdown">
|
||||
<option value="1.0">TLS v1.0</option>
|
||||
<option value="1.1">TLS v1.1</option>
|
||||
<option value="1.2">TLS v1.2</option>
|
||||
<option value="1.3">TLS v1.3</option>
|
||||
</select>
|
||||
<p for="tlsVersionSelect" style="margin: 0;">
|
||||
Minimum TLS Version<br>
|
||||
<small>(Enhance security, but may not be compatible with legacy browsers)</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div id="developmentMode" class="ui toggle checkbox" style="margin-top: 0.6em;">
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -74,14 +74,6 @@
|
||||
<small>Forward UDP request on this listening socket</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" tabindex="0" name="useProxyProtocol" class="hidden">
|
||||
<label>Enable Proxy Protocol V1<br>
|
||||
<small>Enable TCP Proxy Protocol header V1</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" tabindex="0" name="enableLogging" class="hidden">
|
||||
@@ -90,6 +82,18 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Proxy Protocol</label>
|
||||
<select name="proxyProtocolVersion" class="ui dropdown">
|
||||
<option value="0">Disabled</option>
|
||||
<option value="1">Proxy Protocol V1</option>
|
||||
<option value="2">Proxy Protocol V2</option>
|
||||
</select>
|
||||
<small>Select Proxy Protocol v1 / v2 to use (if any)</small>
|
||||
<div id="proxyProtocolUdpWarning" style="display:none; color:#bd7100; margin-top:0.5em;">
|
||||
<i class="exclamation triangle icon"></i> Proxy Protocol V1 is not supported for UDP. The proxy protocol header will not be included in UDP packets if selected.
|
||||
</div>
|
||||
</div>
|
||||
<button id="addStreamProxyButton" class="ui basic button" type="submit"><i class="ui green add icon"></i> Create</button>
|
||||
<button id="editStreamProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event, this);" style="display:none;"><i class="ui green check icon"></i> Update</button>
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelStreamProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
@@ -99,6 +103,21 @@
|
||||
<script>
|
||||
let editingStreamProxyConfigUUID = ""; //The current editing TCP Proxy config UUID
|
||||
|
||||
// Show/hide warning for Proxy Protocol V1 + UDP
|
||||
function updateProxyProtocolUdpWarning() {
|
||||
const proxyProtocolVersion = $("#streamProxyForm select[name=proxyProtocolVersion]").val();
|
||||
const useUDP = $("#streamProxyForm input[name=useUDP]")[0].checked;
|
||||
if (proxyProtocolVersion === "1" && useUDP) {
|
||||
$("#proxyProtocolUdpWarning").show();
|
||||
} else {
|
||||
$("#proxyProtocolUdpWarning").hide();
|
||||
}
|
||||
}
|
||||
|
||||
$("#streamProxyForm select[name=proxyProtocolVersion]").on("change", updateProxyProtocolUdpWarning);
|
||||
$("#streamProxyForm input[name=useUDP]").on("change", updateProxyProtocolUdpWarning);
|
||||
$(document).ready(updateProxyProtocolUdpWarning);
|
||||
|
||||
$("#streamProxyForm .dropdown").dropdown();
|
||||
$('#streamProxyForm').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
@@ -138,7 +157,7 @@
|
||||
|
||||
function clearStreamProxyAddEditForm(){
|
||||
$('#streamProxyForm').find('input:not([type=checkbox]), select').val('');
|
||||
$('#streamProxyForm select').dropdown('clear');
|
||||
$('#streamProxyForm select[name=proxyProtocolVersion]').dropdown('set selected', '0');
|
||||
$("#streamProxyForm input[name=timeout]").val(10);
|
||||
$("#streamProxyForm .toggle.checkbox").checkbox("set unchecked");
|
||||
}
|
||||
@@ -212,8 +231,10 @@
|
||||
modeText.push("UDP")
|
||||
}
|
||||
|
||||
if (config.UseProxyProtocol){
|
||||
modeText.push("ProxyProtocol V1")
|
||||
if (config.ProxyProtocolVersion === 1) {
|
||||
modeText.push("ProxyProtocol V1");
|
||||
} else if (config.ProxyProtocolVersion === 2) {
|
||||
modeText.push("ProxyProtocol V2");
|
||||
}
|
||||
|
||||
modeText = modeText.join(" & ")
|
||||
@@ -277,13 +298,8 @@
|
||||
$(checkboxEle).checkbox("set unchecked");
|
||||
}
|
||||
return;
|
||||
}else if (key == "UseProxyProtocol"){
|
||||
let checkboxEle = $("#streamProxyForm input[name=useProxyProtocol]").parent();
|
||||
if (value === true){
|
||||
$(checkboxEle).checkbox("set checked");
|
||||
}else{
|
||||
$(checkboxEle).checkbox("set unchecked");
|
||||
}
|
||||
}else if (key == "ProxyProtocolVersion") {
|
||||
$("#streamProxyForm select[name=proxyProtocolVersion]").dropdown("set selected", value);
|
||||
return;
|
||||
}else if (key == "EnableLogging"){
|
||||
let checkboxEle = $("#streamProxyForm input[name=enableLogging]").parent();
|
||||
@@ -342,7 +358,7 @@
|
||||
proxyAddr: $("#streamProxyForm input[name=proxyAddr]").val().trim(),
|
||||
useTCP: $("#streamProxyForm input[name=useTCP]")[0].checked ,
|
||||
useUDP: $("#streamProxyForm input[name=useUDP]")[0].checked ,
|
||||
useProxyProtocol: $("#streamProxyForm input[name=useProxyProtocol]")[0].checked ,
|
||||
proxyProtocolVersion: parseInt($("#streamProxyForm select[name=proxyProtocolVersion]").val(), 10),
|
||||
enableLogging: $("#streamProxyForm input[name=enableLogging]")[0].checked ,
|
||||
timeout: parseInt($("#streamProxyForm input[name=timeout]").val().trim()),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user