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:
2026-03-01 02:42:53 +00:00
parent e626608535
commit 31cb36fb77
8 changed files with 666 additions and 0 deletions

View 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
}

View File

@@ -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)
}
}