fix: add nil checks for TLS config in sing-box inbound builders
This commit is contained in:
@@ -451,7 +451,9 @@ func buildVMess(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF
|
|||||||
applyMultiplex(base, nc)
|
applyMultiplex(base, nc)
|
||||||
|
|
||||||
if nc.TLS == 1 {
|
if nc.TLS == 1 {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base
|
return base
|
||||||
@@ -478,7 +480,9 @@ func buildVLESS(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF
|
|||||||
applyMultiplex(base, nc)
|
applyMultiplex(base, nc)
|
||||||
|
|
||||||
if nc.TLS == 1 {
|
if nc.TLS == 1 {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
} else if nc.TLS == 2 {
|
} else if nc.TLS == 2 {
|
||||||
base["tls"] = buildRealityConfig(nc)
|
base["tls"] = buildRealityConfig(nc)
|
||||||
}
|
}
|
||||||
@@ -504,16 +508,22 @@ func buildTrojan(base M, nc *panel.NodeConfig, users []panel.User, certFile, key
|
|||||||
applyMultiplex(base, nc)
|
applyMultiplex(base, nc)
|
||||||
|
|
||||||
if nc.TLS == 1 {
|
if nc.TLS == 1 {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
} else if nc.TLS == 2 {
|
} else if nc.TLS == 2 {
|
||||||
base["tls"] = buildRealityConfig(nc)
|
base["tls"] = buildRealityConfig(nc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trojan requires TLS or Reality to be enabled.
|
// If still no TLS/Reality but cert paths exist, enable TLS (panel may omit tls=1).
|
||||||
// If the panel didn't explicitly set TLS=1 or TLS=2, but we have certs,
|
|
||||||
// we should enable a default TLS config to ensure the inbound can start.
|
|
||||||
if _, ok := base["tls"]; !ok {
|
if _, ok := base["tls"]; !ok {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if base["tls"] == nil {
|
||||||
|
nlog.Core().Warn("trojan inbound has no TLS (no certificate paths and no Reality); sing-box may refuse to start until cert_mode provides cert/key or Reality is configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
return base
|
return base
|
||||||
@@ -558,6 +568,10 @@ func buildHysteria(base M, nc *panel.NodeConfig, users []panel.User, certFile, k
|
|||||||
}
|
}
|
||||||
|
|
||||||
tls := buildTLSConfig(nc, certFile, keyFile)
|
tls := buildTLSConfig(nc, certFile, keyFile)
|
||||||
|
if tls == nil {
|
||||||
|
nlog.Core().Warn("hysteria requires TLS certificate files on disk; configure cert_mode (self, file, http, dns, or content). Sing-box will not start this inbound without tls.")
|
||||||
|
return base
|
||||||
|
}
|
||||||
// Hysteria/Hysteria2 uses QUIC and requires ALPN; default to h3 if not set.
|
// Hysteria/Hysteria2 uses QUIC and requires ALPN; default to h3 if not set.
|
||||||
if _, ok := tls["alpn"]; !ok {
|
if _, ok := tls["alpn"]; !ok {
|
||||||
tls["alpn"] = []string{"h3"}
|
tls["alpn"] = []string{"h3"}
|
||||||
@@ -584,6 +598,10 @@ func buildTUIC(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFi
|
|||||||
}
|
}
|
||||||
|
|
||||||
tls := buildTLSConfig(nc, certFile, keyFile)
|
tls := buildTLSConfig(nc, certFile, keyFile)
|
||||||
|
if tls == nil {
|
||||||
|
nlog.Core().Warn("tuic requires TLS certificate files on disk; configure cert_mode (self, file, http, dns, or content). Sing-box will not start this inbound without tls.")
|
||||||
|
return base
|
||||||
|
}
|
||||||
// TUIC requires ALPN for QUIC negotiation; default to h3 if not set by panel.
|
// TUIC requires ALPN for QUIC negotiation; default to h3 if not set by panel.
|
||||||
if _, ok := tls["alpn"]; !ok {
|
if _, ok := tls["alpn"]; !ok {
|
||||||
tls["alpn"] = []string{"h3"}
|
tls["alpn"] = []string{"h3"}
|
||||||
@@ -608,7 +626,11 @@ func buildAnyTLS(base M, nc *panel.NodeConfig, users []panel.User, certFile, key
|
|||||||
base["padding_scheme"] = string(nc.PaddingScheme)
|
base["padding_scheme"] = string(nc.PaddingScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
} else {
|
||||||
|
nlog.Core().Warn("anytls requires TLS certificate files on disk; configure cert_mode (self, file, http, dns, or content). Sing-box will not start this inbound without tls.")
|
||||||
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +647,9 @@ func buildNaive(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyF
|
|||||||
base["users"] = userList
|
base["users"] = userList
|
||||||
|
|
||||||
if nc.TLS == 1 {
|
if nc.TLS == 1 {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
@@ -658,7 +682,9 @@ func buildHTTP(base M, nc *panel.NodeConfig, users []panel.User, certFile, keyFi
|
|||||||
|
|
||||||
applyProxyProtocol(base, nc)
|
applyProxyProtocol(base, nc)
|
||||||
if nc.TLS == 1 {
|
if nc.TLS == 1 {
|
||||||
base["tls"] = buildTLSConfig(nc, certFile, keyFile)
|
if tls := buildTLSConfig(nc, certFile, keyFile); tls != nil {
|
||||||
|
base["tls"] = tls
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
@@ -715,7 +741,15 @@ func applyTransport(base M, nc *panel.NodeConfig) {
|
|||||||
base["transport"] = transport
|
base["transport"] = transport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildTLSConfig returns sing-box inbound TLS options when certificate and key paths exist.
|
||||||
|
// If either path is missing, it returns nil: the inbound should run without a TLS layer
|
||||||
|
// (e.g. TLS terminated at nginx/CDN while the panel still shows tls=1).
|
||||||
|
// Sing-box does not accept a magic "self-signed" path — it tries to open that name as a file.
|
||||||
func buildTLSConfig(nc *panel.NodeConfig, certFile, keyFile string) M {
|
func buildTLSConfig(nc *panel.NodeConfig, certFile, keyFile string) M {
|
||||||
|
if certFile == "" || keyFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
tls := M{"enabled": true}
|
tls := M{"enabled": true}
|
||||||
|
|
||||||
serverName := nc.ServerName
|
serverName := nc.ServerName
|
||||||
@@ -735,15 +769,8 @@ func buildTLSConfig(nc *panel.NodeConfig, certFile, keyFile string) M {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if certFile != "" && keyFile != "" {
|
|
||||||
tls["certificate_path"] = certFile
|
tls["certificate_path"] = certFile
|
||||||
tls["key_path"] = keyFile
|
tls["key_path"] = keyFile
|
||||||
} else {
|
|
||||||
// If no real certificates are provided, but TLS is requested,
|
|
||||||
// use a self-signed certificate as fallback to prevent sing-box
|
|
||||||
// from failing with "missing certificate".
|
|
||||||
tls["certificate_path"] = "self-signed"
|
|
||||||
}
|
|
||||||
|
|
||||||
return tls
|
return tls
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,21 @@ func TestBuildInbound_VMess_WithTLS(t *testing.T) {
|
|||||||
assertMapValue(t, tls, "key_path", "/path/key.pem")
|
assertMapValue(t, tls, "key_path", "/path/key.pem")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Panel tls=1 with cert_mode none (no paths): inbound must be plain — TLS terminated at nginx/CDN.
|
||||||
|
func TestBuildInbound_VMess_TLS_PanelOn_NoCertFiles(t *testing.T) {
|
||||||
|
nc := &panel.NodeConfig{
|
||||||
|
Protocol: "vmess",
|
||||||
|
ServerPort: 8888,
|
||||||
|
Network: "ws",
|
||||||
|
TLS: 1,
|
||||||
|
ServerName: "example.com",
|
||||||
|
}
|
||||||
|
inbound := buildInbound(nc, testUsers, "", "")
|
||||||
|
if _, exists := inbound["tls"]; exists {
|
||||||
|
t.Fatal("expected no tls block when cert/key paths are empty (nginx offload)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildInbound_VMess_NoTLS(t *testing.T) {
|
func TestBuildInbound_VMess_NoTLS(t *testing.T) {
|
||||||
nc := &panel.NodeConfig{
|
nc := &panel.NodeConfig{
|
||||||
Protocol: "vmess",
|
Protocol: "vmess",
|
||||||
@@ -245,13 +260,10 @@ func TestBuildInbound_Trojan_NoTLS(t *testing.T) {
|
|||||||
TLS: 0,
|
TLS: 0,
|
||||||
}
|
}
|
||||||
inbound := buildInbound(nc, testUsers, "", "")
|
inbound := buildInbound(nc, testUsers, "", "")
|
||||||
// Trojan requires TLS; buildTrojan should force-enable self-signed TLS
|
// Without certificate paths there is no TLS layer; sing-box trojan needs certs or Reality.
|
||||||
// even when the panel sends tls=0.
|
if _, exists := inbound["tls"]; exists {
|
||||||
tls, exists := inbound["tls"].(M)
|
t.Fatal("expected no tls when cert paths are empty and tls=0")
|
||||||
if !exists {
|
|
||||||
t.Fatal("trojan with tls=0 should still get TLS (force-enabled)")
|
|
||||||
}
|
}
|
||||||
assertMapValue(t, tls, "enabled", true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildInbound_Trojan_WithTLS(t *testing.T) {
|
func TestBuildInbound_Trojan_WithTLS(t *testing.T) {
|
||||||
@@ -643,14 +655,14 @@ func TestBuildTLSConfig_WithCert(t *testing.T) {
|
|||||||
func TestBuildTLSConfig_NoCert(t *testing.T) {
|
func TestBuildTLSConfig_NoCert(t *testing.T) {
|
||||||
nc := &panel.NodeConfig{ServerName: "example.com"}
|
nc := &panel.NodeConfig{ServerName: "example.com"}
|
||||||
tls := buildTLSConfig(nc, "", "")
|
tls := buildTLSConfig(nc, "", "")
|
||||||
assertMapValue(t, tls, "enabled", true)
|
if tls != nil {
|
||||||
// Now we fallback to self-signed certificates when both cert and key are empty
|
t.Fatalf("expected nil tls config when cert paths are empty, got %+v", tls)
|
||||||
assertMapValue(t, tls, "certificate_path", "self-signed")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildTLSConfig_FallbackToHost(t *testing.T) {
|
func TestBuildTLSConfig_FallbackToHost(t *testing.T) {
|
||||||
nc := &panel.NodeConfig{Host: "fallback.com"}
|
nc := &panel.NodeConfig{Host: "fallback.com"}
|
||||||
tls := buildTLSConfig(nc, "", "")
|
tls := buildTLSConfig(nc, "/c.pem", "/k.pem")
|
||||||
assertMapValue(t, tls, "server_name", "fallback.com")
|
assertMapValue(t, tls, "server_name", "fallback.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +673,7 @@ func TestBuildTLSConfig_TLSSettingsOverride(t *testing.T) {
|
|||||||
"server_name": "override.com",
|
"server_name": "override.com",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
tls := buildTLSConfig(nc, "", "")
|
tls := buildTLSConfig(nc, "/c.pem", "/k.pem")
|
||||||
assertMapValue(t, tls, "server_name", "override.com")
|
assertMapValue(t, tls, "server_name", "override.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ func buildConfig(kcfg config.KernelConfig, nc *panel.NodeConfig, users []panel.U
|
|||||||
cfg := M{
|
cfg := M{
|
||||||
"log": M{
|
"log": M{
|
||||||
"loglevel": xrayLogLevel(kcfg.LogLevel),
|
"loglevel": xrayLogLevel(kcfg.LogLevel),
|
||||||
"error": "stdout",
|
"error": "",
|
||||||
"access": "stdout",
|
"access": "",
|
||||||
},
|
},
|
||||||
"stats": M{},
|
"stats": M{},
|
||||||
"policy": M{
|
"policy": M{
|
||||||
|
|||||||
@@ -613,7 +613,7 @@ func (x *Xray) ensureGeoData(nc *panel.NodeConfig) {
|
|||||||
// marshalConfig builds the xray JSON config and returns the raw bytes.
|
// marshalConfig builds the xray JSON config and returns the raw bytes.
|
||||||
func marshalConfig(cfg config.KernelConfig, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) ([]byte, error) {
|
func marshalConfig(cfg config.KernelConfig, nc *panel.NodeConfig, users []panel.User, certFile, keyFile string) ([]byte, error) {
|
||||||
cfgMap := buildConfig(cfg, nc, users, certFile, keyFile)
|
cfgMap := buildConfig(cfg, nc, users, certFile, keyFile)
|
||||||
data, err := json.Marshal(cfgMap)
|
data, err := json.MarshalIndent(cfgMap, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("marshal config: %w", err)
|
return nil, fmt.Errorf("marshal config: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user