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 TestDockerDistributionBuilderKeepsSecretInIsolatedInput(t *testing.T) { sourceDir := createBuilderSource(t) workspaceDir := t.TempDir() 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, 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") 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") } 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"} { 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) } } 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 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 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 }