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/...
110 lines
2.8 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|