IAM Inline Policy Network Admin


Description

This policy validates that IAM entities (Groups, Roles, and Users) do not have inline policies attached that grant network admin privileges. Inline policies are more difficult to track and audit than managed policies, and can lead to persistent unexpected access.

Query · python

import json

from policyuniverse.action_categories import categories_for_actions
from policyuniverse.expander_minimizer import expand_policy
from policyuniverse.policy import Policy

# White listed policies (e.g. the approved network admin policy) can be specified here, or as an
# exception to this policy.

ADMIN_ACTIONS = {
    "Tagging",
    "Write",
}
NETWORK_RESOURCES = {
    "dhcpoptions",
    "internetgateway",
    "networkacl",
    "networkinterface",
    "routetable",
    "securitygroup",
    "subnet",
    "transitgateway",
    "vpc",
    "vpn",
}


def is_ec2_admin_policy(iam_policy):
    #
    # These first two checks can technically be skipped and this policy will still return correct
    # results, but they prevent the more computationally expensive check the majority of the time.
    #
    action_summary = iam_policy.action_summary()

    # Check if the policy applies to EC2 resources
    if "ec2" not in action_summary:
        return False

    # Check if the policy grants administrative privileges
    if not ADMIN_ACTIONS.intersection(action_summary["ec2"]):
        return False

    # Get the EC2 actions pertaining specifically to network resources
    network_actions = set()
    for statement in iam_policy.statements:
        # Only check statements granting access
        if statement.effect != "Allow":
            continue
        # Only check actions that are granted on network resources
        for action in statement.actions:
            if any(resource in action for resource in NETWORK_RESOURCES):
                network_actions.add(action)

    # For all actions that have been granted on network resources, ensure none grant admin access
    network_actions_summary = categories_for_actions(network_actions)
    return any(action in ADMIN_ACTIONS for action in network_actions_summary["ec2"])


def policy(resource):
    # This policy only applies to resources with an inline policy document
    if resource["InlinePolicies"] is None:
        return True

    for inline_policy in resource["InlinePolicies"].values():
        iam_policy = Policy(expand_policy(json.loads(inline_policy)))
        if is_ec2_admin_policy(iam_policy):
            return False

    return True

Analyst notes

Remove the inline policy, and if the IAM entity needs the provided permissions create an IAM managed policy with those permissions and apply it to the entity.

Raw source IAM Inline Policy Network Admin · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: policy
Filename: aws_iam_inline_policy_does_not_grant_network_admin_access.py
PolicyID: "AWS.IAM.Entity.InlinePolicyDoesNotGrantNetworkAdminAccess"
DisplayName: "IAM Inline Policy Network Admin"
Enabled: true
ResourceTypes:
  - AWS.IAM.User
  - AWS.IAM.Role
  - AWS.IAM.Group
Tags:
  - AWS
  - PCI
  - Persistence:Valid Accounts
Reports:
  PCI:
    - 1.1.5
    - 2.2.4
    - 7.1.2
  MITRE ATT&CK:
    - TA0003:T1078
Severity: Medium
Description: >
  This policy validates that IAM entities (Groups, Roles, and Users) do not have inline policies attached that grant network admin privileges. Inline policies are more difficult to track and audit than managed policies, and can lead to persistent unexpected access.
Runbook: >
  Remove the inline policy, and if the IAM entity needs the provided permissions create an IAM managed policy with those permissions and apply it to the entity.
Reference: https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html
Tests:
  - Name: IAM Entity Has No Inline Policy
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:user/example-user",
        "CredentialReport":
          {
            "ARN": "arn:aws:iam::123456789012:user/example-user",
            "AccessKey1Active": true,
            "AccessKey1LastRotated": "2019-01-01T00:00:00Z",
            "AccessKey1LastUsedDate": "0001-01-01T00:00:00Z",
            "AccessKey1LastUsedRegion": "N/A",
            "AccessKey1LastUsedService": "N/A",
            "AccessKey2Active": false,
            "AccessKey2LastRotated": "0001-01-01T00:00:00Z",
            "AccessKey2LastUsedDate": "0001-01-01T00:00:00Z",
            "AccessKey2LastUsedRegion": "N/A",
            "AccessKey2LastUsedService": "N/A",
            "Cert1Active": false,
            "Cert1LastRotated": "0001-01-01T00:00:00Z",
            "Cert2Active": false,
            "Cert2LastRotated": "0001-01-01T00:00:00Z",
            "MfaActive": false,
            "PasswordEnabled": true,
            "PasswordLastChanged": "2019-01-01T00:00:00Z",
            "PasswordLastUsed": "2019-01-01T00:00:00Z",
            "PasswordNextRotation": "2019-12-01T00:00:00Z",
            "UserCreationTime": "2019-01-01T00:00:00Z",
            "UserName": "example-user",
          },
        "Groups":
          [
            {
              "Arn": "arn:aws:iam::123456789012:group/example-group",
              "CreateDate": "2019-01-01T00:00:00Z",
              "GroupId": "1111",
              "GroupName": "example-group",
              "Path": "/",
            },
          ],
        "Id": "1111",
        "InlinePolicies": null,
        "ManagedPolicyNames": ["example-policy"],
        "Name": "example-user",
        "PasswordLastUsed": "2019-01-01T00:00:00Z",
        "Path": "/",
        "PermissionsBoundary": null,
        "Region": "global",
        "ResourceId": "arn:aws:iam::123456789012:user/example-user",
        "ResourceType": "AWS.IAM.User",
        "Tags": null,
        "TimeCreated": "2019-01-01T00:00:00.000Z",
        "VirtualMFA":
          {
            "EnableDate": "2019-01-01T00:00:00Z",
            "SerialNumber": "arn:aws:iam::123456789012:mfa/example-mfa",
          },
      }


# ------ paired body: aws_iam_inline_policy_does_not_grant_network_admin_access.py ------

import json

from policyuniverse.action_categories import categories_for_actions
from policyuniverse.expander_minimizer import expand_policy
from policyuniverse.policy import Policy

# White listed policies (e.g. the approved network admin policy) can be specified here, or as an
# exception to this policy.

ADMIN_ACTIONS = {
    "Tagging",
    "Write",
}
NETWORK_RESOURCES = {
    "dhcpoptions",
    "internetgateway",
    "networkacl",
    "networkinterface",
    "routetable",
    "securitygroup",
    "subnet",
    "transitgateway",
    "vpc",
    "vpn",
}


def is_ec2_admin_policy(iam_policy):
    #
    # These first two checks can technically be skipped and this policy will still return correct
    # results, but they prevent the more computationally expensive check the majority of the time.
    #
    action_summary = iam_policy.action_summary()

    # Check if the policy applies to EC2 resources
    if "ec2" not in action_summary:
        return False

    # Check if the policy grants administrative privileges
    if not ADMIN_ACTIONS.intersection(action_summary["ec2"]):
        return False

    # Get the EC2 actions pertaining specifically to network resources
    network_actions = set()
    for statement in iam_policy.statements:
        # Only check statements granting access
        if statement.effect != "Allow":
            continue
        # Only check actions that are granted on network resources
        for action in statement.actions:
            if any(resource in action for resource in NETWORK_RESOURCES):
                network_actions.add(action)

    # For all actions that have been granted on network resources, ensure none grant admin access
    network_actions_summary = categories_for_actions(network_actions)
    return any(action in ADMIN_ACTIONS for action in network_actions_summary["ec2"])


def policy(resource):
    # This policy only applies to resources with an inline policy document
    if resource["InlinePolicies"] is None:
        return True

    for inline_policy in resource["InlinePolicies"].values():
        iam_policy = Policy(expand_policy(json.loads(inline_policy)))
        if is_ec2_admin_policy(iam_policy):
            return False

    return True

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.