QA Engineer Skills 2026QA-2026IAM Policy and Network Rule Verification

IAM Policy and Network Rule Verification

Why IAM Testing Is Critical

IAM policy misconfigurations are consistently the top cloud security risk. A single overly permissive policy can grant an attacker access to every resource in your account. A single missing condition key can allow actions from unauthorized networks. Yet most teams treat IAM policies as "set and forget" -- they create them during initial setup and never test them.

Testing IAM policies programmatically means you can catch overly permissive access, missing conditions, and policy drift as part of your CI pipeline, not after a security incident.


Programmatic IAM Policy Testing

Python Test Suite for IAM Policies

# tests/test_iam_policies.py
import json
import pytest
import os
import glob

def load_policy(path):
    """Load an IAM policy document from a JSON file."""
    with open(path) as f:
        return json.load(f)

def get_all_policy_files():
    """Find all IAM policy JSON files in the Terraform directory."""
    return glob.glob("terraform/policies/*.json")

class TestLambdaExecutionRole:
    """Tests for the Lambda execution role policy."""
    policy = load_policy("terraform/policies/lambda-execution-role.json")

    def test_no_wildcard_resources(self):
        """IAM policies must never use Resource: '*' with mutating actions."""
        for statement in self.policy["Statement"]:
            if statement["Effect"] == "Allow":
                actions = statement.get("Action", [])
                if isinstance(actions, str):
                    actions = [actions]

                mutating = [a for a in actions if not a.endswith(":Get*")
                           and not a.endswith(":List*")
                           and not a.endswith(":Describe*")]

                if mutating:
                    resources = statement.get("Resource", [])
                    if isinstance(resources, str):
                        resources = [resources]
                    assert "*" not in resources, \
                        f"Wildcard resource with mutating actions: {mutating}"

    def test_no_admin_access(self):
        """No policy should grant full admin access."""
        for statement in self.policy["Statement"]:
            if statement["Effect"] == "Allow":
                actions = statement.get("Action", [])
                if isinstance(actions, str):
                    actions = [actions]
                assert "*" not in actions, "Policy grants full admin access"
                assert "iam:*" not in actions, "Policy grants full IAM access"

    def test_has_condition_keys(self):
        """Sensitive actions should have condition constraints."""
        for statement in self.policy["Statement"]:
            actions = statement.get("Action", [])
            if isinstance(actions, str):
                actions = [actions]
            sensitive = [a for a in actions if "s3:Delete" in a or "dynamodb:Delete" in a]
            if sensitive:
                assert "Condition" in statement, \
                    f"Sensitive actions {sensitive} lack Condition constraints"

    def test_no_pass_role_without_conditions(self):
        """iam:PassRole must have a condition limiting which roles can be passed."""
        for statement in self.policy["Statement"]:
            if statement["Effect"] == "Allow":
                actions = statement.get("Action", [])
                if isinstance(actions, str):
                    actions = [actions]
                if "iam:PassRole" in actions:
                    assert "Condition" in statement, \
                        "iam:PassRole must have conditions (e.g., iam:PassedToService)"

Generic Policy Scanner

# tests/test_all_iam_policies.py
import json
import glob
import pytest

POLICY_FILES = glob.glob("terraform/policies/*.json")

@pytest.mark.parametrize("policy_path", POLICY_FILES)
def test_no_wildcard_actions(policy_path):
    """No policy should grant Action: '*'."""
    with open(policy_path) as f:
        policy = json.load(f)

    for statement in policy.get("Statement", []):
        if statement.get("Effect") == "Allow":
            actions = statement.get("Action", [])
            if isinstance(actions, str):
                actions = [actions]
            assert "*" not in actions, \
                f"{policy_path}: Statement grants wildcard action"

@pytest.mark.parametrize("policy_path", POLICY_FILES)
def test_no_wildcard_resource_with_write(policy_path):
    """No write action should use Resource: '*'."""
    with open(policy_path) as f:
        policy = json.load(f)

    readonly_suffixes = [":Get*", ":List*", ":Describe*", ":Head*"]

    for statement in policy.get("Statement", []):
        if statement.get("Effect") == "Allow":
            actions = statement.get("Action", [])
            if isinstance(actions, str):
                actions = [actions]

            has_write = any(
                not any(a.endswith(suffix) for suffix in readonly_suffixes)
                for a in actions
            )

            if has_write:
                resources = statement.get("Resource", [])
                if isinstance(resources, str):
                    resources = [resources]
                assert "*" not in resources, \
                    f"{policy_path}: Wildcard resource with write actions"

@pytest.mark.parametrize("policy_path", POLICY_FILES)
def test_deny_statements_exist(policy_path):
    """Policies should include explicit Deny statements for dangerous actions."""
    with open(policy_path) as f:
        policy = json.load(f)

    dangerous_actions = ["iam:CreateUser", "iam:CreateAccessKey",
                         "organizations:LeaveOrganization", "ec2:RunInstances"]

    for statement in policy.get("Statement", []):
        if statement.get("Effect") == "Allow":
            actions = statement.get("Action", [])
            if isinstance(actions, str):
                actions = [actions]
            for dangerous in dangerous_actions:
                assert dangerous not in actions, \
                    f"{policy_path}: Allows dangerous action {dangerous}"

Network Rule Verification

Security Group Testing with Terraform Plan

# tests/test_network_rules.py
import json
import pytest

@pytest.fixture
def tfplan():
    """Load Terraform plan JSON."""
    with open("tfplan.json") as f:
        return json.load(f)

def test_no_public_ingress_on_database_sg(tfplan):
    """Database security groups must not allow ingress from 0.0.0.0/0."""
    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_security_group":
            if "database" in change["address"] or "rds" in change["address"]:
                ingress_rules = change["change"]["after"].get("ingress", [])
                for rule in ingress_rules:
                    cidrs = rule.get("cidr_blocks", [])
                    assert "0.0.0.0/0" not in cidrs, \
                        f"Database SG {change['address']} allows public ingress"
                    assert "::/0" not in cidrs, \
                        f"Database SG {change['address']} allows public IPv6 ingress"

def test_database_sg_only_allows_specific_ports(tfplan):
    """Database security groups should only allow database ports."""
    allowed_ports = {5432, 3306, 27017, 6379}  # Postgres, MySQL, Mongo, Redis

    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_security_group":
            if "database" in change["address"] or "rds" in change["address"]:
                ingress_rules = change["change"]["after"].get("ingress", [])
                for rule in ingress_rules:
                    from_port = rule.get("from_port", 0)
                    to_port = rule.get("to_port", 0)
                    assert from_port in allowed_ports, \
                        f"Database SG allows unexpected port {from_port}"
                    assert from_port == to_port, \
                        f"Database SG has port range {from_port}-{to_port}"

def test_no_unrestricted_egress(tfplan):
    """Security groups should not have unrestricted egress to the internet."""
    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_security_group":
            egress_rules = change["change"]["after"].get("egress", [])
            for rule in egress_rules:
                if "0.0.0.0/0" in rule.get("cidr_blocks", []):
                    # If egress is open, it must not be on all ports
                    assert rule.get("from_port", 0) != 0 or \
                           rule.get("to_port", 0) != 65535, \
                        f"SG {change['address']} has unrestricted egress on all ports"

def test_ssh_restricted_to_vpn(tfplan):
    """SSH access (port 22) must be restricted to VPN CIDR blocks only."""
    vpn_cidrs = ["10.0.0.0/8", "172.16.0.0/12"]

    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_security_group_rule":
            after = change["change"]["after"]
            if after.get("from_port") == 22 and after.get("type") == "ingress":
                cidrs = after.get("cidr_blocks", [])
                for cidr in cidrs:
                    assert cidr in vpn_cidrs, \
                        f"SSH rule in {change['address']} allows access from {cidr}"

VPC Configuration Testing

def test_private_subnets_have_no_public_ips(tfplan):
    """Private subnets must not auto-assign public IPs."""
    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_subnet":
            if "private" in change["address"]:
                map_public = change["change"]["after"].get(
                    "map_public_ip_on_launch", False
                )
                assert map_public is False, \
                    f"Private subnet {change['address']} assigns public IPs"

def test_nacl_denies_known_bad_ports(tfplan):
    """Network ACLs should deny traffic on known dangerous ports."""
    dangerous_ports = [23, 135, 139, 445, 1433, 3389]  # Telnet, RPC, SMB, MSSQL, RDP

    for change in tfplan["resource_changes"]:
        if change["type"] == "aws_network_acl_rule":
            after = change["change"]["after"]
            if after.get("rule_action") == "allow":
                from_port = after.get("from_port", 0)
                to_port = after.get("to_port", 0)
                for port in dangerous_ports:
                    if from_port <= port <= to_port:
                        pytest.fail(
                            f"NACL rule {change['address']} allows "
                            f"dangerous port {port}"
                        )

AWS IAM Access Analyzer Integration

Beyond static analysis, AWS IAM Access Analyzer can validate policies at runtime:

import boto3

def test_policy_with_access_analyzer():
    """Use AWS IAM Access Analyzer to validate policy documents."""
    client = boto3.client("accessanalyzer")

    with open("terraform/policies/lambda-execution-role.json") as f:
        policy_document = f.read()

    response = client.validate_policy(
        policyDocument=policy_document,
        policyType="IDENTITY_POLICY",
    )

    errors = [
        finding for finding in response["findings"]
        if finding["findingType"] == "ERROR"
    ]
    warnings = [
        finding for finding in response["findings"]
        if finding["findingType"] == "WARNING"
    ]
    security_warnings = [
        finding for finding in response["findings"]
        if finding["findingType"] == "SECURITY_WARNING"
    ]

    assert len(errors) == 0, f"Policy has errors: {errors}"
    assert len(security_warnings) == 0, f"Policy has security warnings: {security_warnings}"

    if warnings:
        for w in warnings:
            print(f"  WARNING: {w['issueCode']}: {w['message']}")

Integrating Into CI

# .github/workflows/iam-network-tests.yml
name: IAM and Network Policy Tests
on:
  pull_request:
    paths:
      - 'terraform/policies/**'
      - 'terraform/modules/networking/**'
      - 'terraform/modules/security/**'

jobs:
  policy-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install pytest
      - name: Run IAM policy tests
        run: pytest tests/test_iam_policies.py tests/test_network_rules.py -v

IAM and network testing is high-value, low-effort work. These tests run in seconds, require no cloud credentials (they analyze JSON files), and catch the most dangerous class of infrastructure misconfigurations.