fetch_ml/internal/container/security_test.go
Jeremie Fraeys 7194826871
feat: implement research-grade maintainability phases 1,3,4,7
Phase 1: Event Sourcing
- Add TaskEvent types (queued, started, completed, failed, etc.)
- Create EventStore with Redis Streams (append-only)
- Support event querying by task ID and time range

Phase 3: Diagnosable Failures
- Enhance TaskExecutionError with Context map, Timestamp, Recoverable flag
- Update container.go to populate error context (image, GPU, duration)
- Add WithContext helper for building error context
- Create cmd/errors CLI for querying task errors

Phase 4: Testable Security
- Add security fields to PodmanConfig (Privileged, Network, ReadOnlyMounts)
- Create ValidateSecurityPolicy() with ErrSecurityViolation
- Add security contract tests (privileged rejection, host network rejection)
- Tests serve as executable security documentation

Phase 7: Reproducible Builds
- Add BuildHash and BuildTime ldflags to Makefile
- Create verify-build target for reproducibility testing
- Add -version and -verify flags to api-server

All tests pass:
- go test ./internal/errtypes/...
- go test ./internal/container/... -run Security
- go test ./internal/queue/...
- go build ./cmd/api-server/...
2026-02-18 15:27:50 -05:00

110 lines
2.8 KiB
Go

package container
import (
"errors"
"testing"
)
// TestContainerSecurityPolicy enforces the security contract for container configurations.
// These tests serve as executable documentation of security requirements.
func TestContainerSecurityPolicy(t *testing.T) {
tests := []struct {
name string
config PodmanConfig
shouldFail bool
reason string
}{
{
name: "reject privileged mode",
config: PodmanConfig{
Image: "pytorch:latest",
Privileged: true, // NEVER allowed
},
shouldFail: true,
reason: "privileged containers bypass isolation",
},
{
name: "reject host network",
config: PodmanConfig{
Image: "pytorch:latest",
Network: "host", // NEVER allowed
},
shouldFail: true,
reason: "host network breaks isolation",
},
{
name: "accept valid configuration",
config: PodmanConfig{
Image: "pytorch:latest",
Privileged: false,
Network: "bridge",
ReadOnlyMounts: true,
},
shouldFail: false,
reason: "valid secure configuration",
},
{
name: "accept empty network (default bridge)",
config: PodmanConfig{
Image: "pytorch:latest",
Privileged: false,
Network: "", // Empty means default bridge
},
shouldFail: false,
reason: "empty network uses default bridge",
},
{
name: "warn on non-read-only mounts",
config: PodmanConfig{
Image: "pytorch:latest",
Privileged: false,
Network: "bridge",
ReadOnlyMounts: false, // Warning-level issue
},
shouldFail: false, // Not a hard failure
reason: "non-read-only mounts are discouraged but allowed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateSecurityPolicy(tt.config)
if tt.shouldFail {
if err == nil {
t.Errorf("%s: expected failure (%s), got success", tt.name, tt.reason)
} else if !errors.Is(err, ErrSecurityViolation) {
t.Errorf("%s: expected ErrSecurityViolation, got %v", tt.name, err)
}
} else {
if err != nil {
t.Errorf("%s: expected success (%s), got error: %v", tt.name, tt.reason, err)
}
}
})
}
}
// TestSecurityPolicy_IsolationEnforcement verifies isolation boundaries
func TestSecurityPolicy_IsolationEnforcement(t *testing.T) {
t.Run("privileged_equals_root_access", func(t *testing.T) {
cfg := PodmanConfig{
Image: "test:latest",
Privileged: true,
}
err := ValidateSecurityPolicy(cfg)
if err == nil {
t.Fatal("privileged mode must be rejected - it grants root access to host")
}
})
t.Run("host_network_equals_no_isolation", func(t *testing.T) {
cfg := PodmanConfig{
Image: "test:latest",
Network: "host",
}
err := ValidateSecurityPolicy(cfg)
if err == nil {
t.Fatal("host network must be rejected - it removes network isolation")
}
})
}