feat: add native Windows hierarchy test harness

This commit is contained in:
2026-09-09 16:48:54 +00:00
parent 8985457d37
commit 409b64a2fb
30 changed files with 1033 additions and 44 deletions
+45 -1
View File
@@ -33,6 +33,8 @@ func main() {
}
}
var nativeTestContextFailures map[clientwindows.ExecutionContext]bool
// run is deliberately a small mode dispatcher. The SCM service invokes only
// --service with an explicit config path; tray/helper modes cannot silently
// turn an ordinary process invocation into a privileged service.
@@ -60,6 +62,7 @@ func run(args []string, output, diagnostics io.Writer) error {
start := flags.Bool("start-service", false, "start the machine-wide service")
stop := flags.Bool("stop-service", false, "stop the machine-wide service")
restart := flags.Bool("restart-service", false, "restart the machine-wide service")
testFailContexts := flags.String("test-fail-contexts", "", "fixture-only pre-launch Windows context failures")
if err := flags.Parse(args); err != nil {
return err
}
@@ -120,6 +123,11 @@ func run(args []string, output, diagnostics io.Writer) error {
}
return runSignalHelper(*channel, diagnostics)
}
var testFaultErr error
nativeTestContextFailures, testFaultErr = clientwindows.NativeTestContextFailures(*testFailContexts)
if testFaultErr != nil {
return testFaultErr
}
return runService(*configPath, diagnostics)
}
@@ -167,7 +175,7 @@ func runClientDaemon(ctx context.Context, configPath string, diagnostics io.Writ
limits = agentproto.DefaultLimits()
}
eventReady := make(chan domain.UUID, 256)
supervised, err := clientwindows.NewSupervisor(clientwindows.NativeOptions{Shells: clientwindows.ShellPaths{CMD: configured.Shells.CMD, PowerShell: configured.Shells.PowerShell}, WorkRoot: configured.Client.DaemonCWD, JobProfiles: clientJobProfiles(configured.Profiles), MaxWrapperBytes: configured.Execution.MaxScriptBytes, MaxOutputChunk: configured.Execution.MaxRawChunkBytes, WindowsTermGrace: configured.Execution.WindowsTermGrace})
supervised, err := clientwindows.NewSupervisor(clientwindows.NativeOptions{Shells: clientwindows.ShellPaths{CMD: configured.Shells.CMD, PowerShell: configured.Shells.PowerShell}, WorkRoot: configured.Client.DaemonCWD, JobProfiles: clientJobProfiles(configured.Profiles), MaxWrapperBytes: configured.Execution.MaxScriptBytes, MaxOutputChunk: configured.Execution.MaxRawChunkBytes, WindowsTermGrace: configured.Execution.WindowsTermGrace, TestContextFailures: nativeTestContextFailures})
if err != nil {
return fmt.Errorf("configure command supervisor: %w", err)
}
@@ -249,10 +257,46 @@ func clientHTTPClient(settings config.TLS) (*http.Client, error) {
return nil, errors.New("TLS CA file contains no certificates")
}
tlsConfig.RootCAs = pool
} else {
// v1 deliberately permits a self-signed endpoint certificate without a
// separately distributed CA. Keep hostname checking: this retains the
// useful routing guard while making no claim that the peer is trusted or
// authenticated. A configured CA file opts back into normal PKI-only
// verification above.
tlsConfig.InsecureSkipVerify = true // #nosec G402 -- verified below to admit matching self-signed leaves for v1.
tlsConfig.VerifyConnection = verifySystemOrSelfSignedServer
}
return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}, nil
}
func verifySystemOrSelfSignedServer(state tls.ConnectionState) error {
if len(state.PeerCertificates) == 0 {
return errors.New("TLS peer sent no certificates")
}
leaf := state.PeerCertificates[0]
roots, err := x509.SystemCertPool()
if err != nil || roots == nil {
roots = x509.NewCertPool()
}
intermediates := x509.NewCertPool()
for _, certificate := range state.PeerCertificates[1:] {
intermediates.AddCert(certificate)
}
_, verifyErr := leaf.Verify(x509.VerifyOptions{
DNSName: state.ServerName,
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
})
if verifyErr == nil {
return nil
}
if err := leaf.CheckSignatureFrom(leaf); err != nil {
return verifyErr
}
return leaf.VerifyHostname(state.ServerName)
}
func clientHello(configured *config.Client, instance domain.UUID) *rvboxv1.ClientHello {
platform := rvboxv1.Platform_PLATFORM_LINUX
shells := []rvboxv1.ShellType{rvboxv1.ShellType_SHELL_CMD, rvboxv1.ShellType_SHELL_POWERSHELL}
+41
View File
@@ -2,7 +2,11 @@ package main
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/rvbox/rvbox/internal/config"
)
func TestClientModeSelectionRequiresExactlyOneMode_HP_WINCLI_01(t *testing.T) {
@@ -19,6 +23,43 @@ func TestClientModeSelectionRequiresExactlyOneMode_HP_WINCLI_01(t *testing.T) {
}
}
func TestClientHTTPClientAcceptsMatchingSelfSignedLeaf(t *testing.T) {
t.Parallel()
server := httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
client, err := clientHTTPClient(config.TLS{ServerName: "example.com"})
if err != nil {
t.Fatalf("clientHTTPClient: %v", err)
}
response, err := client.Get(server.URL)
if err != nil {
t.Fatalf("self-signed request: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusNoContent {
t.Fatalf("status = %d, want %d", response.StatusCode, http.StatusNoContent)
}
}
func TestClientHTTPClientRejectsWrongNameSelfSignedLeaf(t *testing.T) {
t.Parallel()
server := httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
client, err := clientHTTPClient(config.TLS{ServerName: "wrong-name.invalid"})
if err != nil {
t.Fatalf("clientHTTPClient: %v", err)
}
if _, err := client.Get(server.URL); err == nil {
t.Fatal("self-signed request with wrong name unexpectedly succeeded")
}
}
func TestNonWindowsServiceModesRemainExplicitlyUnsupported_BH_WINCLI_01(t *testing.T) {
t.Parallel()
if err := runService("", nil); err == nil {
+53 -6
View File
@@ -26,30 +26,77 @@ func defaultClientConfigPath() string {
}
func runService(configPath string, diagnostics io.Writer) error {
earlyDiagnostics, closeDiagnostics := openServiceDiagnostics(configPath, diagnostics)
defer closeDiagnostics()
_, _ = fmt.Fprintf(earlyDiagnostics, "rvbox service preflight config=%s\n", configPath)
inService, err := svc.IsWindowsService()
if err != nil {
return fmt.Errorf("detect service control manager context: %w", err)
err = fmt.Errorf("detect service control manager context: %w", err)
_, _ = fmt.Fprintf(earlyDiagnostics, "rvbox service preflight failed: %v\n", err)
return err
}
if !inService {
return errors.New("--service is reserved for the installed Windows service")
err = errors.New("--service is reserved for the installed Windows service")
_, _ = fmt.Fprintf(earlyDiagnostics, "rvbox service preflight failed: %v\n", err)
return err
}
_, _ = fmt.Fprintln(earlyDiagnostics, "rvbox service preflight confirmed SCM context")
return runWindowsService(configPath, diagnostics)
}
func runWindowsService(configPath string, diagnostics io.Writer) error {
return windowsservice.Run(func(ctx context.Context) error {
serviceDiagnostics, closeDiagnostics := openServiceDiagnostics(configPath, diagnostics)
defer closeDiagnostics()
_, _ = fmt.Fprintf(serviceDiagnostics, "rvbox Windows service starting config=%s\n", configPath)
err := windowsservice.Run(func(ctx context.Context) error {
// The tray endpoint lives in the same LocalSystem service process. It
// has no store access; the handler below returns only bounded status/path
// data and rechecks SCM authorization in the native pipe adapter.
go func() {
if err := windowstray.Serve(ctx, func(requestContext context.Context, _ windowstray.Peer, request windowstray.Frame) (windowstray.Frame, error) {
return handleTrayRequest(requestContext, configPath, request)
}); err != nil && ctx.Err() == nil && diagnostics != nil {
_, _ = fmt.Fprintf(diagnostics, "rvbox tray endpoint stopped: %v\n", err)
}); err != nil && ctx.Err() == nil {
_, _ = fmt.Fprintf(serviceDiagnostics, "rvbox tray endpoint stopped: %v\n", err)
}
}()
return runClientDaemon(ctx, configPath, diagnostics)
err := runClientDaemon(ctx, configPath, serviceDiagnostics)
if err != nil {
_, _ = fmt.Fprintf(serviceDiagnostics, "rvbox Windows service startup failed: %v\n", err)
}
return err
})
if err != nil {
_, _ = fmt.Fprintf(serviceDiagnostics, "rvbox Windows service stopped with error: %v\n", err)
}
return err
}
// openServiceDiagnostics preserves the reason for an early service failure:
// a GUI-subsystem executable has no reliable inherited stderr under SCM. The
// file is deliberately machine-wide instead of beside the selected config.
// LocalSystem can therefore report an access failure to a per-run bundle,
// config, or state directory before the normal client logger exists.
func openServiceDiagnostics(configPath string, diagnostics io.Writer) (io.Writer, func()) {
_ = configPath // Kept in the signature so callers document the selected config.
root := os.Getenv("ProgramData")
if root == "" {
root = `C:\ProgramData`
}
path := filepath.Join(root, "RVBox", "service-startup.log")
file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
if err != nil {
if diagnostics == nil {
return io.Discard, func() {}
}
return diagnostics, func() {}
}
if diagnostics == nil {
return file, func() { _ = file.Close() }
}
// A GUI-subsystem service can inherit an invalid stderr handle from SCM.
// MultiWriter stops on its first failed destination, so keep the durable
// machine log first and make the inherited diagnostic stream best effort.
return io.MultiWriter(file, diagnostics), func() { _ = file.Close() }
}
func runTray(configPath string, diagnostics io.Writer) error {