Scaffold dd0c/drift Go agent: CLI, scanner, scrubber, reporter, models
- cobra CLI: check (one-shot), watch (SQS consumer), version - models: DriftReport, DriftedResource, severity classification (critical/high/medium/low) - scanner: Terraform v4 state parser, resource counter - scrubber: regex + Shannon entropy secret detection (strict/permissive/off modes) - reporter: mTLS HTTP client with nonce replay prevention - tests: severity classification (8 cases), scrubber (AWS keys, RSA, entropy, attributes)
This commit is contained in:
86
products/02-iac-drift-detection/agent/pkg/models/drift.go
Normal file
86
products/02-iac-drift-detection/agent/pkg/models/drift.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// DriftedResource represents a single resource that has drifted from its IaC definition.
|
||||
type DriftedResource struct {
|
||||
ResourceType string `json:"resource_type"`
|
||||
ResourceAddress string `json:"resource_address"`
|
||||
Module string `json:"module,omitempty"`
|
||||
Provider string `json:"provider"`
|
||||
Severity DriftSeverity `json:"severity"`
|
||||
Diffs []AttributeDiff `json:"diffs"`
|
||||
}
|
||||
|
||||
// AttributeDiff represents a single attribute-level change.
|
||||
type AttributeDiff struct {
|
||||
AttributeName string `json:"attribute_name"`
|
||||
OldValue string `json:"old_value"`
|
||||
NewValue string `json:"new_value"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
}
|
||||
|
||||
// DriftSeverity classifies how dangerous a drift is.
|
||||
type DriftSeverity string
|
||||
|
||||
const (
|
||||
SeverityCritical DriftSeverity = "critical" // Security group, IAM policy
|
||||
SeverityHigh DriftSeverity = "high" // Instance type, storage config
|
||||
SeverityMedium DriftSeverity = "medium" // Tags, descriptions
|
||||
SeverityLow DriftSeverity = "low" // Cosmetic changes
|
||||
)
|
||||
|
||||
// DriftReport is the payload sent from agent to SaaS.
|
||||
type DriftReport struct {
|
||||
StackName string `json:"stack_name"`
|
||||
StackFingerprint string `json:"stack_fingerprint"` // Hash of backend config
|
||||
AgentVersion string `json:"agent_version"`
|
||||
ScannedAt time.Time `json:"scanned_at"`
|
||||
StateSerial int64 `json:"state_serial"`
|
||||
Lineage string `json:"lineage"`
|
||||
TotalResources int `json:"total_resources"`
|
||||
DriftedResources []DriftedResource `json:"drifted_resources"`
|
||||
DriftScore float64 `json:"drift_score"` // 0-100
|
||||
Nonce string `json:"nonce"` // Replay prevention
|
||||
}
|
||||
|
||||
// ClassifySeverity determines drift severity based on resource type and changed attributes.
|
||||
func ClassifySeverity(resourceType string, attrName string) DriftSeverity {
|
||||
// Security-critical resources
|
||||
criticalTypes := map[string]bool{
|
||||
"aws_security_group": true,
|
||||
"aws_security_group_rule": true,
|
||||
"aws_iam_policy": true,
|
||||
"aws_iam_role": true,
|
||||
"aws_iam_role_policy": true,
|
||||
"aws_iam_user_policy": true,
|
||||
"aws_kms_key": true,
|
||||
"aws_s3_bucket_policy": true,
|
||||
"aws_s3_bucket_acl": true,
|
||||
}
|
||||
|
||||
if criticalTypes[resourceType] {
|
||||
return SeverityCritical
|
||||
}
|
||||
|
||||
// High-impact attributes on any resource
|
||||
highAttrs := map[string]bool{
|
||||
"instance_type": true,
|
||||
"ami": true,
|
||||
"engine_version": true,
|
||||
"allocated_storage": true,
|
||||
"vpc_id": true,
|
||||
"subnet_id": true,
|
||||
}
|
||||
|
||||
if highAttrs[attrName] {
|
||||
return SeverityHigh
|
||||
}
|
||||
|
||||
// Tag changes are low severity
|
||||
if attrName == "tags" || attrName == "tags_all" {
|
||||
return SeverityLow
|
||||
}
|
||||
|
||||
return SeverityMedium
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package models
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestClassifySeverity_SecurityGroupIsCritical(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_security_group", "ingress")
|
||||
if sev != SeverityCritical {
|
||||
t.Fatalf("Expected critical, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_IAMPolicyIsCritical(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_iam_policy", "policy")
|
||||
if sev != SeverityCritical {
|
||||
t.Fatalf("Expected critical, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_InstanceTypeIsHigh(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_instance", "instance_type")
|
||||
if sev != SeverityHigh {
|
||||
t.Fatalf("Expected high, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_AMIIsHigh(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_instance", "ami")
|
||||
if sev != SeverityHigh {
|
||||
t.Fatalf("Expected high, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_TagsAreLow(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_instance", "tags")
|
||||
if sev != SeverityLow {
|
||||
t.Fatalf("Expected low, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_UnknownAttrIsMedium(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_instance", "some_random_attr")
|
||||
if sev != SeverityMedium {
|
||||
t.Fatalf("Expected medium, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_KMSKeyIsCritical(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_kms_key", "key_rotation")
|
||||
if sev != SeverityCritical {
|
||||
t.Fatalf("Expected critical, got %s", sev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifySeverity_S3BucketPolicyIsCritical(t *testing.T) {
|
||||
sev := ClassifySeverity("aws_s3_bucket_policy", "policy")
|
||||
if sev != SeverityCritical {
|
||||
t.Fatalf("Expected critical, got %s", sev)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user