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