Implement 4 prerequisite test requirements: - TestConfigIntegrityVerification: Config signing, tamper detection, hash stability - TestManifestFilenameNonce: Cryptographic nonce generation and filename patterns - TestGPUDetectionAudit: Structured logging of GPU detection at startup - TestResourceEnvVarParsing: Resource env var parsing and override behavior Also update manifest run_manifest.go: - Add nonce-based filename support to WriteToDir - Add nonce-based file detection to LoadFromDir
140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
package security
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/manifest"
|
|
)
|
|
|
|
// TestManifestFilenameNonce verifies that manifest filenames include a cryptographic nonce
|
|
// to prevent information disclosure in multi-tenant environments where predictable
|
|
// filenames could be enumerated.
|
|
func TestManifestFilenameNonce(t *testing.T) {
|
|
t.Run("FilenameIncludesNonce", func(t *testing.T) {
|
|
// Generate multiple filenames and verify they are unique
|
|
filenames := make(map[string]bool)
|
|
for i := 0; i < 10; i++ {
|
|
filename, err := manifest.GenerateManifestFilename()
|
|
if err != nil {
|
|
t.Fatalf("GenerateManifestFilename failed: %v", err)
|
|
}
|
|
|
|
// Verify format: run_manifest_<nonce>.json
|
|
if !strings.HasPrefix(filename, "run_manifest_") {
|
|
t.Errorf("filename %q missing required prefix 'run_manifest_'", filename)
|
|
}
|
|
if !strings.HasSuffix(filename, ".json") {
|
|
t.Errorf("filename %q missing required suffix '.json'", filename)
|
|
}
|
|
|
|
// Extract and verify nonce
|
|
nonce := manifest.ParseManifestFilename(filename)
|
|
if nonce == "" {
|
|
t.Errorf("failed to parse nonce from filename %q", filename)
|
|
}
|
|
if len(nonce) != 32 {
|
|
t.Errorf("nonce length = %d, want 32 hex chars", len(nonce))
|
|
}
|
|
|
|
// Verify uniqueness (no collisions in 10 generations)
|
|
if filenames[filename] {
|
|
t.Errorf("duplicate filename generated: %q", filename)
|
|
}
|
|
filenames[filename] = true
|
|
}
|
|
})
|
|
|
|
t.Run("ManifestWrittenWithNonce", func(t *testing.T) {
|
|
// Generate a nonce for the manifest
|
|
nonce, err := manifest.GenerateManifestNonce()
|
|
if err != nil {
|
|
t.Fatalf("GenerateManifestNonce failed: %v", err)
|
|
}
|
|
|
|
// Create a manifest with nonce in Environment
|
|
created := time.Now().UTC()
|
|
m := manifest.NewRunManifest("run-test-nonce", "task-nonce", "job-nonce", created)
|
|
m.CommitID = "deadbeef"
|
|
m.Environment = &manifest.ExecutionEnvironment{
|
|
ConfigHash: "abc123",
|
|
ManifestNonce: nonce,
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
if err := m.WriteToDir(dir); err != nil {
|
|
t.Fatalf("WriteToDir failed: %v", err)
|
|
}
|
|
|
|
// List files in directory
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("ReadDir failed: %v", err)
|
|
}
|
|
|
|
// Find manifest file
|
|
var manifestFile string
|
|
for _, entry := range entries {
|
|
if strings.HasPrefix(entry.Name(), "run_manifest_") && strings.HasSuffix(entry.Name(), ".json") {
|
|
manifestFile = entry.Name()
|
|
break
|
|
}
|
|
}
|
|
|
|
if manifestFile == "" {
|
|
t.Fatal("no manifest file found with expected naming pattern")
|
|
}
|
|
|
|
// Verify nonce is present in filename
|
|
parsedNonce := manifest.ParseManifestFilename(manifestFile)
|
|
if parsedNonce == "" {
|
|
t.Errorf("manifest file %q does not contain a valid nonce", manifestFile)
|
|
}
|
|
if parsedNonce != nonce {
|
|
t.Errorf("nonce mismatch: got %q, want %q", parsedNonce, nonce)
|
|
}
|
|
if len(parsedNonce) != 32 {
|
|
t.Errorf("nonce length = %d, want 32 hex chars", len(parsedNonce))
|
|
}
|
|
|
|
// Verify file can be loaded back
|
|
loaded, err := manifest.LoadFromDir(dir)
|
|
if err != nil {
|
|
t.Fatalf("LoadFromDir failed: %v", err)
|
|
}
|
|
if loaded.RunID != m.RunID {
|
|
t.Errorf("loaded RunID = %q, want %q", loaded.RunID, m.RunID)
|
|
}
|
|
})
|
|
|
|
t.Run("NonceUniqueness", func(t *testing.T) {
|
|
// Generate many nonces to check for collisions (statistical test)
|
|
nonces := make(map[string]int)
|
|
iterations := 100
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
nonce, err := manifest.GenerateManifestNonce()
|
|
if err != nil {
|
|
t.Fatalf("GenerateManifestNonce failed: %v", err)
|
|
}
|
|
nonces[nonce]++
|
|
}
|
|
|
|
// Check for any collisions
|
|
collisions := 0
|
|
for nonce, count := range nonces {
|
|
if count > 1 {
|
|
t.Errorf("nonce collision detected: %q appeared %d times", nonce, count)
|
|
collisions++
|
|
}
|
|
}
|
|
|
|
if collisions > 0 {
|
|
t.Fatalf("detected %d nonce collisions in %d iterations", collisions, iterations)
|
|
}
|
|
|
|
t.Logf("Generated %d unique nonces with no collisions", iterations)
|
|
})
|
|
}
|