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


Description

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

Query · python

import json

from botocore.exceptions import NoCredentialsError
from panther_aws_helpers import BadLookup, resource_lookup

# This is a list of the account numbers included in the organization
# Example:
# accounts = [
#   '123456789012',
#   '123456789013'
# ]

# account *12 is used as an organizational account in built-in unit tests
# account *13 is used as a non-organizational account
accounts = [
    "123456789012",
]

# The specific permission that the policy checks
# CONFIGURATION_REQUIRED: replace default "lambda:AddPermission" in unit tests
#  with specified permission
PERMISSION = "lambda:AddPermission"

# CONFIGURATION_REQUIRED: modify policy to contain specified permission, above
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": "*"
            }
        ]
    }
}
""")


# check to see if the account that is granted permission is third party
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):
    # pylint: disable=too-complex
    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:
                # CONFIGURATION REQUIRED
                # to mock a Managed Policy
                #   - create mock policy, above
                #   - add '“IsUnitTest": true,' to test resource in .yml
                #   - add an additional 'key: value' pair and 'if/elif' block for each
                #     mocked case to .yml
                # Note: all Managed Policies in the test resource will be mocked
                # comment out 'if, else' block to optimize for production use
                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
                # uncomment next line to optimize for production use
                # managed_policy = resource_lookup(managed_policy_id)
            except BadLookup:
                return True
            except NoCredentialsError:
                return True

            policy_text = managed_policy.get("PolicyDocument")
            if not check_policy(policy_text):
                return False

    return True
Raw source AWS IAM Role Grants (permission) to Non-organizational Account · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
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",
      }


# ------ paired body: aws_iam_role_external_permission.py ------

import json

from botocore.exceptions import NoCredentialsError
from panther_aws_helpers import BadLookup, resource_lookup

# This is a list of the account numbers included in the organization
# Example:
# accounts = [
#   '123456789012',
#   '123456789013'
# ]

# account *12 is used as an organizational account in built-in unit tests
# account *13 is used as a non-organizational account
accounts = [
    "123456789012",
]

# The specific permission that the policy checks
# CONFIGURATION_REQUIRED: replace default "lambda:AddPermission" in unit tests
#  with specified permission
PERMISSION = "lambda:AddPermission"

# CONFIGURATION_REQUIRED: modify policy to contain specified permission, above
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": "*"
            }
        ]
    }
}
""")


# check to see if the account that is granted permission is third party
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):
    # pylint: disable=too-complex
    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:
                # CONFIGURATION REQUIRED
                # to mock a Managed Policy
                #   - create mock policy, above
                #   - add '“IsUnitTest": true,' to test resource in .yml
                #   - add an additional 'key: value' pair and 'if/elif' block for each
                #     mocked case to .yml
                # Note: all Managed Policies in the test resource will be mocked
                # comment out 'if, else' block to optimize for production use
                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
                # uncomment next line to optimize for production use
                # managed_policy = resource_lookup(managed_policy_id)
            except BadLookup:
                return True
            except NoCredentialsError:
                return True

            policy_text = managed_policy.get("PolicyDocument")
            if not check_policy(policy_text):
                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.