mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-08 06:08:30 +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:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -105,3 +106,49 @@ func (router *Router) RemoveProxyEndpointByRootname(rootnameOrMatchingDomain str
|
||||
|
||||
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
|
||||
|
||||
//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
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
|
@@ -20,11 +20,21 @@ type CertCache struct {
|
||||
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 {
|
||||
CertStore string //Path where all the certs are stored
|
||||
LoadedCerts []*CertCache //A list of loaded certs
|
||||
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
|
||||
@@ -50,10 +60,11 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
||||
}
|
||||
|
||||
thisManager := Manager{
|
||||
CertStore: certStore,
|
||||
LoadedCerts: []*CertCache{},
|
||||
verbal: verbal,
|
||||
Logger: logger,
|
||||
CertStore: certStore,
|
||||
LoadedCerts: []*CertCache{},
|
||||
hostSpecificTlsBehavior: defaultHostSpecificTlsBehavior, //Default to no SNI and no auto HTTPS
|
||||
verbal: verbal,
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
err := thisManager.UpdateLoadedCertList()
|
||||
@@ -64,6 +75,21 @@ func NewManager(certStore string, verbal bool, logger *logger.Logger) (*Manager,
|
||||
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
|
||||
func (m *Manager) UpdateLoadedCertList() error {
|
||||
//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
|
||||
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
//Check if the domain corrisponding cert exists
|
||||
pubKey := "./tmp/localhost.pem"
|
||||
priKey := "./tmp/localhost.key"
|
||||
|
||||
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".pem")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
|
||||
//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")
|
||||
}
|
||||
//Look for the certificate by hostname
|
||||
pubKey, priKey, err := m.GetCertificateByHostname(helloInfo.ServerName)
|
||||
if err != nil {
|
||||
m.Logger.PrintAndLog("tls-router", "Failed to get certificate for "+helloInfo.ServerName, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Load the cert and serve it
|
||||
@@ -190,6 +203,51 @@ func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, err
|
||||
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
|
||||
func (m *Manager) DefaultCertExists() bool {
|
||||
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
|
||||
m.UpdateLoadedCertList()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user