Skip to content

Cloud Security Automation Scripts

This section showcases custom security tools and policy analysis scripts written in Go. These scripts can run in standalone CLI environments or integrate into cloud threat detection pipelines.


1. SCP Conflict Auditor (scp-auditor.go)

Purpose

Validates and simulates AWS Service Control Policy (SCP) evaluation logic across nested Organizational Units (OUs) to detect policy conflicts, redundant allows, or unintended blocks before they are pushed to AWS Organizations.

Code Implementation

// Purpose: Simulates and validates AWS Service Control Policy (SCP) evaluation logic across nested Organizational Units (OUs) to detect policy conflicts or unintended blocks.
package main

import (
    "fmt"
    "strings"
)

// Policy represents a simple SCP with its Statements.
type Policy struct {
    Name       string
    Statements []SCPStatement
}

// SCPStatement represents an individual SCP permission statement.
type SCPStatement struct {
    Effect   string   // Allow or Deny
    Action   []string // e.g. ["*"], ["s3:*"], ["iam:DeleteRole"]
    Resource []string // e.g. ["*"]
}

// OrgNode represents a level in the AWS Organizations hierarchy (Root, OU, or Account).
type OrgNode struct {
    Name     string
    Type     string // ROOT, OU, ACCOUNT
    Policies []Policy
    Parent   *OrgNode
}

// MatchesAction checks if an action matches the patterns in the statement.
func MatchesAction(action string, patterns []string) bool {
    action = strings.ToLower(action)
    for _, pattern := range patterns {
        pattern = strings.ToLower(pattern)
        if pattern == "*" {
            return true
        }
        if strings.HasSuffix(pattern, "*") {
            prefix := strings.TrimSuffix(pattern, "*")
            if strings.HasPrefix(action, prefix) {
                return true
            }
        }
        if action == pattern {
            return true
        }
    }
    return false
}

// MatchesResource checks if a resource matches the patterns in the statement.
func MatchesResource(resource string, patterns []string) bool {
    // For simplicity in simulation, we support basic wildcard matching
    for _, pattern := range patterns {
        if pattern == "*" {
            return true
        }
        if strings.Contains(pattern, "*") {
            prefix := strings.Split(pattern, "*")[0]
            if strings.HasPrefix(resource, prefix) {
                return true
            }
        }
        if resource == pattern {
            return true
        }
    }
    return false
}

// EvaluateAction determines if an action is permitted through the organization hierarchy to the target account.
// AWS SCP Evaluation Rule:
// 1. Default is implicit deny.
// 2. An explicit Deny at any level blocks the request immediately.
// 3. For an action to be allowed, it must be explicitly allowed at every level of the hierarchy (e.g. via FullAWSAccess or custom allows).
func EvaluateAction(action string, resource string, leaf *OrgNode) (bool, string) {
    // Build the path from leaf to root
    var path []*OrgNode
    curr := leaf
    for curr != nil {
        path = append([]*OrgNode{curr}, path...) // prepend to preserve root-to-leaf order
        curr = curr.Parent
    }

    fmt.Printf("Evaluating path: ")
    for i, node := range path {
        if i > 0 {
            fmt.Print(" -> ")
        }
        fmt.Printf("[%s: %s]", node.Type, node.Name)
    }
    fmt.Println()

    // Step 1: Check for any explicit DENY across all levels (evaluated globally)
    for _, node := range path {
        for _, policy := range node.Policies {
            for _, stmt := range policy.Statements {
                if strings.ToLower(stmt.Effect) == "deny" {
                    if MatchesAction(action, stmt.Action) && MatchesResource(resource, stmt.Resource) {
                        return false, fmt.Sprintf("Blocked by explicit DENY in policy '%s' at level [%s: %s]", policy.Name, node.Type, node.Name)
                    }
                }
            }
        }
    }

    // Step 2: Ensure there is an explicit ALLOW at each level of the hierarchy.
    // In AWS Organizations, if any level does not allow the action (either via FullAWSAccess or a specific rule), the evaluation fails (implicit deny).
    for _, node := range path {
        levelAllowed := false
        for _, policy := range node.Policies {
            for _, stmt := range policy.Statements {
                if strings.ToLower(stmt.Effect) == "allow" {
                    if MatchesAction(action, stmt.Action) && MatchesResource(resource, stmt.Resource) {
                        levelAllowed = true
                        break
                    }
                }
            }
            if levelAllowed {
                break
            }
        }
        if !levelAllowed {
            return false, fmt.Sprintf("Blocked by implicit DENY: Action not allowed at level [%s: %s]", node.Type, node.Name)
        }
    }

    return true, "Allowed by SCP tree verification"
}

func main() {
    fmt.Println("==================================================")
    fmt.Println("🛡️ AWS Organizations SCP Hierarchy Simulator")
    fmt.Println("==================================================")

    // Create policies
    fullAWSAccess := Policy{
        Name: "FullAWSAccess",
        Statements: []SCPStatement{
            {Effect: "Allow", Action: []string{"*"}, Resource: []string{"*"}},
        },
    }

    restrictiveS3 := Policy{
        Name: "RestrictiveS3Only",
        Statements: []SCPStatement{
            {Effect: "Allow", Action: []string{"s3:*"}, Resource: []string{"*"}},
        },
    }

    denyRootUser := Policy{
        Name: "DenyRootUserUsage",
        Statements: []SCPStatement{
            {Effect: "Deny", Action: []string{"*"}, Resource: []string{"*"}}, // Simplified; normally condition matches Root account user
        },
    }

    denyLeavingOrg := Policy{
        Name: "DenyLeaveOrganization",
        Statements: []SCPStatement{
            {Effect: "Deny", Action: []string{"organizations:LeaveOrganization"}, Resource: []string{"*"}},
        },
    }

    // Build hierarchy
    // Root -> SecurityOU -> ProdAccount
    rootNode := &OrgNode{Name: "Root-OU", Type: "ROOT", Policies: []Policy{fullAWSAccess, denyLeavingOrg}}

    securityOU := &OrgNode{
        Name:     "Security-Core-OU",
        Type:     "OU",
        Policies: []Policy{fullAWSAccess},
        Parent:   rootNode,
    }

    prodAccount := &OrgNode{
        Name:     "Production-App-Account",
        Type:     "ACCOUNT",
        Policies: []Policy{fullAWSAccess},
        Parent:   securityOU,
    }

    // SandboxOU (does not inherit FullAWSAccess if we replace it, but let's test a restricted OU)
    sandboxOU := &OrgNode{
        Name:     "Sandbox-OU",
        Type:     "OU",
        Policies: []Policy{restrictiveS3}, // Custom allowed list: only S3
        Parent:   rootNode,
    }

    devAccount := &OrgNode{
        Name:     "Dev-Test-Account",
        Type:     "ACCOUNT",
        Policies: []Policy{fullAWSAccess},
        Parent:   sandboxOU,
    }

    // Test Case 1: Standard permitted action in Production
    fmt.Println("\n[Test 1] Can Prod Account run 's3:CreateBucket'?")
    allowed, reason := EvaluateAction("s3:CreateBucket", "arn:aws:s3:::my-bucket", prodAccount)
    fmt.Printf("Result: %t. Reason: %s\n", allowed, reason)

    // Test Case 2: Action blocked by explicit deny in Root
    fmt.Println("\n[Test 2] Can Prod Account run 'organizations:LeaveOrganization'?")
    allowed, reason = EvaluateAction("organizations:LeaveOrganization", "*", prodAccount)
    fmt.Printf("Result: %t. Reason: %s\n", allowed, reason)

    // Test Case 3: Action blocked due to restriction in intermediate OU (Sandbox has only S3 allowed)
    fmt.Println("\n[Test 3] Can Dev Account run 'ec2:RunInstances'?")
    allowed, reason = EvaluateAction("ec2:RunInstances", "*", devAccount)
    fmt.Printf("Result: %t. Reason: %s\n", allowed, reason)

    // Test Case 4: Action allowed in Sandbox OU (S3 allowed at sandbox, full access at account)
    fmt.Println("\n[Test 4] Can Dev Account run 's3:ListBucket'?")
    allowed, reason = EvaluateAction("s3:ListBucket", "arn:aws:s3:::my-sandbox-bucket", devAccount)
    fmt.Printf("Result: %t. Reason: %s\n", allowed, reason)

    // Test Case 5: Root OU has root-denial policy added
    fmt.Println("\n[Test 5] Attaching explicit '*' deny policy at root to test global block.")
    rootNode.Policies = append(rootNode.Policies, denyRootUser)
    allowed, reason = EvaluateAction("s3:ListBucket", "*", prodAccount)
    fmt.Printf("Result: %t. Reason: %s\n", allowed, reason)
}

2. IAM Policy Analyzer (iam-analyzer.go)

Purpose

Scans simulated or provided AWS IAM policy documents (in JSON format) for over-permissive configurations, wildcards, and administrative privilege escalation paths.

Code Implementation

// Purpose: Scans simulated or provided AWS IAM policy documents (in JSON format) for over-permissive configurations, wildcards, and privilege escalation paths.
package main

import (
    "encoding/json"
    "fmt"
    "strings"
)

// PolicyDocument represents a standard AWS IAM policy structure.
type PolicyDocument struct {
    Version   string      `json:"Version"`
    Statement []Statement `json:"Statement"`
}

// Statement represents an individual policy statement block.
type Statement struct {
    Sid       string      `json:"Sid,omitempty"`
    Effect    string      `json:"Effect"`
    Action    interface{} `json:"Action"`   // Can be string or array of strings
    Resource  interface{} `json:"Resource"` // Can be string or array of strings
    Condition interface{} `json:"Condition,omitempty"`
}

// Helper to convert interface{} action/resource into standard string slice.
func getSlice(input interface{}) []string {
    if input == nil {
        return nil
    }
    switch val := input.(type) {
    case string:
        return []string{val}
    case []interface{}:
        var out []string
        for _, item := range val {
            if str, ok := item.(string); ok {
                out = append(out, str)
            }
        }
        return out
    case []string:
        return val
    }
    return nil
}

// List of AWS actions that can lead to privilege escalation.
// Source: https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
var privEscActions = map[string]string{
    "iam:createpolicyversion":      "CreatePolicyVersion: Allows creating a new policy version, setting it to default, leading to full IAM privilege modification.",
    "iam:setdefaultpolicyversion":  "SetDefaultPolicyVersion: Allows setting an existing inactive policy version as default, bypassing checks.",
    "iam:passrole":                 "PassRole: Combined with EC2/Glue/Lambda creation, allows passing high-privilege roles to compute resources.",
    "iam:createaccesskey":          "CreateAccessKey: Allows creating new access keys for other users, bypassing their MFA configuration.",
    "iam:createuser":               "CreateUser: Allows creating arbitrary IAM users.",
    "iam:addusertogroup":           "AddUserToGroup: Allows adding current user or arbitrary user to administrative groups.",
    "iam:attachuserpolicy":         "AttachUserPolicy: Directly attaches administrative policies to user accounts.",
    "iam:attachgrouppolicy":        "AttachGroupPolicy: Attaches administrative policies to a group the user belongs to.",
    "iam:attachrolepolicy":         "AttachRolePolicy: Attaches administrative policies to a role the user can assume.",
    "iam:putuserpolicy":            "PutUserPolicy: Creates/updates inline administrative policies on user accounts.",
    "iam:putgrouppolicy":           "PutGroupPolicy: Creates/updates inline administrative policies on group accounts.",
    "iam:putrolepolicy":            "PutRolePolicy: Creates/updates inline administrative policies on roles.",
    "iam:updateassumerolepolicy":   "UpdateAssumeRolePolicy: Alters trust relationships of roles, allowing external cross-account access.",
    "iam:createloginprofile":       "CreateLoginProfile: Allows setting/resetting passwords for console access.",
    "iam:updateloginprofile":       "UpdateLoginProfile: Allows modifying console passwords for existing administrative users.",
}

func main() {
    // Sample policy demonstrating multiple critical misconfigurations.
    samplePolicyJSON := `{
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "AllowAllGlobalActions",
                "Effect": "Allow",
                "Action": "*",
                "Resource": "*"
            },
            {
                "Sid": "PrivilegeEscalationVector1",
                "Effect": "Allow",
                "Action": ["iam:CreatePolicyVersion", "iam:PassRole"],
                "Resource": "arn:aws:iam::123456789012:role/admin-role"
            },
            {
                "Sid": "DangerousWildcardResource",
                "Effect": "Allow",
                "Action": "s3:GetObject",
                "Resource": "*"
            }
        ]
    }`

    fmt.Println("==================================================")
    fmt.Println("🛡️ AWS IAM Policy Analyzer & Privilege Escalation Scanner")
    fmt.Println("==================================================")

    var doc PolicyDocument
    err := json.Unmarshal([]byte(samplePolicyJSON), &doc)
    if err != nil {
        fmt.Printf("Error parsing policy: %v\n", err)
        return
    }

    findings := 0

    for _, stmt := range doc.Statement {
        if strings.ToLower(stmt.Effect) != "allow" {
            continue
        }

        actions := getSlice(stmt.Action)
        resources := getSlice(stmt.Resource)

        // 1. Audit Wildcard Action and Resource
        hasWildcardAction := false
        for _, action := range actions {
            if action == "*" {
                hasWildcardAction = true
                break
            }
        }

        hasWildcardResource := false
        for _, res := range resources {
            if res == "*" {
                hasWildcardResource = true
                break
            }
        }

        if hasWildcardAction && hasWildcardResource {
            fmt.Printf("[CRITICAL] Statement '%s' allows Administrator Access ('*' on '*').\n", stmt.Sid)
            findings++
        } else if hasWildcardAction {
            fmt.Printf("[HIGH] Statement '%s' contains wildcard action '*' over resources: %v.\n", stmt.Sid, resources)
            findings++
        }

        // 2. Audit Privilege Escalation Risks
        for _, action := range actions {
            loweredAction := strings.ToLower(action)
            if desc, exists := privEscActions[loweredAction]; exists {
                fmt.Printf("[HIGH] Privilege Escalation risk in statement '%s': %s (Target Resource: %v)\n", stmt.Sid, desc, resources)
                findings++
            }
        }

        // 3. Unsafe Resource Wildcard with sensitive operations
        for _, action := range actions {
            loweredAction := strings.ToLower(action)
            if hasWildcardResource && (strings.HasPrefix(loweredAction, "s3:") || strings.HasPrefix(loweredAction, "dynamodb:") || strings.HasPrefix(loweredAction, "rds:")) {
                if loweredAction != "s3:*" && !strings.Contains(loweredAction, "list") && !strings.Contains(loweredAction, "describe") {
                    fmt.Printf("[MEDIUM] Data store action '%s' configured with wildcard '*' resource in statement '%s'.\n", action, stmt.Sid)
                    findings++
                }
            }
        }
    }

    fmt.Printf("\nScan completed. Found %d issues.\n", findings)
}

3. Kubernetes RBAC Auditor (k8s-rbac-auditor.go)

Purpose

Scans Kubernetes RBAC (Roles and ClusterRoles) configurations for high-risk permissions, wildcard verbs, and administrative escalation vectors (such as bind or escalate privileges).

Code Implementation

// Purpose: Scans Kubernetes RBAC (Roles and ClusterRoles) configurations for high-risk permissions, wildcard verbs, and administrative escalation vectors.
package main

import (
    "fmt"
    "strings"
)

// PolicyRule matches a single rule in a Kubernetes Role/ClusterRole.
type PolicyRule struct {
    APIGroups []string
    Resources []string
    Verbs     []string
}

// Role represents a Role or ClusterRole object.
type Role struct {
    Name      string
    Namespace string // Empty for ClusterRole
    IsCluster bool
    Rules     []PolicyRule
}

// Subject represents a binding target (User, Group, or ServiceAccount).
type Subject struct {
    Kind      string // User, Group, ServiceAccount
    Name      string
    Namespace string
}

// RoleBinding represents a RoleBinding or ClusterRoleBinding.
type RoleBinding struct {
    Name      string
    Namespace string // Empty for ClusterRoleBinding
    RoleRef   string
    Subjects  []Subject
}

// High-risk verbs and resources mapping for K8s privilege escalation.
// Ref: https://kubernetes.io/docs/concepts/security/rbac-good-practices/
var highRiskCombinations = []struct {
    Resources []string
    Verbs     []string
    Severity  string
    Reason    string
}{
    {
        Resources: []string{"*"},
        Verbs:     []string{"*"},
        Severity:  "CRITICAL",
        Reason:    "Full cluster-admin access equivalent. Complete compromise vector.",
    },
    {
        Resources: []string{"roles", "clusterroles", "rolebindings", "clusterrolebindings"},
        Verbs:     []string{"bind", "escalate", "*"},
        Severity:  "CRITICAL",
        Reason:    "Can bypass RBAC checks to bind high-privilege roles or escalate privileges.",
    },
    {
        Resources: []string{"secrets"},
        Verbs:     []string{"get", "list", "watch", "*"},
        Severity:  "HIGH",
        Reason:    "Allows harvesting credential tokens, service accounts, and database secrets.",
    },
    {
        Resources: []string{"pods"},
        Verbs:     []string{"create", "update", "patch", "*"},
        Severity:  "HIGH",
        Reason:    "Can spawn pods with hostPath mounts, hostNetwork, or privileged mode to break containment.",
    },
    {
        Resources: []string{"deployments", "daemonsets", "statefulsets", "jobs", "cronjobs"},
        Verbs:     []string{"create", "update", "patch", "*"},
        Severity:  "HIGH",
        Reason:    "Allows indirect workload creation (e.g. creating malicious pods via deployment spec).",
    },
    {
        Resources: []string{"certificatesigningrequests"},
        Verbs:     []string{"create", "update", "patch", "*"},
        Severity:  "HIGH",
        Reason:    "Can approve custom certificates to impersonate cluster admins/nodes.",
    },
    {
        Resources: []string{"serviceaccounts/token", "tokenreviews"},
        Verbs:     []string{"create", "*"},
        Severity:  "HIGH",
        Reason:    "Can generate tokens for other service accounts, facilitating identity theft.",
    },
}

// Helper to check if a list contains a wildcard or specific value
func contains(list []string, val string) bool {
    for _, item := range list {
        if item == "*" || strings.ToLower(item) == strings.ToLower(val) {
            return true
        }
    }
    return false
}

// Audits a single role against high-risk rules.
func AuditRole(role Role) []string {
    var alerts []string

    for _, rule := range role.Rules {
        for _, risk := range highRiskCombinations {
            // Check if any rule resource matches the risk resource
            resourceMatch := false
            var matchedResource string
            for _, rRes := range rule.Resources {
                for _, riskRes := range risk.Resources {
                    if rRes == "*" || strings.ToLower(rRes) == strings.ToLower(riskRes) {
                        resourceMatch = true
                        matchedResource = rRes
                        break
                    }
                }
                if resourceMatch {
                    break
                }
            }

            // Check if any rule verb matches the risk verb
            verbMatch := false
            var matchedVerb string
            for _, rVerb := range rule.Verbs {
                for _, riskVerb := range risk.Verbs {
                    if rVerb == "*" || strings.ToLower(rVerb) == strings.ToLower(riskVerb) {
                        verbMatch = true
                        matchedVerb = rVerb
                        break
                    }
                }
                if verbMatch {
                    break
                }
            }

            if resourceMatch && verbMatch {
                alerts = append(alerts, fmt.Sprintf("[%s] Role '%s' allows resource '%s' with verb '%s': %s",
                    risk.Severity, role.Name, matchedResource, matchedVerb, risk.Reason))
            }
        }
    }
    return alerts
}

func main() {
    fmt.Println("==================================================")
    fmt.Println("🛡️ Kubernetes RBAC Security Audit Utility")
    fmt.Println("==================================================")

    // Simulated Kubernetes Roles
    roles := []Role{
        {
            Name:      "cluster-admin-role",
            IsCluster: true,
            Rules: []PolicyRule{
                {APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}},
            },
        },
        {
            Name:      "secrets-harvester",
            Namespace: "default",
            IsCluster: false,
            Rules: []PolicyRule{
                {APIGroups: []string{""}, Resources: []string{"secrets"}, Verbs: []string{"list", "get"}},
            },
        },
        {
            Name:      "pod-deployer",
            Namespace: "kube-system",
            IsCluster: false,
            Rules: []PolicyRule{
                {APIGroups: []string{"apps"}, Resources: []string{"deployments"}, Verbs: []string{"create"}},
            },
        },
        {
            Name:      "read-only",
            Namespace: "default",
            IsCluster: false,
            Rules: []PolicyRule{
                {APIGroups: []string{""}, Resources: []string{"pods", "services"}, Verbs: []string{"get", "list"}},
            },
        },
    }

    // Simulated Bindings
    bindings := []RoleBinding{
        {
            Name:      "admin-binding",
            RoleRef:   "cluster-admin-role",
            Subjects:  []Subject{{Kind: "Group", Name: "system:masters"}},
        },
        {
            Name:      "app-deploy-binding",
            Namespace: "kube-system",
            RoleRef:   "pod-deployer",
            Subjects:  []Subject{{Kind: "ServiceAccount", Name: "jenkins-ci-sa", Namespace: "kube-system"}},
        },
        {
            Name:      "read-binding",
            Namespace: "default",
            RoleRef:   "read-only",
            Subjects:  []Subject{{Kind: "User", Name: "alice@company.com"}},
        },
    }

    // Step 1: Audit all Roles
    fmt.Println("\n--- Stage 1: Scanning Roles & ClusterRoles for Risks ---")
    findingsMap := make(map[string][]string)
    for _, role := range roles {
        alerts := AuditRole(role)
        if len(alerts) > 0 {
            findingsMap[role.Name] = alerts
            for _, alert := range alerts {
                fmt.Println(alert)
            }
        }
    }
    if len(findingsMap) == 0 {
        fmt.Println("No high-risk roles identified.")
    }

    // Step 2: Correlate bindings with high-risk roles
    fmt.Println("\n--- Stage 2: Binding Correlation & Exposure Analysis ---")
    for _, binding := range bindings {
        alerts, exists := findingsMap[binding.RoleRef]
        if exists {
            fmt.Printf("\n⚠️  High-Risk Role '%s' is bound by '%s' to subjects:\n", binding.RoleRef, binding.Name)
            for _, sub := range binding.Subjects {
                nsStr := ""
                if sub.Namespace != "" {
                    nsStr = fmt.Sprintf(" in namespace '%s'", sub.Namespace)
                }
                fmt.Printf("   - %s: '%s'%s\n", sub.Kind, sub.Name, nsStr)
            }
            fmt.Println("   Impact Details:")
            for _, alert := range alerts {
                fmt.Printf("     * %s\n", alert)
            }
        }
    }
}