package security import ( "os" "path/filepath" "testing" "github.com/jfraeys/fetch_ml/internal/crypto" "github.com/jfraeys/fetch_ml/internal/worker" ) // TestConfigIntegrityVerification verifies config file integrity and signature verification. // This test ensures that config files can be signed and their signatures verified. func TestConfigIntegrityVerification(t *testing.T) { t.Run("ConfigLoadWithoutSignature", func(t *testing.T) { // Create a temp config file tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") configContent := ` host: localhost port: 22 max_workers: 4 ` if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil { t.Fatalf("failed to write config: %v", err) } // Load config without signature verification (default behavior) cfg, err := worker.LoadConfig(configPath) if err != nil { t.Fatalf("LoadConfig failed: %v", err) } if cfg.Host != "localhost" { t.Errorf("host = %q, want localhost", cfg.Host) } if cfg.Port != 22 { t.Errorf("port = %d, want 22", cfg.Port) } }) t.Run("ConfigFileTamperingDetection", func(t *testing.T) { // Create a temp config file tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") configContent := []byte(` host: localhost port: 22 max_workers: 4 `) if err := os.WriteFile(configPath, configContent, 0600); err != nil { t.Fatalf("failed to write config: %v", err) } // Generate signing keys publicKey, privateKey, err := crypto.GenerateSigningKeys() if err != nil { t.Fatalf("GenerateSigningKeys failed: %v", err) } // Create signer signer, err := crypto.NewManifestSigner(privateKey, "test-key-1") if err != nil { t.Fatalf("NewManifestSigner failed: %v", err) } // Sign the config content result, err := signer.SignManifestBytes(configContent) if err != nil { t.Fatalf("SignManifestBytes failed: %v", err) } // Verify signature against original content valid, err := crypto.VerifyManifestBytes(configContent, result, publicKey) if err != nil { t.Fatalf("VerifyManifestBytes failed: %v", err) } if !valid { t.Error("signature should be valid for original content") } // Tamper with the config file tamperedContent := []byte(` host: malicious-host port: 22 max_workers: 4 `) if err := os.WriteFile(configPath, tamperedContent, 0600); err != nil { t.Fatalf("failed to write tampered config: %v", err) } // Verify signature against tampered content (should fail) valid, err = crypto.VerifyManifestBytes(tamperedContent, result, publicKey) if err != nil { // Expected - signature doesn't match t.Logf("Expected verification error for tampered content: %v", err) } if valid { t.Error("signature should be invalid for tampered content") } }) t.Run("MissingSignatureFile", func(t *testing.T) { tempDir := t.TempDir() configPath := filepath.Join(tempDir, "config.yaml") configContent := ` host: localhost port: 22 ` if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil { t.Fatalf("failed to write config: %v", err) } // Config loads without signature cfg, err := worker.LoadConfig(configPath) if err != nil { t.Fatalf("LoadConfig should work without signature: %v", err) } if cfg == nil { t.Error("expected config to be loaded") } }) } // TestConfigHashStability verifies that the same config produces the same hash func TestConfigHashStability(t *testing.T) { cfg := &worker.Config{ Host: "localhost", Port: 22, MaxWorkers: 4, GPUVendor: "nvidia", Sandbox: worker.SandboxConfig{ NetworkMode: "none", SeccompProfile: "default-hardened", }, } hash1, err := cfg.ComputeResolvedConfigHash() if err != nil { t.Fatalf("ComputeResolvedConfigHash failed: %v", err) } hash2, err := cfg.ComputeResolvedConfigHash() if err != nil { t.Fatalf("ComputeResolvedConfigHash failed: %v", err) } if hash1 != hash2 { t.Error("same config should produce identical hashes") } // Modify config and verify hash changes cfg.MaxWorkers = 8 hash3, err := cfg.ComputeResolvedConfigHash() if err != nil { t.Fatalf("ComputeResolvedConfigHash failed: %v", err) } if hash1 == hash3 { t.Error("different configs should produce different hashes") } }