package service import ( "archive/tar" "archive/zip" "bytes" "compress/gzip" "context" "errors" "io" "os" "path/filepath" "strings" "testing" "time" "browser.local/platform/domain" ) func TestPinnedBuilderImageRequiresExplicitTagOrDigest(t *testing.T) { validDigest := strings.Repeat("a", 64) for _, test := range []struct { image string want bool }{ {image: "browser-platform-distribution-builder:1.0.0", want: true}, {image: "registry.example.test:5000/builders/distribution:v1", want: true}, {image: "registry.example.test/builders/distribution@sha256:" + validDigest, want: true}, {image: "browser-platform-distribution-builder", want: false}, {image: "browser-platform-distribution-builder:latest", want: false}, {image: "registry.example.test/builders/distribution@sha256:", want: false}, {image: "registry.example.test/builders/distribution@sha256:not-a-digest", want: false}, } { t.Run(test.image, func(t *testing.T) { if got := pinnedBuilderImage(test.image); got != test.want { t.Fatalf("pinnedBuilderImage(%q) = %t, want %t", test.image, got, test.want) } }) } } func TestDockerDistributionBuilderReadinessNamesPlatformBuilderFailures(t *testing.T) { sourceDir := createBuilderSource(t) workspaceDir := t.TempDir() for _, test := range []struct { name string config DockerDistributionBuilderConfig reason string }{ { name: "floating image", config: DockerDistributionBuilderConfig{Image: "golang:latest", SourceDir: sourceDir, WorkspaceDir: workspaceDir}, reason: "platform builder image must be pinned", }, { name: "missing source", config: DockerDistributionBuilderConfig{Image: "builder:1.0.0", WorkspaceDir: workspaceDir}, reason: "platform builder run source directory is not configured", }, { name: "image unavailable", config: DockerDistributionBuilderConfig{ Image: "builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) { if len(args) > 0 && args[0] == "version" { return []byte("27.0.0"), nil } return nil, errors.New("image missing") }, }, reason: "platform builder image is unavailable", }, { name: "container runtime unavailable", config: DockerDistributionBuilderConfig{ Image: "builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, CommandRunner: func(context.Context, string, ...string) ([]byte, error) { return nil, errors.New("docker unavailable") }, }, reason: "platform builder container runtime is unavailable", }, } { t.Run(test.name, func(t *testing.T) { ready, reason := NewDockerDistributionBuilder(test.config).Readiness() if ready || !strings.Contains(reason, test.reason) || !strings.Contains(reason, "platform builder") { t.Fatalf("expected platform builder readiness failure %q, ready=%t reason=%q", test.reason, ready, reason) } }) } } func TestDockerDistributionBuilderRefreshesRunSourceAtBuildTime(t *testing.T) { sourceDir := filepath.Join(t.TempDir(), "run-checkout") workspaceDir := t.TempDir() var gitCalls [][]string builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{ DockerBinary: "docker-test", Image: "browser-platform-distribution-builder:1.0.0", SourceDir: sourceDir, SourceRepositoryURL: "git@example.test:admin/run.git", SourceRevision: "main", WorkspaceDir: workspaceDir, GitCommandRunner: func(_ context.Context, name string, args ...string) ([]byte, error) { if name != "git" { t.Fatalf("unexpected source command %q", name) } gitCalls = append(gitCalls, append([]string(nil), args...)) if len(args) >= 4 && args[0] == "clone" { if err := os.MkdirAll(filepath.Join(args[len(args)-1], ".git"), 0o700); err != nil { t.Fatalf("create fake checkout: %v", err) } } if len(args) >= 4 && args[0] == "-C" && args[2] == "archive" { return runSourceArchive(t), nil } return nil, nil }, CommandRunner: func(_ context.Context, name string, args ...string) ([]byte, error) { if name != "docker-test" { t.Fatalf("unexpected container runtime %q", name) } if len(args) > 0 && (args[0] == "version" || args[0] == "image") { return []byte("27.0.0"), nil } outputDir := builderMountHostPath(t, args, "/workspace/output") if err := os.WriteFile(filepath.Join(outputDir, "run"), []byte("compiled-run"), 0o700); err != nil { t.Fatalf("write fake build output: %v", err) } return nil, nil }, }) payload, err := builder.Build(domain.DistributionBuildInput{ JobID: "job-source-refresh", ComponentKind: domain.DistributionComponentRun, PluginID: "game.scum", TargetOS: "linux", TargetArch: "amd64", OutputFilename: "run", AuthKey: "component-key", }) if err != nil { t.Fatalf("build refreshed Run source: %v", err) } if string(payload) != "compiled-run" { t.Fatalf("unexpected built payload %q", payload) } if len(gitCalls) != 3 || gitCalls[0][0] != "clone" || gitCalls[1][0] != "-C" || gitCalls[1][2] != "fetch" || gitCalls[2][2] != "archive" { t.Fatalf("expected clone, fetch, and archive during build, got %#v", gitCalls) } } func TestDockerDistributionBuilderKeepsSecretInIsolatedInput(t *testing.T) { sourceDir := createBuilderSource(t) workspaceDir := t.TempDir() cacheDir := filepath.Join(t.TempDir(), "builder-cache") secret := "component-auth-key-that-must-not-leave-input" var dockerArgs []string builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{ DockerBinary: "docker-test", Image: "browser-platform-distribution-builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, CacheDir: cacheDir, CommandRunner: func(_ context.Context, name string, args ...string) ([]byte, error) { if name != "docker-test" { t.Fatalf("unexpected container runtime %q", name) } if len(args) > 0 && (args[0] == "version" || args[0] == "image") { return []byte("27.0.0"), nil } dockerArgs = append([]string(nil), args...) inputDir := builderMountHostPath(t, args, "/workspace/input:ro") outputDir := builderMountHostPath(t, args, "/workspace/output") goBuildCacheDir := builderMountHostPath(t, args, "/workspace/cache/go-build") goModCacheDir := builderMountHostPath(t, args, "/workspace/cache/go-mod") if goBuildCacheDir != filepath.Join(cacheDir, "go-build") || goModCacheDir != filepath.Join(cacheDir, "go-mod") { t.Fatalf("unexpected Go cache mounts: build=%q mod=%q", goBuildCacheDir, goModCacheDir) } authPath := filepath.Join(inputDir, "auth-key") payload, err := os.ReadFile(authPath) if err != nil { t.Fatalf("read per-job auth input: %v", err) } if string(payload) != secret { t.Fatalf("unexpected per-job auth input %q", payload) } info, err := os.Stat(authPath) if err != nil { t.Fatalf("stat per-job auth input: %v", err) } if info.Mode().Perm() != 0o600 { t.Fatalf("auth input mode = %o, want 600", info.Mode().Perm()) } script, err := os.ReadFile(filepath.Join(inputDir, "build.sh")) if err != nil { t.Fatalf("read build script: %v", err) } if bytes.Contains(script, []byte(secret)) { t.Fatal("build script must not embed the component auth key") } for _, ordered := range [][2]string{{"find /workspace/source -mindepth 1 -maxdepth 1 ! -name pax_global_header", "mkdir -p /workspace/build/run-source/config"}, {"mkdir -p /workspace/build/run-source/config", "workspace_seed_generated.go"}, {"workspace_seed_generated.go", "go build -trimpath -ldflags"}} { if bytes.Index(script, []byte(ordered[0])) >= bytes.Index(script, []byte(ordered[1])) { t.Fatalf("Run build script must prepare source before injecting config and compiling: %q before %q", ordered[0], ordered[1]) } } planPayload, err := os.ReadFile(filepath.Join(inputDir, "autonomous-lifecycle-plan.json")) if err != nil { t.Fatalf("read autonomous lifecycle plan input: %v", err) } if strings.TrimSpace(string(planPayload)) != "{}" { t.Fatalf("unexpected empty autonomous lifecycle plan payload %q", planPayload) } if err := os.WriteFile(filepath.Join(outputDir, "run.exe"), []byte("compiled-run"), 0o700); err != nil { t.Fatalf("write fake build output: %v", err) } return nil, nil }, }) input := domain.DistributionBuildInput{ JobID: "job/build:one", ComponentKind: domain.DistributionComponentRun, ServerInstanceID: "server-one", PluginID: "game.scum", RunEndpointID: "server-run-server-one", TargetOS: "windows", TargetArch: "amd64", TargetRelease: "release-one", PlatformURL: "https://platform.example.test", OutputFilename: "run.exe", KeyGeneration: 1, AuthKey: secret, } payload, err := builder.Build(input) if err != nil { t.Fatalf("build Run distribution: %v", err) } if string(payload) != "compiled-run" { t.Fatalf("unexpected built payload %q", payload) } joinedArgs := strings.Join(dockerArgs, "\x00") if strings.Contains(joinedArgs, secret) { t.Fatal("component auth key leaked into Docker arguments or environment") } for _, required := range []string{"--read-only", "--pull\x00never", sourceDir + ":/workspace/source:ro", ":/workspace/input:ro", ":/workspace/output", cacheDir + "/go-build:/workspace/cache/go-build", cacheDir + "/go-mod:/workspace/cache/go-mod", "GOCACHE=/workspace/cache/go-build", "GOMODCACHE=/workspace/cache/go-mod"} { if !strings.Contains(joinedArgs, required) { t.Fatalf("Docker arguments do not contain required isolation %q: %q", required, joinedArgs) } } jobDir := filepath.Join(workspaceDir, "game.scum", "job-build-one") if _, err := os.Stat(jobDir); !errors.Is(err, os.ErrNotExist) { t.Fatalf("per-job workspace was not removed: %v", err) } if _, err := os.Stat(filepath.Join(cacheDir, "go-mod")); err != nil { t.Fatalf("persistent Go module cache was not retained: %v", err) } } func TestDockerDistributionBuilderStreamsProgressMarkers(t *testing.T) { sourceDir := createBuilderSource(t) workspaceDir := t.TempDir() var progress []DistributionBuildProgress builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{ DockerBinary: "docker-test", Image: "browser-platform-distribution-builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) { if len(args) > 0 && (args[0] == "version" || args[0] == "image") { return []byte("27.0.0"), nil } return nil, errors.New("unexpected non-stream command") }, CommandStream: func(_ context.Context, _ string, args []string, onLine func(string)) ([]byte, error) { onLine("__platform_builder_progress__|72|build_compile: compiling run target executable") outputDir := builderMountHostPath(t, args, "/workspace/output") if err := os.WriteFile(filepath.Join(outputDir, "run"), []byte("compiled-run"), 0o700); err != nil { return nil, err } return []byte("builder finished"), nil }, }) payload, err := builder.BuildWithProgress(domain.DistributionBuildInput{ JobID: "job-progress", ComponentKind: domain.DistributionComponentRun, PluginID: "game.scum", TargetOS: "linux", TargetArch: "amd64", OutputFilename: "run", AuthKey: "component-key", }, func(item DistributionBuildProgress) { progress = append(progress, item) }) if err != nil { t.Fatalf("build with progress: %v", err) } if string(payload) != "compiled-run" { t.Fatalf("unexpected built payload %q", payload) } found := false for _, item := range progress { if item.Percent == 72 && item.Message == "build_compile: compiling run target executable" { found = true } } if !found { t.Fatalf("streamed progress marker was not reported: %+v", progress) } } func TestDockerDistributionBuilderRedactsFailureAndTimeout(t *testing.T) { sourceDir := createBuilderSource(t) workspaceDir := t.TempDir() secret := "sensitive-component-key" input := domain.DistributionBuildInput{ JobID: "job-redaction", ComponentKind: domain.DistributionComponentRun, PluginID: "game.scum", TargetOS: "linux", TargetArch: "amd64", OutputFilename: "run", AuthKey: secret, } builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{ Image: "builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) { if len(args) > 0 && (args[0] == "version" || args[0] == "image") { return []byte("27.0.0"), nil } jobDir := filepath.Join(workspaceDir, "game.scum", "job-redaction") return []byte(secret + "\n" + sourceDir + "/go.mod: build failed\n" + jobDir + "/input/auth-key"), errors.New("exit 1") }, }) _, err := builder.Build(input) if err == nil { t.Fatal("expected failed builder command") } if strings.Contains(err.Error(), secret) || strings.Contains(err.Error(), sourceDir) || strings.Contains(err.Error(), workspaceDir) || strings.Contains(err.Error(), "auth-key") { t.Fatalf("builder failure leaked secret or host path: %v", err) } timeoutBuilder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{ Image: "builder:1.0.0", SourceDir: sourceDir, WorkspaceDir: workspaceDir, Timeout: 5 * time.Millisecond, CommandRunner: func(ctx context.Context, _ string, args ...string) ([]byte, error) { if len(args) > 0 && (args[0] == "version" || args[0] == "image") { return []byte("27.0.0"), nil } <-ctx.Done() return nil, ctx.Err() }, }) _, err = timeoutBuilder.Build(input) if err == nil || !strings.Contains(err.Error(), "platform builder timed out") { t.Fatalf("expected bounded platform builder timeout, got %v", err) } } func TestSafeBuilderFailureFallsBackToCommandError(t *testing.T) { secret := "sensitive-component-key" message := safeBuilderFailureWithCommandError( []byte("__platform_builder_progress__|72|build_compile: compiling run target executable\n"), errors.New("exit status 1: failed to start container with "+secret+" /private/platform/build"), secret, ) if message != "exit status 1: failed to start container with [redacted] [redacted-path]" { t.Fatalf("unexpected command error fallback: %q", message) } } func TestBuilderJobFailureMessageRedactsSensitiveValuesAndHostPaths(t *testing.T) { const secret = "sensitive-component-key" message := builderJobFailureMessage(errors.New(secret+" /private/platform/build/input/auth-key"), secret) if strings.Contains(message, secret) || strings.Contains(message, "/private/") || strings.Contains(message, "auth-key") { t.Fatalf("builder job failure leaked sensitive details: %s", message) } } func TestPackageClientManagerDistributionProducesProtectedArchives(t *testing.T) { for _, packageFormat := range []string{"zip", "tar.gz"} { t.Run(packageFormat, func(t *testing.T) { payload, err := packageClientManagerDistribution(packageFormat, "client-manager.exe", []byte("binary"), []byte("credential: protected")) if err != nil { t.Fatalf("package client manager: %v", err) } files := readDistributionArchive(t, packageFormat, payload) if string(files["client-manager.exe"].payload) != "binary" || files["client-manager.exe"].mode.Perm() != 0o755 { t.Fatalf("unexpected executable archive entry: %+v", files["client-manager.exe"]) } if string(files["config.yaml"].payload) != "credential: protected" || files["config.yaml"].mode.Perm() != 0o600 { t.Fatalf("unexpected config archive entry: %+v", files["config.yaml"]) } }) } } type distributionArchiveFile struct { mode os.FileMode payload []byte } func createBuilderSource(t *testing.T) string { t.Helper() sourceDir := t.TempDir() if err := os.WriteFile(filepath.Join(sourceDir, "go.mod"), []byte("module browser.local/run\n"), 0o600); err != nil { t.Fatalf("write run source go.mod: %v", err) } return sourceDir } func runSourceArchive(t *testing.T) []byte { t.Helper() var payload bytes.Buffer writer := tar.NewWriter(&payload) if err := writer.WriteHeader(&tar.Header{Name: "go.mod", Mode: 0o600, Size: int64(len("module browser.local/run\n"))}); err != nil { t.Fatalf("write source archive header: %v", err) } if _, err := writer.Write([]byte("module browser.local/run\n")); err != nil { t.Fatalf("write source archive body: %v", err) } if err := writer.Close(); err != nil { t.Fatalf("close source archive: %v", err) } return payload.Bytes() } func builderMountHostPath(t *testing.T, args []string, containerSuffix string) string { t.Helper() for index := 0; index+1 < len(args); index++ { if args[index] != "-v" || !strings.HasSuffix(args[index+1], ":"+containerSuffix) { continue } return strings.TrimSuffix(args[index+1], ":"+containerSuffix) } t.Fatalf("Docker arguments do not mount %s: %+v", containerSuffix, args) return "" } func readDistributionArchive(t *testing.T, packageFormat string, payload []byte) map[string]distributionArchiveFile { t.Helper() files := map[string]distributionArchiveFile{} switch packageFormat { case "zip": reader, err := zip.NewReader(bytes.NewReader(payload), int64(len(payload))) if err != nil { t.Fatalf("open zip: %v", err) } for _, entry := range reader.File { body, err := entry.Open() if err != nil { t.Fatalf("open zip entry %s: %v", entry.Name, err) } content, err := io.ReadAll(body) if closeErr := body.Close(); err == nil { err = closeErr } if err != nil { t.Fatalf("read zip entry %s: %v", entry.Name, err) } files[entry.Name] = distributionArchiveFile{mode: entry.Mode(), payload: content} } case "tar.gz": gzipReader, err := gzip.NewReader(bytes.NewReader(payload)) if err != nil { t.Fatalf("open gzip: %v", err) } tarReader := tar.NewReader(gzipReader) for { header, err := tarReader.Next() if errors.Is(err, io.EOF) { break } if err != nil { t.Fatalf("read tar header: %v", err) } content, err := io.ReadAll(tarReader) if err != nil { t.Fatalf("read tar entry %s: %v", header.Name, err) } files[header.Name] = distributionArchiveFile{mode: os.FileMode(header.Mode), payload: content} } if err := gzipReader.Close(); err != nil { t.Fatalf("close gzip: %v", err) } default: t.Fatalf("unsupported archive format %q", packageFormat) } return files }