# IAM policy evaluations ======================================================= # Controls: IAM-001 through IAM-006 package controls.aws.iam import data.common.definitions as defs import data.common.exemptions as exemptions import data.common.outputs as outputs import data.common.policydoc as policydoc # IAM-001 — Require Policy Attachment to Groups or Roles ---------------------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-001" some _, rc in input.resource_changes rc.type in {"aws_iam_user_policy", "aws_iam_user_policy_attachment"} rc.change.actions != ["delete"] meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("IAM policy attached directly to user via '%s.%s'", [rc.type, rc.name]), ) } # IAM-002 — Prohibit Long-Term IAM User Access Keys --------------------------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-002" some _, rc in input.resource_changes rc.type == "aws_iam_access_key" rc.change.actions != ["delete"] meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("Long-term IAM access key created: '%s'", [rc.name]), ) } # IAM-003 — Restrict Broad KMS Key Permissions -------------------------------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-003" some _, rc in input.resource_changes rc.type in {"aws_iam_role_policy", "aws_iam_user_policy", "aws_iam_group_policy"} rc.change.actions != ["delete"] doc := json.unmarshal(rc.change.after.policy) some stmt in doc.Statement stmt.Effect == "Allow" some action in policydoc.as_list(stmt.Action) _broad_kms_action(action) some resource in policydoc.as_list(stmt.Resource) resource == "*" meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("Broad KMS action '%s' on Resource * in '%s.%s'", [action, rc.type, rc.name]), ) } _broad_kms_action(action) if action == "kms:*" _broad_kms_action(action) if action == "kms:Decrypt" _broad_kms_action(action) if action == "kms:ReEncryptFrom" # IAM-004 — Require Permissions Boundary on Delegated Roles ------------------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-004" some _, rc in input.resource_changes rc.type == "aws_iam_role" rc.change.actions != ["delete"] not _has_permissions_boundary(rc.change.after) meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("IAM role '%s' has no permissions boundary set", [rc.name]), ) } _has_permissions_boundary(after) if { after.permissions_boundary != null after.permissions_boundary != "" } # IAM-005 — Prevent Confused-Deputy in Cross-Account Trust Policies ----------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-005" some _, rc in input.resource_changes rc.type == "aws_iam_role" rc.change.actions != ["delete"] doc := json.unmarshal(rc.change.after.assume_role_policy) some stmt in doc.Statement stmt.Effect == "Allow" _public_principal(stmt) not stmt.Condition meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("IAM role '%s' trust policy allows Principal * without a scoping Condition", [rc.name]), ) } _public_principal(stmt) if stmt.Principal == "*" _public_principal(stmt) if stmt.Principal.AWS == "*" _public_principal(stmt) if { some p in policydoc.as_list(stmt.Principal.AWS) p == "*" } # IAM-006 — Detect Hardcoded Credentials and Secrets in Terraform ------------- deny contains msg if { policy_id := "ICP-TF-AWS-IAM-006" some name, provider in input.configuration.provider_config startswith(name, "aws") some field in defs.aws_cred_fields provider.expressions[field].constant_value meta := data.policies[policy_id] meta.status == "active" not exemptions.policy_exemption(policy_id) not exemptions.severity_exemption(meta.severity) msg := outputs.violation( policy_id, meta, sprintf("Hardcoded AWS %s detected in provider block '%s'", [field, name]), ) }