mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-02 19:36:54 +02:00
Added cert resolve viewer
- Added certificate resolve viewer on HTTP proxy rule editor - Exposed SNI options (wip) - Code optimize
This commit is contained in:
@@ -34,6 +34,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
|
||||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
|
||||||
|
authRouter.HandleFunc("/api/proxy/setTlsConfig", ReverseProxyHandleSetTlsConfig)
|
||||||
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
|
authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname)
|
||||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||||
@@ -79,6 +80,7 @@ func RegisterTLSAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
authRouter.HandleFunc("/api/cert/listdomains", handleListDomains)
|
||||||
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
|
||||||
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
|
||||||
|
authRouter.HandleFunc("/api/cert/resolve", handleCertTryResolve)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
// Register the APIs for Authentication handlers like Forward Auth and OAUTH2
|
||||||
|
81
src/cert.go
81
src/cert.go
@@ -360,6 +360,87 @@ func handleCertUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
fmt.Fprintln(w, "File upload successful!")
|
fmt.Fprintln(w, "File upload successful!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleCertTryResolve(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// get the domain
|
||||||
|
domain, err := utils.GetPara(r, "domain")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "invalid domain given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the proxy rule, the pass in domain value must be root or matching domain
|
||||||
|
proxyRule, err := dynamicProxyRouter.GetProxyEndpointById(domain, false)
|
||||||
|
if err != nil {
|
||||||
|
//Try to resolve the domain via alias
|
||||||
|
proxyRule, err = dynamicProxyRouter.GetProxyEndpointByAlias(domain)
|
||||||
|
if err != nil {
|
||||||
|
//No matching rule found
|
||||||
|
utils.SendErrorResponse(w, "proxy rule not found for domain: "+domain)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// list all the alias domains for this rule
|
||||||
|
allDomains := []string{proxyRule.RootOrMatchingDomain}
|
||||||
|
aliasDomains := []string{}
|
||||||
|
for _, alias := range proxyRule.MatchingDomainAlias {
|
||||||
|
if alias != "" {
|
||||||
|
aliasDomains = append(aliasDomains, alias)
|
||||||
|
allDomains = append(allDomains, alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to resolve the domain
|
||||||
|
domainKeyPairs := map[string]string{}
|
||||||
|
for _, thisDomain := range allDomains {
|
||||||
|
pubkey, prikey, err := tlsCertManager.GetCertificateByHostname(thisDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Make sure pubkey and private key are not empty
|
||||||
|
if pubkey == "" || prikey == "" {
|
||||||
|
domainKeyPairs[thisDomain] = ""
|
||||||
|
} else {
|
||||||
|
//Store the key pair
|
||||||
|
keyname := strings.TrimSuffix(filepath.Base(pubkey), filepath.Ext(pubkey))
|
||||||
|
if keyname == "localhost" {
|
||||||
|
//Internal certs like localhost should not be used
|
||||||
|
//report as "fallback" key
|
||||||
|
keyname = "fallback certificate"
|
||||||
|
}
|
||||||
|
domainKeyPairs[thisDomain] = keyname
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//A domain must be UseDNSValidation if it is a wildcard domain or its alias is a wildcard domain
|
||||||
|
useDNSValidation := strings.HasPrefix(proxyRule.RootOrMatchingDomain, "*")
|
||||||
|
for _, alias := range aliasDomains {
|
||||||
|
if strings.HasPrefix(alias, "*") || strings.HasPrefix(domain, "*") {
|
||||||
|
useDNSValidation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CertInfo struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
AliasDomains []string `json:"alias_domains"`
|
||||||
|
DomainKeyPair map[string]string `json:"domain_key_pair"`
|
||||||
|
UseDNSValidation bool `json:"use_dns_validation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &CertInfo{
|
||||||
|
Domain: proxyRule.RootOrMatchingDomain,
|
||||||
|
AliasDomains: aliasDomains,
|
||||||
|
DomainKeyPair: domainKeyPairs,
|
||||||
|
UseDNSValidation: useDNSValidation,
|
||||||
|
}
|
||||||
|
|
||||||
|
js, _ := json.Marshal(result)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
// Handle cert remove
|
// Handle cert remove
|
||||||
func handleCertRemove(w http.ResponseWriter, r *http.Request) {
|
func handleCertRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
domain, err := utils.PostPara(r, "domain")
|
domain, err := utils.PostPara(r, "domain")
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||||
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,12 +60,18 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
thisConfigEndpoint.Tags = []string{}
|
thisConfigEndpoint.Tags = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Make sure the TLS options are not nil
|
||||||
|
if thisConfigEndpoint.TlsOptions == nil {
|
||||||
|
thisConfigEndpoint.TlsOptions = tlscert.GetDefaultHostSpecificTlsBehavior()
|
||||||
|
}
|
||||||
|
|
||||||
//Matching domain not set. Assume root
|
//Matching domain not set. Assume root
|
||||||
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
if thisConfigEndpoint.RootOrMatchingDomain == "" {
|
||||||
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
thisConfigEndpoint.RootOrMatchingDomain = "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeRoot {
|
switch thisConfigEndpoint.ProxyType {
|
||||||
|
case dynamicproxy.ProxyTypeRoot:
|
||||||
//This is a root config file
|
//This is a root config file
|
||||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
|
|
||||||
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
dynamicProxyRouter.SetProxyRouteAsRoot(rootProxyEndpoint)
|
||||||
|
|
||||||
} else if thisConfigEndpoint.ProxyType == dynamicproxy.ProxyTypeHost {
|
case dynamicproxy.ProxyTypeHost:
|
||||||
//This is a host config file
|
//This is a host config file
|
||||||
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
readyProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisConfigEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,7 +88,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyProxyEndpoint)
|
||||||
} else {
|
default:
|
||||||
return errors.New("not supported proxy type")
|
return errors.New("not supported proxy type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -105,3 +106,49 @@ func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain str
|
|||||||
|
|
||||||
return targetEpt.Remove()
|
return targetEpt.Remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProxyEndpointById retrieves a proxy endpoint by its ID from the Router's ProxyEndpoints map.
|
||||||
|
// It returns the ProxyEndpoint if found, or an error if not found.
|
||||||
|
func (h *Router) GetProxyEndpointById(searchingDomain string, includeAlias bool) (*ProxyEndpoint, error) {
|
||||||
|
var found *ProxyEndpoint
|
||||||
|
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
|
proxy, ok := value.(*ProxyEndpoint)
|
||||||
|
if ok && (proxy.RootOrMatchingDomain == searchingDomain || (includeAlias && utils.StringInArray(proxy.MatchingDomainAlias, searchingDomain))) {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
}
|
||||||
|
return true // continue iteration
|
||||||
|
})
|
||||||
|
if found != nil {
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("proxy rule with given id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Router) GetProxyEndpointByAlias(alias string) (*ProxyEndpoint, error) {
|
||||||
|
var found *ProxyEndpoint
|
||||||
|
h.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||||
|
proxy, ok := value.(*ProxyEndpoint)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
//Also check for wildcard aliases that matches the alias
|
||||||
|
for _, thisAlias := range proxy.MatchingDomainAlias {
|
||||||
|
if ok && thisAlias == alias {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
} else if ok && strings.HasPrefix(thisAlias, "*") {
|
||||||
|
//Check if the alias matches a wildcard alias
|
||||||
|
if strings.HasSuffix(alias, thisAlias[1:]) {
|
||||||
|
found = proxy
|
||||||
|
return false // stop iteration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true // continue iteration
|
||||||
|
})
|
||||||
|
if found != nil {
|
||||||
|
return found, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("proxy rule with given alias not found")
|
||||||
|
}
|
||||||
|
@@ -175,7 +175,8 @@ type ProxyEndpoint struct {
|
|||||||
Disabled bool //If the rule is disabled
|
Disabled bool //If the rule is disabled
|
||||||
|
|
||||||
//Inbound TLS/SSL Related
|
//Inbound TLS/SSL Related
|
||||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||||
|
TlsOptions *tlscert.HostSpecificTlsBehavior //TLS options for this endpoint, if nil, use global TLS options
|
||||||
|
|
||||||
//Virtual Directories
|
//Virtual Directories
|
||||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||||
|
@@ -20,11 +20,21 @@ type CertCache struct {
|
|||||||
PriKey string
|
PriKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HostSpecificTlsBehavior struct {
|
||||||
|
DisableSNI bool //If SNI is enabled for this server name
|
||||||
|
DisableLegacyCertificateMatching bool //If legacy certificate matching is disabled for this server name
|
||||||
|
EnableAutoHTTPS bool //If auto HTTPS is enabled for this server name
|
||||||
|
PreferredCertificate string //Preferred certificate for this server name, if empty, use the first matching certificate
|
||||||
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
CertStore string //Path where all the certs are stored
|
CertStore string //Path where all the certs are stored
|
||||||
LoadedCerts []*CertCache //A list of loaded certs
|
LoadedCerts []*CertCache //A list of loaded certs
|
||||||
Logger *logger.Logger //System wide logger for debug mesage
|
Logger *logger.Logger //System wide logger for debug mesage
|
||||||
verbal bool
|
|
||||||
|
/* External handlers */
|
||||||
|
hostSpecificTlsBehavior func(serverName string) (*HostSpecificTlsBehavior, error) // Function to get host specific TLS behavior, if nil, use global TLS options
|
||||||
|
verbal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed localhost.pem localhost.key
|
//go:embed localhost.pem localhost.key
|
||||||
@@ -50,10 +60,11 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
|||||||
}
|
}
|
||||||
|
|
||||||
thisManager := Manager{
|
thisManager := Manager{
|
||||||
CertStore: certStore,
|
CertStore: certStore,
|
||||||
LoadedCerts: []*CertCache{},
|
LoadedCerts: []*CertCache{},
|
||||||
verbal: verbal,
|
hostSpecificTlsBehavior: defaultHostSpecificTlsBehavior, //Default to no SNI and no auto HTTPS
|
||||||
Logger: logger,
|
verbal: verbal,
|
||||||
|
Logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := thisManager.UpdateLoadedCertList()
|
err := thisManager.UpdateLoadedCertList()
|
||||||
@@ -64,6 +75,21 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
|||||||
return &thisManager, nil
|
return &thisManager, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default host specific TLS behavior
|
||||||
|
// This is used when no specific TLS behavior is defined for a server name
|
||||||
|
func GetDefaultHostSpecificTlsBehavior() *HostSpecificTlsBehavior {
|
||||||
|
return &HostSpecificTlsBehavior{
|
||||||
|
DisableSNI: false,
|
||||||
|
DisableLegacyCertificateMatching: false,
|
||||||
|
EnableAutoHTTPS: false,
|
||||||
|
PreferredCertificate: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultHostSpecificTlsBehavior(serverName string) (*HostSpecificTlsBehavior, error) {
|
||||||
|
return GetDefaultHostSpecificTlsBehavior(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Update domain mapping from file
|
// Update domain mapping from file
|
||||||
func (m *Manager) UpdateLoadedCertList() error {
|
func (m *Manager) UpdateLoadedCertList() error {
|
||||||
//Get a list of certificates from file
|
//Get a list of certificates from file
|
||||||
@@ -161,24 +187,11 @@ func (m *Manager) ListCerts() ([]string, error) {
|
|||||||
|
|
||||||
// Get a certificate from disk where its certificate matches with the helloinfo
|
// Get a certificate from disk where its certificate matches with the helloinfo
|
||||||
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
//Check if the domain corrisponding cert exists
|
//Look for the certificate by hostname
|
||||||
pubKey := "./tmp/localhost.pem"
|
pubKey, priKey, err := m.GetCertificateByHostname(helloInfo.ServerName)
|
||||||
priKey := "./tmp/localhost.key"
|
if err != nil {
|
||||||
|
m.Logger.PrintAndLog("tls-router", "Failed to get certificate for "+helloInfo.ServerName, err)
|
||||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".pem")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
return nil, err
|
||||||
//Direct hit
|
|
||||||
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".pem")
|
|
||||||
priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
|
|
||||||
} else if m.CertMatchExists(helloInfo.ServerName) {
|
|
||||||
//Use x509
|
|
||||||
pubKey, priKey = m.GetCertByX509CNHostname(helloInfo.ServerName)
|
|
||||||
} else {
|
|
||||||
//Fallback to legacy method of matching certificates
|
|
||||||
if m.DefaultCertExists() {
|
|
||||||
//Use default.pem and default.key
|
|
||||||
pubKey = filepath.Join(m.CertStore, "default.pem")
|
|
||||||
priKey = filepath.Join(m.CertStore, "default.key")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load the cert and serve it
|
//Load the cert and serve it
|
||||||
@@ -190,6 +203,51 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
|||||||
return &cer, nil
|
return &cer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCertificateByHostname returns the certificate and private key for a given hostname
|
||||||
|
func (m *Manager) GetCertificateByHostname(hostname string) (string, string, error) {
|
||||||
|
//Check if the domain corrisponding cert exists
|
||||||
|
pubKey := "./tmp/localhost.pem"
|
||||||
|
priKey := "./tmp/localhost.key"
|
||||||
|
|
||||||
|
tlsBehavior, err := m.hostSpecificTlsBehavior(hostname)
|
||||||
|
if err != nil {
|
||||||
|
tlsBehavior, _ = defaultHostSpecificTlsBehavior(hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsBehavior.DisableSNI && tlsBehavior.PreferredCertificate != "" &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")) &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")) {
|
||||||
|
//User setup a Preferred certificate, use the preferred certificate directly
|
||||||
|
pubKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, tlsBehavior.PreferredCertificate+".key")
|
||||||
|
} else {
|
||||||
|
if !tlsBehavior.DisableLegacyCertificateMatching &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, hostname+".pem")) &&
|
||||||
|
utils.FileExists(filepath.Join(m.CertStore, hostname+".key")) {
|
||||||
|
//Legacy filename matching, use the file names directly
|
||||||
|
//This is the legacy method of matching certificates, it will match the file names directly
|
||||||
|
//This is used for compatibility with Zoraxy v2 setups
|
||||||
|
pubKey = filepath.Join(m.CertStore, hostname+".pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, hostname+".key")
|
||||||
|
} else if !tlsBehavior.DisableSNI &&
|
||||||
|
m.CertMatchExists(hostname) {
|
||||||
|
//SNI scan match, find the first matching certificate
|
||||||
|
pubKey, priKey = m.GetCertByX509CNHostname(hostname)
|
||||||
|
} else if tlsBehavior.EnableAutoHTTPS {
|
||||||
|
//Get certificate from CA, WIP
|
||||||
|
//TODO: Implement AutoHTTPS
|
||||||
|
} else {
|
||||||
|
//Fallback to legacy method of matching certificates
|
||||||
|
if m.DefaultCertExists() {
|
||||||
|
//Use default.pem and default.key
|
||||||
|
pubKey = filepath.Join(m.CertStore, "default.pem")
|
||||||
|
priKey = filepath.Join(m.CertStore, "default.key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pubKey, priKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check if both the default cert public key and private key exists
|
// Check if both the default cert public key and private key exists
|
||||||
func (m *Manager) DefaultCertExists() bool {
|
func (m *Manager) DefaultCertExists() bool {
|
||||||
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
return utils.FileExists(filepath.Join(m.CertStore, "default.pem")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
|
||||||
@@ -220,7 +278,6 @@ func (m *Manager) RemoveCert(domain string) error {
|
|||||||
|
|
||||||
//Update the cert list
|
//Update the cert list
|
||||||
m.UpdateLoadedCertList()
|
m.UpdateLoadedCertList()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import (
|
|||||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||||
"imuslab.com/zoraxy/mod/netutils"
|
"imuslab.com/zoraxy/mod/netutils"
|
||||||
|
"imuslab.com/zoraxy/mod/tlscert"
|
||||||
"imuslab.com/zoraxy/mod/uptime"
|
"imuslab.com/zoraxy/mod/uptime"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
@@ -334,7 +335,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
tags = filteredTags
|
tags = filteredTags
|
||||||
|
|
||||||
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
|
var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
|
||||||
if eptype == "host" {
|
switch eptype {
|
||||||
|
case "host":
|
||||||
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.SendErrorResponse(w, "hostname not defined")
|
utils.SendErrorResponse(w, "hostname not defined")
|
||||||
@@ -415,7 +417,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
|
dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
|
||||||
proxyEndpointCreated = &thisProxyEndpoint
|
proxyEndpointCreated = &thisProxyEndpoint
|
||||||
} else if eptype == "root" {
|
case "root":
|
||||||
//Get the default site options and target
|
//Get the default site options and target
|
||||||
dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
|
dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -469,7 +471,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
proxyEndpointCreated = &rootRoutingEndpoint
|
proxyEndpointCreated = &rootRoutingEndpoint
|
||||||
} else {
|
default:
|
||||||
//Invalid eptype
|
//Invalid eptype
|
||||||
utils.SendErrorResponse(w, "invalid endpoint type")
|
utils.SendErrorResponse(w, "invalid endpoint type")
|
||||||
return
|
return
|
||||||
@@ -677,6 +679,65 @@ func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReverseProxyHandleSetTlsConfig(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
utils.SendErrorResponse(w, "Method not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rootnameOrMatchingDomain, err := utils.PostPara(r, "ep")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid ep given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := utils.PostPara(r, "tlsConfig")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid TLS config given")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig = strings.TrimSpace(tlsConfig)
|
||||||
|
if tlsConfig == "" {
|
||||||
|
utils.SendErrorResponse(w, "TLS config cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newTlsConfig := &tlscert.HostSpecificTlsBehavior{}
|
||||||
|
err = json.Unmarshal([]byte(tlsConfig), newTlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Invalid TLS config given: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Load the target endpoint
|
||||||
|
ept, err := dynamicProxyRouter.LoadProxy(rootnameOrMatchingDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ept.TlsOptions = newTlsConfig
|
||||||
|
|
||||||
|
//Prepare to replace the current routing rule
|
||||||
|
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(ept)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
|
||||||
|
|
||||||
|
//Save it to file
|
||||||
|
err = SaveReverseProxyConfig(ept)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Failed to save TLS config: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyHandleSetHostname(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
utils.SendErrorResponse(w, "Method not supported")
|
utils.SendErrorResponse(w, "Method not supported")
|
||||||
@@ -1015,6 +1076,7 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
|||||||
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
js, err := json.Marshal(dynamicProxyRouter)
|
js, err := json.Marshal(dynamicProxyRouter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("proxy-config", "Unable to marshal status data", err)
|
||||||
utils.SendErrorResponse(w, "Unable to marshal status data")
|
utils.SendErrorResponse(w, "Unable to marshal status data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -338,8 +338,37 @@
|
|||||||
<!-- TLS / SSL -->
|
<!-- TLS / SSL -->
|
||||||
<div class="rpconfig_content" rpcfg="ssl">
|
<div class="rpconfig_content" rpcfg="ssl">
|
||||||
<div class="ui segment">
|
<div class="ui segment">
|
||||||
<p>Work In Progress <br>
|
<p>The table below shows which certificate will be served by Zoraxy when a client request the following hostnames.</p>
|
||||||
Please use the outer-most menu TLS / SSL tab for now. </p>
|
<table class="ui celled small compact table Tls_resolve_list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Hostname</th>
|
||||||
|
<th>Resolve to Certificate</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Rows will be dynamically populated -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableSNI">
|
||||||
|
<label>Enable SNI<br>
|
||||||
|
<small>Resolve Server Name Indication (SNI) and automatically select a certificate</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableLegacyCertificateMatching">
|
||||||
|
<label>Enable Legacy Certificate Matching<br>
|
||||||
|
<small>Use legacy filename / hostname matching for loading certificates</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
|
<input type="checkbox" class="Tls_EnableAutoHTTPS">
|
||||||
|
<label>Enable Auto HTTPS<br>
|
||||||
|
<small>Automatically request a certificate for the domain</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
|
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -711,6 +740,66 @@
|
|||||||
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveTlsConfigs(uuid){
|
||||||
|
let enableSNI = $("#httprpEditModal .Tls_EnableSNI")[0].checked;
|
||||||
|
let enableLegacyCertificateMatching = $("#httprpEditModal .Tls_EnableLegacyCertificateMatching")[0].checked;
|
||||||
|
let enableAutoHTTPS = $("#httprpEditModal .Tls_EnableAutoHTTPS")[0].checked;
|
||||||
|
let newTlsOption = {
|
||||||
|
"DisableSNI": !enableSNI,
|
||||||
|
"DisableLegacyCertificateMatching": !enableLegacyCertificateMatching,
|
||||||
|
"EnableAutoHTTPS": enableAutoHTTPS
|
||||||
|
}
|
||||||
|
$.cjax({
|
||||||
|
url: "/api/proxy/setTlsConfig",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"ep": uuid,
|
||||||
|
"tlsConfig": JSON.stringify(newTlsOption)
|
||||||
|
},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error !== undefined){
|
||||||
|
msgbox(data.error, false, 3000);
|
||||||
|
}else{
|
||||||
|
msgbox("TLS Config updated");
|
||||||
|
}
|
||||||
|
updateTlsResolveList(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTlsResolveList(uuid){
|
||||||
|
let editor = $("#httprpEditModalWrapper");
|
||||||
|
//Update the TLS resolve list
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/cert/resolve?domain=" + uuid,
|
||||||
|
method: "GET",
|
||||||
|
success: function(data) {
|
||||||
|
// Populate the TLS resolve list
|
||||||
|
let resolveList = editor.find(".Tls_resolve_list tbody");
|
||||||
|
resolveList.empty(); // Clear existing entries
|
||||||
|
let primaryDomain = data.domain;
|
||||||
|
let aliasDomains = data.alias_domains || [];
|
||||||
|
let certMap = data.domain_key_pair;
|
||||||
|
|
||||||
|
// Add primary domain entry
|
||||||
|
resolveList.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${primaryDomain}</td>
|
||||||
|
<td>${certMap[primaryDomain] || "Fallback Certificate"}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
aliasDomains.forEach(alias => {
|
||||||
|
resolveList.append(`
|
||||||
|
<tr>
|
||||||
|
<td>${alias}</td>
|
||||||
|
<td>${certMap[alias] || "Fallback Certificate"}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function saveProxyInlineEdit(uuid){
|
function saveProxyInlineEdit(uuid){
|
||||||
let editor = $("#httprpEditModal");
|
let editor = $("#httprpEditModal");
|
||||||
|
|
||||||
@@ -1245,6 +1334,20 @@
|
|||||||
editor.find(".RateLimit").off("change").on("change", rateLimitChangeEvent);
|
editor.find(".RateLimit").off("change").on("change", rateLimitChangeEvent);
|
||||||
|
|
||||||
/* ------------ TLS ------------ */
|
/* ------------ TLS ------------ */
|
||||||
|
updateTlsResolveList(uuid);
|
||||||
|
editor.find(".Tls_EnableSNI").prop("checked", !subd.TlsOptions.DisableSNI);
|
||||||
|
editor.find(".Tls_EnableLegacyCertificateMatching").prop("checked", !subd.TlsOptions.DisableLegacyCertificateMatching);
|
||||||
|
editor.find(".Tls_EnableAutoHTTPS").prop("checked", !!subd.TlsOptions.EnableAutoHTTPS);
|
||||||
|
|
||||||
|
editor.find(".Tls_EnableSNI").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
editor.find(".Tls_EnableLegacyCertificateMatching").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
editor.find(".Tls_EnableAutoHTTPS").off("change").on("change", function() {
|
||||||
|
saveTlsConfigs(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
/* ------------ Tags ------------ */
|
/* ------------ Tags ------------ */
|
||||||
(()=>{
|
(()=>{
|
||||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user