Detection rules › Panther

AWS IAM Role Grants (permission) to Non-organizational Account

Severity
critical
Tags
AWS, Identity & Access Management, Configuration Required
Source
github.com/panther-labs/panther-analysis

This policy validates that IAM roles that grant the (specified) permission do not allow accounts outside the organization to assume them.

Rule body yaml

AnalysisType: policy
Filename: aws_iam_role_external_permission.py
PolicyID: "AWS.IAM.Role.ExternalPermission"
DisplayName: "AWS IAM Role Grants (permission) to Non-organizational Account"
Enabled: false
ResourceTypes:
  - AWS.IAM.Role
Tags:
  - AWS
  - Identity & Access Management
  - Configuration Required
Severity: Critical
Description: >
  This policy validates that IAM roles that grant the (specified) permission do not allow accounts outside the organization to assume them.
Tests:
  - Name: Permission Present Inline Internal Account
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS": "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "CONFIGURATION_REQUIRED": "set Action: <specified permission> in TestPermPolicy",
        "InlinePolicies":
          {
            "TestPermPolicy": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["lambda:AddPermission", "test:Permission"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission", "test2:Permission"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames": [],
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Permission Not Present Inline
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "CONFIGURATION_REQUIRED": "set_AccountId_to_any_org_acct_see_accounts_variable",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "InlinePolicies":
          {
            "TestPolicy1": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames": [],
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Permission Present Inline External Account Multi-Principal
    ExpectedResult: false
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS":
                        [
                          "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                          "arn:aws:iam::123456789013:root",
                        ],
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "CONFIGURATION_REQUIRED": "set Action: <specified permission> in TestPermPolicy",
        "InlinePolicies":
          {
            "TestPermPolicy": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["lambda:AddPermission", "test:Permission"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission", "test2:Permission"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames": [],
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Null inline policy
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS": "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "InlinePolicies": null,
        "ManagedPolicyNames": [],
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Permission Present Managed Internal Account
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "CONFIGURATION_REQUIRED": "set_AccountId_to_any_org_acct",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS": "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "InlinePolicies":
          {
            "TestPolicy1": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames":
          [
            "CONFIGURATION_REQUIRED_Policy_must_actually_exist_in_Principal_acct_and_have_specified_permission",
            "Alternate_method_is_create_mock_see_instructions_in_policy",
          ],
        "IsUnitTest": true,
        "HasPermission": true,
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Permission Present Managed External Account
    ExpectedResult: false
    Resource:
      {
        "AccountId": "123456789012",
        "CONFIGURATION_REQUIRED": "set_AccountId_to_any_org_acct_see_accounts_variable",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal": { "AWS": "arn:aws:iam::123456789013:root" },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "InlinePolicies":
          {
            "TestPolicy1": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission1"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames":
          [
            "CONFIGURATION_REQUIRED_Policy_must_actually_exist_in_Principal_acct_and_have_specified_permission",
            "Alternate_method_is_create_mock_see_instructions_in_policy",
          ],
        "IsUnitTest": true,
        "HasPermission": true,
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Permission Not Present Managed Internal Account
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "CONFIGURATION_REQUIRED": "set_AccountId_to_any_org_acct_see_accounts_variable",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "InlinePolicies":
          {
            "TestPolicy1": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission1"],"Resource": "*"}]}',
            "TestPolicy2": '{"Version": "2012-10-17","Statement": [{ "Sid": "TestSid","Effect": "Allow","Action": ["test:Permission1", "test:Permission2"],"Resource": "*"}]}',
          },
        "ManagedPolicyNames":
          [
            "CONFIGURATION_REQUIRED_Policy_must_actually_exist_in_Principal_acct_and_NOT_have_specified_permission",
            "Alternate_method_is_create_mock_see_instructions_in_policy",
          ],
        "IsUnitTest": true,
        "DoesNotHavePermission": true,
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Managed Policy Not Found Internal Account
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS": "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "CONFIGURATION_REQUIRED": "set Action: <specified permission> in TestPermPolicy",
        "InlinePolicies": null,
        "ManagedPolicyNames": ["PolicyDoesNotExist"],
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Null Managed Policy
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/SampleRole",
        "AssumeRolePolicyDocument":
          {
            "Statement":
              [
                {
                  "Action": "sts:AssumeRole",
                  "Condition": {},
                  "Effect": "Allow",
                  "Principal":
                    {
                      "AWS": "arn:aws:iam::123456789012:role/CONFIGURATION_REQUIRED_set_AccountId_to_any_org_acct",
                    },
                },
              ],
            "Version": "2012-10-17",
          },
        "CreateDate": "2018-01-01T19:00:00+00:00",
        "CONFIGURATION_REQUIRED": "set Action: <specified permission> in TestPermPolicy",
        "InlinePolicies": null,
        "ManagedPolicyNames": null,
        "Path": "/",
        "RoleId": "ROLEID",
        "RoleName": "SampleRole",
      }
  - Name: Non-AWS Trust Principal
    ExpectedResult: true
    Resource:
      {
        "AccountId": "123456789012",
        "Arn": "arn:aws:iam::123456789012:role/somerole",
        "AssumeRolePolicyDocument": '{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}',
        "Description": "A role",
        "Id": "AROOOOOOOOOOOOOOOOOOO",
        "InlinePolicies": null,
        "ManagedPolicyNames": ["somepolicy"],
        "MaxSessionDuration": 3600,
        "Name": "somename",
        "Path": "/",
        "PermissionsBoundary": null,
        "Region": "global",
        "ResourceId": "arn:aws:iam::123456789012:role/somerole",
        "ResourceType": "AWS.IAM.Role",
        "TimeCreated": "2020-01-01T00:00:00.000Z",
      }

Detection logic

Rule logic imperative Python

import json
from botocore.exceptions import NoCredentialsError
from panther_aws_helpers import BadLookup, resource_lookup
accounts = [
    "123456789012",
]
PERMISSION = "lambda:AddPermission"
mock_policy_has_permission = json.loads("""
{
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "lambda:AddPermission",
                    "lambda:GetPolicy"
                ],
                "Resource": "*"
            }
        ]
    }
}
""")
mock_policy_no_permission = json.loads("""
{
    "PolicyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": "lambda:ListAliases",
                "Resource": "*"
            }
        ]
    }
}
""")
def check_account(resource):
    content_assumerole = resource.get("AssumeRolePolicyDocument")
    if isinstance(content_assumerole, str):
        content_assumerole = json.loads(content_assumerole)
    principal = content_assumerole["Statement"][0]["Principal"]
    if "AWS" in principal.keys():
        if isinstance(principal["AWS"], list):
            for principal_aws in principal["AWS"]:
                if not check_account_number(principal_aws):
                    return False
        else:
            return check_account_number(principal["AWS"])
    return True
def check_account_number(principal_aws):
    if principal_aws.split(":")[4] not in accounts:
        return False
    return True
def check_policy(policy_text):
    if isinstance(policy_text, str):
        policy_text = json.loads(policy_text)
    for statement in policy_text.get("Statement", []):
        if PERMISSION in statement.get("Action", []):
            return False
    return True
def policy(resource):
    if not check_account(resource):
        for policy_text in (resource.get("InlinePolicies") or {}).values():
            if not check_policy(policy_text):
                return False
        for managed_policy_name in resource.get("ManagedPolicyNames") or []:
            managed_policy_id = f"arn:aws:iam::{resource['AccountId']}:policy/{managed_policy_name}"
            try:
                if not resource.get("IsUnitTest"):
                    managed_policy = resource_lookup(managed_policy_id)
                else:
                    if resource.get("HasPermission"):
                        managed_policy = mock_policy_has_permission
                    elif resource.get("DoesNotHavePermission"):
                        managed_policy = mock_policy_no_permission
                    else:
                        return True
            except BadLookup:
                return True
            except NoCredentialsError:
                return True
            policy_text = managed_policy.get("PolicyDocument")
            if not check_policy(policy_text):
                return False
    return True

The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.