1186 lines
36 KiB
Go
1186 lines
36 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSelectTransportBackendDefaultMock(t *testing.T) {
|
|
b := selectTransportBackend(TransportClient{
|
|
Kind: TransportClientSingBox,
|
|
Config: nil,
|
|
})
|
|
if b.ID() != "mock" {
|
|
t.Fatalf("expected mock backend, got %q", b.ID())
|
|
}
|
|
}
|
|
|
|
func TestSelectTransportBackendUnitInfersSystemd(t *testing.T) {
|
|
b := selectTransportBackend(TransportClient{
|
|
Kind: TransportClientPhoenix,
|
|
Config: map[string]any{
|
|
"unit": "phoenix.service",
|
|
},
|
|
})
|
|
if b.ID() != "systemd" {
|
|
t.Fatalf("expected systemd backend, got %q", b.ID())
|
|
}
|
|
}
|
|
|
|
func TestSelectTransportBackendRuntimeModeEmbeddedUnsupported(t *testing.T) {
|
|
b := selectTransportBackend(TransportClient{
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runtime_mode": "embedded",
|
|
"runner": "systemd",
|
|
},
|
|
})
|
|
if b.ID() != "unsupported" {
|
|
t.Fatalf("expected unsupported backend for embedded runtime_mode, got %q", b.ID())
|
|
}
|
|
}
|
|
|
|
func TestSelectTransportBackendRuntimeModeSidecarUnsupported(t *testing.T) {
|
|
b := selectTransportBackend(TransportClient{
|
|
Kind: TransportClientPhoenix,
|
|
Config: map[string]any{
|
|
"runtime_mode": "sidecar",
|
|
"runner": "systemd",
|
|
},
|
|
})
|
|
if b.ID() != "unsupported" {
|
|
t.Fatalf("expected unsupported backend for sidecar runtime_mode, got %q", b.ID())
|
|
}
|
|
}
|
|
|
|
func TestTransportUnsupportedRuntimeBackendReturnsUnifiedError(t *testing.T) {
|
|
client := TransportClient{
|
|
ID: "ph-1",
|
|
Kind: TransportClientPhoenix,
|
|
Config: map[string]any{
|
|
"runtime_mode": "sidecar",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
act := backend.Action(client, "start")
|
|
if act.OK {
|
|
t.Fatalf("expected unsupported runtime action to fail")
|
|
}
|
|
if act.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
|
|
t.Fatalf("unexpected action code: %#v", act)
|
|
}
|
|
prov := backend.Provision(client)
|
|
if prov.OK {
|
|
t.Fatalf("expected unsupported runtime provision to fail")
|
|
}
|
|
if prov.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
|
|
t.Fatalf("unexpected provision code: %#v", prov)
|
|
}
|
|
health := backend.Health(client)
|
|
if health.OK {
|
|
t.Fatalf("expected unsupported runtime health to fail")
|
|
}
|
|
if health.Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
|
|
t.Fatalf("unexpected health code: %#v", health)
|
|
}
|
|
if health.Status != TransportClientDegraded {
|
|
t.Fatalf("expected degraded status for unsupported runtime_mode, got %#v", health)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdActionUnitsDNSTTSSHTunnel(t *testing.T) {
|
|
client := TransportClient{
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-client.service",
|
|
"ssh_tunnel": true,
|
|
"ssh_unit": "dnstt-ssh.service",
|
|
},
|
|
}
|
|
unitsStart, code, msg := transportSystemdActionUnits(client, "start")
|
|
if code != "" || msg != "" {
|
|
t.Fatalf("unexpected action-unit error: code=%q msg=%q", code, msg)
|
|
}
|
|
if strings.Join(unitsStart, ",") != "dnstt-ssh.service,dnstt-client.service" {
|
|
t.Fatalf("unexpected start units order: %#v", unitsStart)
|
|
}
|
|
unitsStop, code, msg := transportSystemdActionUnits(client, "stop")
|
|
if code != "" || msg != "" {
|
|
t.Fatalf("unexpected action-unit error: code=%q msg=%q", code, msg)
|
|
}
|
|
if strings.Join(unitsStop, ",") != "dnstt-client.service,dnstt-ssh.service" {
|
|
t.Fatalf("unexpected stop units order: %#v", unitsStop)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendActionAndHealth(t *testing.T) {
|
|
orig := transportRunCommand
|
|
defer func() { transportRunCommand = orig }()
|
|
|
|
calls := make([]string, 0, 8)
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
calls = append(calls, cmd)
|
|
if cmd == "systemctl reset-failed dnstt-client.service" {
|
|
return "", "", 0, nil
|
|
}
|
|
if cmd == "systemctl reset-failed dnstt-ssh.service" {
|
|
return "", "", 0, nil
|
|
}
|
|
if cmd == "systemctl start dnstt-client.service" {
|
|
return "", "", 0, nil
|
|
}
|
|
if cmd == "systemctl start dnstt-ssh.service" {
|
|
return "", "", 0, nil
|
|
}
|
|
if cmd == "systemctl is-active dnstt-client.service" {
|
|
return "active\n", "", 0, nil
|
|
}
|
|
if cmd == "systemctl is-active dnstt-ssh.service" {
|
|
return "active\n", "", 0, nil
|
|
}
|
|
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
|
|
}
|
|
|
|
client := TransportClient{
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-client.service",
|
|
"ssh_tunnel": true,
|
|
"ssh_unit": "dnstt-ssh.service",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Action(client, "start")
|
|
if !res.OK {
|
|
t.Fatalf("expected action success, got %#v", res)
|
|
}
|
|
if len(calls) < 4 {
|
|
t.Fatalf("expected start calls, got %#v", calls)
|
|
}
|
|
if calls[0] != "systemctl reset-failed dnstt-ssh.service" ||
|
|
calls[1] != "systemctl reset-failed dnstt-client.service" ||
|
|
calls[2] != "systemctl start dnstt-ssh.service" ||
|
|
calls[3] != "systemctl start dnstt-client.service" {
|
|
t.Fatalf("unexpected call order: %#v", calls)
|
|
}
|
|
|
|
health := backend.Health(client)
|
|
if !health.OK || health.Status != TransportClientUp {
|
|
t.Fatalf("expected healthy up, got %#v", health)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendStopMissingUnitIsNoop(t *testing.T) {
|
|
orig := transportRunCommand
|
|
defer func() { transportRunCommand = orig }()
|
|
|
|
calls := make([]string, 0, 4)
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
calls = append(calls, cmd)
|
|
if cmd == "systemctl stop singbox@sg-missing.service" {
|
|
return "", "Failed to stop singbox@sg-missing.service: Unit singbox@sg-missing.service not loaded.", 5, fmt.Errorf("exit status 5")
|
|
}
|
|
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-missing",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Action(client, "stop")
|
|
if !res.OK {
|
|
t.Fatalf("expected stop noop success, got %#v", res)
|
|
}
|
|
if len(calls) != 1 || calls[0] != "systemctl stop singbox@sg-missing.service" {
|
|
t.Fatalf("unexpected calls: %#v", calls)
|
|
}
|
|
if !strings.Contains(strings.ToLower(res.Message), "systemctl stop ok") {
|
|
t.Fatalf("unexpected success message: %#v", res)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendStartAutoProvisionOnMissingUnit(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
|
|
startCalls := 0
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
switch cmd {
|
|
case "systemctl reset-failed singbox@sg-auto.service":
|
|
return "", "", 0, nil
|
|
case "systemctl daemon-reload":
|
|
return "", "", 0, nil
|
|
case "systemctl start singbox@sg-auto.service":
|
|
startCalls++
|
|
if startCalls == 1 {
|
|
return "", "Failed to start singbox@sg-auto.service: Unit singbox@sg-auto.service not found.", 5, fmt.Errorf("exit status 5")
|
|
}
|
|
return "", "", 0, nil
|
|
default:
|
|
return "", "", 0, nil
|
|
}
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-auto",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"exec_start": "/usr/bin/sing-box run -c /tmp/sg-auto.json",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Action(client, "start")
|
|
if !res.OK {
|
|
t.Fatalf("expected start success after auto-provision, got %#v", res)
|
|
}
|
|
if startCalls != 2 {
|
|
t.Fatalf("expected start retried after auto-provision, got calls=%d", startCalls)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(tmpDir, "singbox@.service")); err != nil {
|
|
t.Fatalf("expected provisioned template unit file, stat error: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(tmpDir, "singbox@sg-auto.service.d", transportSingBoxInstanceDropIn)); err != nil {
|
|
t.Fatalf("expected provisioned template drop-in file, stat error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendHealthOverlayMismatch(t *testing.T) {
|
|
orig := transportRunCommand
|
|
defer func() { transportRunCommand = orig }()
|
|
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
switch cmd {
|
|
case "systemctl is-active dnstt-client.service":
|
|
return "active\n", "", 0, nil
|
|
case "systemctl is-active dnstt-ssh.service":
|
|
return "inactive\n", "", 0, nil
|
|
default:
|
|
return "", "", 0, nil
|
|
}
|
|
}
|
|
|
|
client := TransportClient{
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-client.service",
|
|
"ssh_overlay": true,
|
|
"ssh_unit": "dnstt-ssh.service",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
health := backend.Health(client)
|
|
if health.OK {
|
|
t.Fatalf("expected unhealthy overlay mismatch, got %#v", health)
|
|
}
|
|
if health.Code != "TRANSPORT_BACKEND_HEALTH_FAILED" {
|
|
t.Fatalf("unexpected health code: %#v", health)
|
|
}
|
|
if health.Status != TransportClientDegraded {
|
|
t.Fatalf("unexpected health status: %#v", health)
|
|
}
|
|
}
|
|
|
|
func TestTransportCollectSingBoxConfigProbeEndpoints(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
cfgPath := filepath.Join(tmpDir, "singbox.json")
|
|
cfg := `{
|
|
"outbounds": [
|
|
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 },
|
|
{ "type": "wireguard", "server": "198.51.100.5", "server_port": "51820" }
|
|
]
|
|
}`
|
|
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
client := TransportClient{
|
|
ID: "sg-probe",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"config_path": cfgPath,
|
|
},
|
|
}
|
|
endpoints := transportCollectSingBoxConfigProbeEndpoints(client)
|
|
got := make([]string, 0, len(endpoints))
|
|
for _, ep := range endpoints {
|
|
got = append(got, ep.address())
|
|
}
|
|
want := []string{"n3.elmprod.tech:40903", "198.51.100.5:51820"}
|
|
for _, w := range want {
|
|
found := false
|
|
for _, g := range got {
|
|
if g == w {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("missing endpoint %q in %#v", w, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendHealthIncludesLatencyProbe(t *testing.T) {
|
|
origRun := transportRunCommand
|
|
origDial := transportDialContext
|
|
defer func() {
|
|
transportRunCommand = origRun
|
|
transportDialContext = origDial
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
cfgPath := filepath.Join(tmpDir, "singbox.json")
|
|
cfg := `{
|
|
"outbounds": [
|
|
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
|
|
]
|
|
}`
|
|
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
if cmd == "systemctl is-active singbox@sg-probe.service" {
|
|
return "active\n", "", 0, nil
|
|
}
|
|
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
|
|
}
|
|
|
|
calledAddr := ""
|
|
transportDialContext = func(_ context.Context, network, address string) (net.Conn, error) {
|
|
if network != "tcp4" {
|
|
return nil, fmt.Errorf("unexpected network: %s", network)
|
|
}
|
|
calledAddr = address
|
|
c1, c2 := net.Pipe()
|
|
_ = c2.Close()
|
|
return c1, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-probe",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"config_path": cfgPath,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
health := backend.Health(client)
|
|
if !health.OK || health.Status != TransportClientUp {
|
|
t.Fatalf("expected healthy up, got %#v", health)
|
|
}
|
|
if health.LatencyMS <= 0 {
|
|
t.Fatalf("expected latency sample, got %#v", health)
|
|
}
|
|
if calledAddr != "n3.elmprod.tech:40903" {
|
|
t.Fatalf("unexpected dial addr: %q", calledAddr)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendHealthProbeFailureKeepsUpStatus(t *testing.T) {
|
|
origRun := transportRunCommand
|
|
origDial := transportDialContext
|
|
defer func() {
|
|
transportRunCommand = origRun
|
|
transportDialContext = origDial
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
cfgPath := filepath.Join(tmpDir, "singbox.json")
|
|
cfg := `{
|
|
"outbounds": [
|
|
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
|
|
]
|
|
}`
|
|
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
if cmd == "systemctl is-active singbox@sg-probe.service" {
|
|
return "active\n", "", 0, nil
|
|
}
|
|
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
|
|
}
|
|
transportDialContext = func(_ context.Context, _ string, _ string) (net.Conn, error) {
|
|
return nil, fmt.Errorf("dial timeout")
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-probe",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"config_path": cfgPath,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
health := backend.Health(client)
|
|
if !health.OK || health.Status != TransportClientUp {
|
|
t.Fatalf("probe failure must not degrade systemd up status, got %#v", health)
|
|
}
|
|
if health.LatencyMS != 0 {
|
|
t.Fatalf("latency must be empty when probe fails, got %#v", health)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendHealthNetnsProbePreferred(t *testing.T) {
|
|
origRun := transportRunCommand
|
|
origDial := transportDialContext
|
|
defer func() {
|
|
transportRunCommand = origRun
|
|
transportDialContext = origDial
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
cfgPath := filepath.Join(tmpDir, "singbox.json")
|
|
cfg := `{
|
|
"outbounds": [
|
|
{ "type": "vless", "server": "n3.elmprod.tech", "server_port": 40903 }
|
|
]
|
|
}`
|
|
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
|
t.Fatalf("write config: %v", err)
|
|
}
|
|
|
|
netnsProbeCalled := false
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
switch cmd {
|
|
case "systemctl is-active singbox@sg-probe.service":
|
|
return "active\n", "", 0, nil
|
|
}
|
|
if strings.HasPrefix(cmd, "ip netns exec svpn-sg bash -lc ") {
|
|
netnsProbeCalled = true
|
|
return "", "", 0, nil
|
|
}
|
|
return "", "unexpected command", 1, fmt.Errorf("unexpected command: %s", cmd)
|
|
}
|
|
|
|
hostDialCalled := false
|
|
transportDialContext = func(_ context.Context, _ string, _ string) (net.Conn, error) {
|
|
hostDialCalled = true
|
|
return nil, fmt.Errorf("must not use host dial when netns probe succeeds")
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-probe",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"config_path": cfgPath,
|
|
"netns_enabled": true,
|
|
"netns_name": "svpn-sg",
|
|
"netns_exec_mode": "ip",
|
|
"netns_ip_bin": "ip",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
health := backend.Health(client)
|
|
if !health.OK || health.Status != TransportClientUp {
|
|
t.Fatalf("expected healthy up, got %#v", health)
|
|
}
|
|
if health.LatencyMS <= 0 {
|
|
t.Fatalf("expected latency sample from netns probe, got %#v", health)
|
|
}
|
|
if !netnsProbeCalled {
|
|
t.Fatalf("expected netns probe command to be called")
|
|
}
|
|
if hostDialCalled {
|
|
t.Fatalf("host dial must not be called when netns probe succeeds")
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionWritesUnits(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
|
|
calls := make([]string, 0, 8)
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
calls = append(calls, cmd)
|
|
switch cmd {
|
|
case "systemctl daemon-reload", "systemctl enable dnstt-ssh.service", "systemctl enable dnstt-client.service":
|
|
return "", "", 0, nil
|
|
default:
|
|
return "", "", 0, nil
|
|
}
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "dnstt-home",
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-client.service",
|
|
"doh_url": "https://dns.google/dns-query",
|
|
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
"domain": "tunnel.example.com",
|
|
"local_addr": "127.0.0.1:7001",
|
|
"ssh_tunnel": true,
|
|
"ssh_unit": "dnstt-ssh.service",
|
|
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
|
|
"enable_on_boot": true,
|
|
},
|
|
}
|
|
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
if !strings.Contains(res.Message, "template:dnstt") {
|
|
t.Fatalf("expected template source in provision message, got %#v", res)
|
|
}
|
|
primaryPath := filepath.Join(tmpDir, "dnstt-client.service")
|
|
sshPath := filepath.Join(tmpDir, "dnstt-ssh.service")
|
|
primaryData, err := os.ReadFile(primaryPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read primary unit: %v", err)
|
|
}
|
|
sshData, err := os.ReadFile(sshPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read ssh unit: %v", err)
|
|
}
|
|
primaryText := string(primaryData)
|
|
if !strings.Contains(primaryText, "Requires=dnstt-ssh.service") {
|
|
t.Fatalf("primary unit missing ssh require: %s", primaryText)
|
|
}
|
|
if !strings.Contains(primaryText, "dnstt-client") {
|
|
t.Fatalf("primary unit missing dnstt command: %s", primaryText)
|
|
}
|
|
if !strings.Contains(primaryText, "ExecStart=/bin/sh -lc ") {
|
|
t.Fatalf("primary unit missing shell exec start: %s", primaryText)
|
|
}
|
|
if !strings.Contains(string(sshData), "ExecStart=/bin/sh -lc ") {
|
|
t.Fatalf("ssh unit missing shell exec start")
|
|
}
|
|
if len(calls) == 0 || calls[0] != "systemctl daemon-reload" {
|
|
t.Fatalf("expected daemon-reload call first, got %#v", calls)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionRequiresDNSTTTemplateFields(t *testing.T) {
|
|
backend := transportSystemdBackend{}
|
|
client := TransportClient{
|
|
ID: "dnstt-home",
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-client.service",
|
|
},
|
|
}
|
|
res := backend.Provision(client)
|
|
if res.OK {
|
|
t.Fatalf("expected provision failure without required dnstt template fields")
|
|
}
|
|
if res.Code != "TRANSPORT_BACKEND_PROVISION_CONFIG_REQUIRED" {
|
|
t.Fatalf("unexpected code: %#v", res)
|
|
}
|
|
if !strings.Contains(strings.ToLower(res.Message), "dnstt template requires") {
|
|
t.Fatalf("unexpected message: %#v", res)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendCleanupRemovesOwnedUnits(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
|
|
unit := "sg-clean.service"
|
|
unitPath := filepath.Join(tmpDir, unit)
|
|
unitBody := "[Service]\nEnvironment=SVPN_TRANSPORT_ID=sg-clean\nExecStart=/usr/bin/sleep 60\n"
|
|
if err := os.WriteFile(unitPath, []byte(unitBody), 0o644); err != nil {
|
|
t.Fatalf("write unit file: %v", err)
|
|
}
|
|
|
|
calls := make([]string, 0, 8)
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
calls = append(calls, cmd)
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-clean",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": unit,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Cleanup(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected cleanup success, got %#v", res)
|
|
}
|
|
if _, err := os.Stat(unitPath); !os.IsNotExist(err) {
|
|
t.Fatalf("unit file was not removed: %v", err)
|
|
}
|
|
|
|
got := strings.Join(calls, " | ")
|
|
wantParts := []string{
|
|
"systemctl stop " + unit,
|
|
"systemctl disable " + unit,
|
|
"systemctl daemon-reload",
|
|
"systemctl reset-failed " + unit,
|
|
}
|
|
for _, part := range wantParts {
|
|
if !strings.Contains(got, part) {
|
|
t.Fatalf("expected cleanup calls to contain %q, got: %s", part, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendCleanupSkipsForeignUnits(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
|
|
unit := "sg-foreign.service"
|
|
unitPath := filepath.Join(tmpDir, unit)
|
|
unitBody := "[Service]\nEnvironment=SVPN_TRANSPORT_ID=someone-else\nExecStart=/usr/bin/sleep 60\n"
|
|
if err := os.WriteFile(unitPath, []byte(unitBody), 0o644); err != nil {
|
|
t.Fatalf("write unit file: %v", err)
|
|
}
|
|
|
|
calls := make([]string, 0, 4)
|
|
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
|
|
cmd := name + " " + strings.Join(args, " ")
|
|
calls = append(calls, cmd)
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-clean",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": unit,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Cleanup(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected cleanup success for foreign unit skip, got %#v", res)
|
|
}
|
|
if !strings.Contains(strings.ToLower(res.Message), "no managed unit artifacts") {
|
|
t.Fatalf("unexpected cleanup message: %#v", res)
|
|
}
|
|
if _, err := os.Stat(unitPath); err != nil {
|
|
t.Fatalf("foreign unit should stay intact, stat error: %v", err)
|
|
}
|
|
if len(calls) != 0 {
|
|
t.Fatalf("expected no systemctl calls for foreign unit, got %#v", calls)
|
|
}
|
|
}
|
|
|
|
func TestResolveTransportPrimaryExecStartManualOverride(t *testing.T) {
|
|
client := TransportClient{
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"exec_start": " /custom/bin/sing-box run -c /tmp/custom.json ",
|
|
},
|
|
}
|
|
cmd, source, err := resolveTransportPrimaryExecStart(client)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if source != "manual" {
|
|
t.Fatalf("expected manual source, got %q", source)
|
|
}
|
|
if cmd != "/custom/bin/sing-box run -c /tmp/custom.json" {
|
|
t.Fatalf("unexpected manual command: %q", cmd)
|
|
}
|
|
}
|
|
|
|
func TestResolveTransportPrimaryExecStartSingBoxTemplate(t *testing.T) {
|
|
client := TransportClient{
|
|
ID: "sg-eu",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"bin": "/usr/bin/sing-box",
|
|
"config_path": "/etc/singbox/eu.json",
|
|
},
|
|
}
|
|
cmd, source, err := resolveTransportPrimaryExecStart(client)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if source != "template:singbox" {
|
|
t.Fatalf("unexpected source: %q", source)
|
|
}
|
|
if !strings.Contains(cmd, "'/usr/bin/sing-box' 'run' '-c' '/etc/singbox/eu.json'") {
|
|
t.Fatalf("unexpected template command: %q", cmd)
|
|
}
|
|
}
|
|
|
|
func TestResolveTransportPrimaryExecStartPhoenixTemplate(t *testing.T) {
|
|
client := TransportClient{
|
|
ID: "ph-eu",
|
|
Kind: TransportClientPhoenix,
|
|
Config: map[string]any{
|
|
"phoenix_bin": "/usr/local/bin/phoenix-client",
|
|
"config_path": "/etc/phoenix/client.toml",
|
|
},
|
|
}
|
|
cmd, source, err := resolveTransportPrimaryExecStart(client)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if source != "template:phoenix" {
|
|
t.Fatalf("unexpected source: %q", source)
|
|
}
|
|
if !strings.Contains(cmd, "'/usr/local/bin/phoenix-client' '-config' '/etc/phoenix/client.toml'") {
|
|
t.Fatalf("unexpected template command: %q", cmd)
|
|
}
|
|
}
|
|
|
|
func TestBuildTransportDNSTTClientCommandTemplate(t *testing.T) {
|
|
client := TransportClient{
|
|
ID: "dn-home",
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"dnstt_bin": "/usr/local/bin/dnstt-client",
|
|
"doh_url": "https://dns.example.com/dns-query",
|
|
"pubkey": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
|
|
"domain": "tunnel.example.com",
|
|
"local_addr": "127.0.0.1:7002",
|
|
"utls": "HelloChrome_Auto",
|
|
},
|
|
}
|
|
cmd, err := buildTransportDNSTTClientCommand(client)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
wantParts := []string{
|
|
"'/usr/local/bin/dnstt-client'",
|
|
"'-doh' 'https://dns.example.com/dns-query'",
|
|
"'-pubkey' 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'",
|
|
"'-utls' 'HelloChrome_Auto'",
|
|
"'tunnel.example.com' '127.0.0.1:7002'",
|
|
}
|
|
for _, part := range wantParts {
|
|
if !strings.Contains(cmd, part) {
|
|
t.Fatalf("command missing %q in %q", part, cmd)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildTransportDNSTTClientCommandRequiresDomain(t *testing.T) {
|
|
client := TransportClient{
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"doh_url": "https://dns.example.com/dns-query",
|
|
"pubkey": "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
|
|
},
|
|
}
|
|
_, err := buildTransportDNSTTClientCommand(client)
|
|
if err == nil {
|
|
t.Fatalf("expected error for missing domain")
|
|
}
|
|
if !strings.Contains(err.Error(), "config.domain") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildTransportSingBoxCommandBundledProfilePrefersBinRoot(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
binPath := filepath.Join(tmpDir, "sing-box")
|
|
if err := os.WriteFile(binPath, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
|
|
t.Fatalf("write bundled binary: %v", err)
|
|
}
|
|
client := TransportClient{
|
|
ID: "sg-pack",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"packaging_profile": "bundled",
|
|
"bin_root": tmpDir,
|
|
"require_binary": true,
|
|
"config_path": "/etc/singbox/eu.json",
|
|
},
|
|
}
|
|
cmd, err := buildTransportSingBoxCommand(client)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !strings.Contains(cmd, shellQuoteArg(binPath)) {
|
|
t.Fatalf("expected bundled binary path in command, got %q", cmd)
|
|
}
|
|
}
|
|
|
|
func TestBuildTransportPhoenixClientCommandRequireBinaryMissing(t *testing.T) {
|
|
client := TransportClient{
|
|
ID: "ph-pack",
|
|
Kind: TransportClientPhoenix,
|
|
Config: map[string]any{
|
|
"packaging_profile": "bundled",
|
|
"bin_root": t.TempDir(),
|
|
"packaging_system_fallback": false,
|
|
"require_binary": true,
|
|
"config_path": "/etc/phoenix/client.toml",
|
|
},
|
|
}
|
|
_, err := buildTransportPhoenixClientCommand(client)
|
|
if err == nil {
|
|
t.Fatalf("expected error when required bundled binary is missing")
|
|
}
|
|
if !strings.Contains(err.Error(), "required phoenix binary not found") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBuildTransportSingBoxCommandRequireBinaryMissingManualOverride(t *testing.T) {
|
|
client := TransportClient{
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"singbox_bin": "/tmp/definitely-missing-sing-box-bin",
|
|
"require_binary": true,
|
|
"singbox_config_path": "/etc/singbox/eu.json",
|
|
},
|
|
}
|
|
_, err := buildTransportSingBoxCommand(client)
|
|
if err == nil {
|
|
t.Fatalf("expected manual binary validation error")
|
|
}
|
|
if !strings.Contains(err.Error(), "required singbox binary not found") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionRendersServiceTuning(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-eu",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "singbox@.service",
|
|
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
|
|
"restart_policy": "on-failure",
|
|
"restart_sec": 7,
|
|
"start_limit_interval_sec": 900,
|
|
"start_limit_burst": 11,
|
|
"timeout_start_sec": 55,
|
|
"timeout_stop_sec": 33,
|
|
"watchdog_sec": 20,
|
|
},
|
|
}
|
|
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
unitPath := filepath.Join(tmpDir, "singbox@sg-eu.service.d", transportSingBoxInstanceDropIn)
|
|
data, err := os.ReadFile(unitPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read drop-in unit: %v", err)
|
|
}
|
|
text := string(data)
|
|
want := []string{
|
|
"StartLimitIntervalSec=900",
|
|
"StartLimitBurst=11",
|
|
"Restart=on-failure",
|
|
"RestartSec=7",
|
|
"TimeoutStartSec=55",
|
|
"TimeoutStopSec=33",
|
|
"WatchdogSec=20",
|
|
"NotifyAccess=main",
|
|
}
|
|
for _, part := range want {
|
|
if !strings.Contains(text, part) {
|
|
t.Fatalf("expected unit to contain %q, got: %s", part, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionRendersSSHServiceTuningOverrides(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "dn-home",
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dnstt-home.service",
|
|
"doh_url": "https://dns.google/dns-query",
|
|
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
"domain": "tunnel.example.com",
|
|
"local_addr": "127.0.0.1:7003",
|
|
"ssh_tunnel": true,
|
|
"ssh_unit": "dnstt-home-ssh.service",
|
|
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
|
|
"restart_sec": 4,
|
|
"ssh_restart_sec": 9,
|
|
"watchdog_sec": 0,
|
|
"ssh_watchdog_sec": 25,
|
|
"ssh_restart_policy": "on-failure",
|
|
},
|
|
}
|
|
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
primaryData, err := os.ReadFile(filepath.Join(tmpDir, "dnstt-home.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read primary unit: %v", err)
|
|
}
|
|
sshData, err := os.ReadFile(filepath.Join(tmpDir, "dnstt-home-ssh.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read ssh unit: %v", err)
|
|
}
|
|
primaryText := string(primaryData)
|
|
sshText := string(sshData)
|
|
if !strings.Contains(primaryText, "RestartSec=4") {
|
|
t.Fatalf("expected primary restart sec from base config: %s", primaryText)
|
|
}
|
|
if strings.Contains(primaryText, "WatchdogSec=") {
|
|
t.Fatalf("did not expect primary watchdog line: %s", primaryText)
|
|
}
|
|
if !strings.Contains(sshText, "RestartSec=9") {
|
|
t.Fatalf("expected ssh restart sec override: %s", sshText)
|
|
}
|
|
if !strings.Contains(sshText, "Restart=on-failure") {
|
|
t.Fatalf("expected ssh restart policy override: %s", sshText)
|
|
}
|
|
if !strings.Contains(sshText, "WatchdogSec=25") {
|
|
t.Fatalf("expected ssh watchdog override: %s", sshText)
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionDefaultHardeningEnabled(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-hardened",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "sg-hardened.service",
|
|
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(tmpDir, "sg-hardened.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read unit: %v", err)
|
|
}
|
|
text := string(data)
|
|
want := []string{
|
|
"NoNewPrivileges=yes",
|
|
"PrivateTmp=yes",
|
|
"ProtectSystem=full",
|
|
"ProtectHome=read-only",
|
|
"ProtectControlGroups=yes",
|
|
"ProtectKernelModules=yes",
|
|
"ProtectKernelTunables=yes",
|
|
"RestrictSUIDSGID=yes",
|
|
"LockPersonality=yes",
|
|
"UMask=0077",
|
|
}
|
|
for _, part := range want {
|
|
if !strings.Contains(text, part) {
|
|
t.Fatalf("expected baseline hardening line %q in unit: %s", part, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionHardeningCanBeDisabled(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "sg-soft",
|
|
Kind: TransportClientSingBox,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "sg-soft.service",
|
|
"exec_start": "/usr/bin/sing-box run -c /etc/singbox/eu.json",
|
|
"hardening_enabled": false,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(tmpDir, "sg-soft.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read unit: %v", err)
|
|
}
|
|
text := string(data)
|
|
blocked := []string{
|
|
"NoNewPrivileges=",
|
|
"ProtectSystem=",
|
|
"ProtectHome=",
|
|
"RestrictSUIDSGID=",
|
|
"UMask=",
|
|
}
|
|
for _, part := range blocked {
|
|
if strings.Contains(text, part) {
|
|
t.Fatalf("expected hardening line %q to be absent when disabled: %s", part, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTransportSystemdBackendProvisionSSHHardeningOverrideDisabled(t *testing.T) {
|
|
origRunner := transportRunCommand
|
|
origUnitsDir := transportSystemdUnitsDir
|
|
defer func() {
|
|
transportRunCommand = origRunner
|
|
transportSystemdUnitsDir = origUnitsDir
|
|
}()
|
|
|
|
tmpDir := t.TempDir()
|
|
transportSystemdUnitsDir = tmpDir
|
|
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
|
|
return "", "", 0, nil
|
|
}
|
|
|
|
client := TransportClient{
|
|
ID: "dn-hard",
|
|
Kind: TransportClientDNSTT,
|
|
Config: map[string]any{
|
|
"runner": "systemd",
|
|
"unit": "dn-hard.service",
|
|
"doh_url": "https://dns.google/dns-query",
|
|
"pubkey": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
|
"domain": "tunnel.example.com",
|
|
"local_addr": "127.0.0.1:7004",
|
|
"ssh_tunnel": true,
|
|
"ssh_unit": "dn-hard-ssh.service",
|
|
"ssh_exec_start": "/usr/bin/ssh -N -D 127.0.0.1:1080 root@example.com",
|
|
"hardening_profile": "strict",
|
|
"ssh_hardening_enabled": false,
|
|
},
|
|
}
|
|
backend := selectTransportBackend(client)
|
|
res := backend.Provision(client)
|
|
if !res.OK {
|
|
t.Fatalf("expected provision success, got %#v", res)
|
|
}
|
|
primaryData, err := os.ReadFile(filepath.Join(tmpDir, "dn-hard.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read primary unit: %v", err)
|
|
}
|
|
sshData, err := os.ReadFile(filepath.Join(tmpDir, "dn-hard-ssh.service"))
|
|
if err != nil {
|
|
t.Fatalf("failed to read ssh unit: %v", err)
|
|
}
|
|
primaryText := string(primaryData)
|
|
sshText := string(sshData)
|
|
if !strings.Contains(primaryText, "ProtectSystem=strict") {
|
|
t.Fatalf("expected strict hardening in primary unit: %s", primaryText)
|
|
}
|
|
if !strings.Contains(primaryText, "PrivateDevices=yes") {
|
|
t.Fatalf("expected strict private devices in primary unit: %s", primaryText)
|
|
}
|
|
if strings.Contains(sshText, "NoNewPrivileges=") || strings.Contains(sshText, "ProtectSystem=") {
|
|
t.Fatalf("expected ssh unit hardening to be disabled by ssh_hardening_enabled=false: %s", sshText)
|
|
}
|
|
}
|