Repository: cloud-custodian/cloud-custodian

Issue:
aws - check-permissions - support permission boundaries
aws iam policy simulator supports permission boundaries as of january, 2020
https://awsapichanges.info/archive/changes/98b098-iam.html

check-permissions filter should utilize that on applicable iam resources (user, role).



Discussion:

Resolving patch:
diff --git a/c7n/resources/iam.py b/c7n/resources/iam.py
--- a/c7n/resources/iam.py
+++ b/c7n/resources/iam.py
@@ -110,6 +110,8 @@ def get_resources(self, resource_ids, cache=True):
         client = local_session(self.manager.session_factory).client('iam')
         resources = []
         for rid in resource_ids:
+            if rid.startswith('arn'):
+                rid = Arn.parse(rid).resource
             try:
                 result = self.manager.retry(client.get_role, RoleName=rid)
             except client.exceptions.NoSuchEntityException:
@@ -495,6 +497,8 @@ class CheckPermissions(Filter):
                 match: allowed
                 actions:
                  - iam:CreateUser
+
+    By default permission boundaries are checked.
     """
 
     schema = type_schema(
@@ -503,6 +507,7 @@ class CheckPermissions(Filter):
                 {'enum': ['allowed', 'denied']},
                 {'$ref': '#/definitions/filters/valuekv'},
                 {'$ref': '#/definitions/filters/value'}]},
+            'boundaries': {'type': 'boolean'},
             'match-operator': {'enum': ['and', 'or']},
             'actions': {'type': 'array', 'items': {'type': 'string'}},
             'required': ('actions', 'match')})
@@ -512,8 +517,12 @@ class CheckPermissions(Filter):
 
     def get_permissions(self):
         if self.manager.type == 'iam-policy':
-            return ('iam:SimulateCustomPolicy',)
-        return ('iam:SimulatePrincipalPolicy',)
+            return ('iam:SimulateCustomPolicy', 'iam:GetPolicyVersion')
+        perms = ('iam:SimulatePrincipalPolicy', 'iam:GetPolicy', 'iam:GetPolicyVersion')
+        if self.manager.type not in ('iam-user', 'iam-role',):
+            # for simulating w/ permission boundaries
+            perms += ('iam:GetRole',)
+        return perms
 
     def process(self, resources, event=None):
         client = local_session(self.manager.session_factory).client('iam')
@@ -521,9 +530,11 @@ def process(self, resources, event=None):
         matcher = self.get_eval_matcher()
         operator = self.data.get('match-operator', 'and') == 'and' and all or any
 
+        arn_resources = list(zip(self.get_iam_arns(resources), resources))
+        self.initialize_boundaries(client, arn_resources)
         results = []
         eval_cache = {}
-        for arn, r in zip(self.get_iam_arns(resources), resources):
+        for arn, r in arn_resources:
             if arn is None:
                 continue
             if arn in eval_cache:
@@ -531,7 +542,6 @@ def process(self, resources, event=None):
             else:
                 evaluations = self.get_evaluations(client, arn, r, actions)
                 eval_cache[arn] = evaluations
-
             matches = []
             matched = []
             for e in evaluations:
@@ -544,6 +554,49 @@ def process(self, resources, event=None):
                 results.append(r)
         return results
 
+    def initialize_boundaries(self, client, iam_resources):
+        """For IAM boundaries we need to retrieve boundary policy content.
+        """
+        # boundaries aren't attached to these.
+        if (self.manager.type in ('iam-policy', 'iam-group') or
+                self.data.get('boundary', True) is False):
+            return
+
+        # if boundary attributes aren't directly on the resource, fetch
+        # the iam role for the resource to get the boundary.
+        if self.manager.type not in ('iam-role', 'iam-user'):
+            iam_arns = {iam_arn for iam_arn, r in iam_resources if iam_arn is not None}
+            roles = self.manager.get_resource_manager(
+                'iam-role').get_resources(list(iam_arns), augment=False)
+            boundary_iam_map = {
+                r['Arn']: r.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn')
+                for r in roles}
+            boundary_map = {}
+            for iam_arn, r in iam_resources:
+                boundary_map[self.manager.get_arns((r,))[0]] = boundary_iam_map.get(iam_arn)
+        else:
+            boundary_map = {
+                resource_arn: r.get('PermissionsBoundary', {}).get('PermissionsBoundaryArn')
+                for resource_arn, r in iam_resources}
+
+        # fetch boundary policies text
+        boundary_arns = set(boundary_map.values())
+        if None in boundary_arns:
+            boundary_arns.remove(None)
+        policies = self.manager.get_resource_manager(
+            'iam-policy').get_resources(list(boundary_arns))
+        boundaries = {}
+        for p in policies:
+            boundaries[p['Arn']] = json.dumps(client.get_policy_version(
+                PolicyArn=p['Arn'],
+                VersionId=p['DefaultVersionId'])['PolicyVersion']['Document'])
+
+        for resource_arn, boundary_arn in list(boundary_map.items()):
+            if boundary_arn is None:
+                continue
+            boundary_map[resource_arn] = boundaries[boundary_arn]
+        self.boundaries = boundary_map
+
     def get_iam_arns(self, resources):
         return self.manager.get_arns(resources)
 
@@ -558,11 +611,17 @@ def get_evaluations(self, client, arn, r, actions):
                 client.simulate_custom_policy,
                 PolicyInputList=[json.dumps(policy['Document'])],
                 ActionNames=actions).get('EvaluationResults', ())
-        else:
-            evaluations = self.manager.retry(
-                client.simulate_principal_policy,
-                PolicySourceArn=arn,
-                ActionNames=actions).get('EvaluationResults', ())
+            return evaluations
+
+        params = dict(PolicySourceArn=arn, ActionNames=actions)
+        if self.boundaries:
+            boundary_policy = self.boundaries.get(
+                self.manager.get_arns([r])[0])
+            if boundary_policy:
+                params['PermissionsBoundaryPolicyInputList'] = [boundary_policy]
+
+        evaluations = self.manager.retry(
+            client.simulate_principal_policy, **params).get('EvaluationResults', ())
         return evaluations
 
     def get_eval_matcher(self):


Test patch:
diff --git a/tests/data/placebo/test_ec2_permissions/ec2.DescribeInstances_1.json b/tests/data/placebo/test_ec2_permissions/ec2.DescribeInstances_1.json
--- a/tests/data/placebo/test_ec2_permissions/ec2.DescribeInstances_1.json
+++ b/tests/data/placebo/test_ec2_permissions/ec2.DescribeInstances_1.json
@@ -1,165 +1,154 @@
 {
-    "status_code": 200,
-    "data": {
-        "Reservations": [
-            {
-                "Groups": [],
-                "Instances": [
-                    {
-                        "AmiLaunchIndex": 0,
-                        "ImageId": "ami-0bbe6b35405ecebdb",
-                        "InstanceId": "i-0cdbc9746913097d5",
-                        "InstanceType": "t2.micro",
-                        "LaunchTime": {
-                            "__class__": "datetime",
-                            "year": 2019,
-                            "month": 2,
-                            "day": 28,
-                            "hour": 14,
-                            "minute": 7,
-                            "second": 46,
-                            "microsecond": 0
-                        },
-                        "Monitoring": {
-                            "State": "disabled"
-                        },
-                        "Placement": {
-                            "AvailabilityZone": "us-west-2a",
-                            "GroupName": "",
-                            "Tenancy": "default"
-                        },
-                        "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
-                        "PrivateIpAddress": "172.31.25.97",
-                        "ProductCodes": [],
-                        "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
-                        "PublicIpAddress": "18.237.70.187",
-                        "State": {
-                            "Code": 16,
-                            "Name": "running"
-                        },
-                        "StateTransitionReason": "",
-                        "SubnetId": "subnet-15452171",
-                        "VpcId": "vpc-4a9ff72e",
-                        "Architecture": "x86_64",
-                        "BlockDeviceMappings": [
-                            {
-                                "DeviceName": "/dev/sda1",
-                                "Ebs": {
-                                    "AttachTime": {
-                                        "__class__": "datetime",
-                                        "year": 2019,
-                                        "month": 2,
-                                        "day": 28,
-                                        "hour": 14,
-                                        "minute": 7,
-                                        "second": 47,
-                                        "microsecond": 0
-                                    },
-                                    "DeleteOnTermination": true,
-                                    "Status": "attached",
-                                    "VolumeId": "vol-09e24ed598d8ad490"
-                                }
-                            }
-                        ],
-                        "ClientToken": "",
-                        "EbsOptimized": false,
-                        "EnaSupport": true,
-                        "Hypervisor": "xen",
-                        "IamInstanceProfile": {
-                            "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
-                            "Id": "AIPAJO5EMJ3YEMNUF5ZWM"
-                        },
-                        "NetworkInterfaces": [
-                            {
-                                "Association": {
-                                    "IpOwnerId": "amazon",
-                                    "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
-                                    "PublicIp": "18.237.70.187"
-                                },
-                                "Attachment": {
-                                    "AttachTime": {
-                                        "__class__": "datetime",
-                                        "year": 2019,
-                                        "month": 2,
-                                        "day": 28,
-                                        "hour": 14,
-                                        "minute": 7,
-                                        "second": 46,
-                                        "microsecond": 0
-                                    },
-                                    "AttachmentId": "eni-attach-028f9f4eab73b33e3",
-                                    "DeleteOnTermination": true,
-                                    "DeviceIndex": 0,
-                                    "Status": "attached"
-                                },
-                                "Description": "",
-                                "Groups": [
-                                    {
-                                        "GroupName": "default",
-                                        "GroupId": "sg-f9cc4d9f"
-                                    }
-                                ],
-                                "Ipv6Addresses": [],
-                                "MacAddress": "02:1f:11:0f:a1:06",
-                                "NetworkInterfaceId": "eni-029655e1e425bc4a1",
-                                "OwnerId": "644160558196",
-                                "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
-                                "PrivateIpAddress": "172.31.25.97",
-                                "PrivateIpAddresses": [
-                                    {
-                                        "Association": {
-                                            "IpOwnerId": "amazon",
-                                            "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
-                                            "PublicIp": "18.237.70.187"
-                                        },
-                                        "Primary": true,
-                                        "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
-                                        "PrivateIpAddress": "172.31.25.97"
-                                    }
-                                ],
-                                "SourceDestCheck": true,
-                                "Status": "in-use",
-                                "SubnetId": "subnet-15452171",
-                                "VpcId": "vpc-4a9ff72e"
-                            }
-                        ],
-                        "RootDeviceName": "/dev/sda1",
-                        "RootDeviceType": "ebs",
-                        "SecurityGroups": [
-                            {
-                                "GroupName": "default",
-                                "GroupId": "sg-f9cc4d9f"
-                            }
-                        ],
-                        "SourceDestCheck": true,
-                        "VirtualizationType": "hvm",
-                        "CpuOptions": {
-                            "CoreCount": 1,
-                            "ThreadsPerCore": 1
-                        },
-                        "CapacityReservationSpecification": {
-                            "CapacityReservationPreference": "open"
-                        },
-                        "HibernationOptions": {
-                            "Configured": false
-                        }
-                    }
+  "status_code": 200,
+  "data": {
+    "Reservations": [
+      {
+        "Groups": [],
+        "Instances": [
+          {
+            "AmiLaunchIndex": 0,
+            "ImageId": "ami-0bbe6b35405ecebdb",
+            "InstanceId": "i-0cdbc9746913097d5",
+            "InstanceType": "t2.micro",
+            "LaunchTime": {
+              "__class__": "datetime",
+              "year": 2019,
+              "month": 2,
+              "day": 28,
+              "hour": 14,
+              "minute": 7,
+              "second": 46,
+              "microsecond": 0
+            },
+            "Monitoring": {
+              "State": "disabled"
+            },
+            "Placement": {
+              "AvailabilityZone": "us-west-2a",
+              "GroupName": "",
+              "Tenancy": "default"
+            },
+            "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
+            "PrivateIpAddress": "172.31.25.97",
+            "ProductCodes": [],
+            "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
+            "PublicIpAddress": "18.237.70.187",
+            "State": {
+              "Code": 16,
+              "Name": "running"
+            },
+            "StateTransitionReason": "",
+            "SubnetId": "subnet-15452171",
+            "VpcId": "vpc-4a9ff72e",
+            "Architecture": "x86_64",
+            "BlockDeviceMappings": [
+              {
+                "DeviceName": "/dev/sda1",
+                "Ebs": {
+                  "AttachTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 2,
+                    "day": 28,
+                    "hour": 14,
+                    "minute": 7,
+                    "second": 47,
+                    "microsecond": 0
+                  },
+                  "DeleteOnTermination": true,
+                  "Status": "attached",
+                  "VolumeId": "vol-09e24ed598d8ad490"
+                }
+              }
+            ],
+            "ClientToken": "",
+            "EbsOptimized": false,
+            "EnaSupport": true,
+            "Hypervisor": "xen",
+            "IamInstanceProfile": {
+              "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
+              "Id": "AIPAJO5EMJ3YEMNUF5ZWM"
+            },
+            "NetworkInterfaces": [
+              {
+                "Association": {
+                  "IpOwnerId": "amazon",
+                  "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
+                  "PublicIp": "18.237.70.187"
+                },
+                "Attachment": {
+                  "AttachTime": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 2,
+                    "day": 28,
+                    "hour": 14,
+                    "minute": 7,
+                    "second": 46,
+                    "microsecond": 0
+                  },
+                  "AttachmentId": "eni-attach-028f9f4eab73b33e3",
+                  "DeleteOnTermination": true,
+                  "DeviceIndex": 0,
+                  "Status": "attached"
+                },
+                "Description": "",
+                "Groups": [
+                  {
+                    "GroupName": "default",
+                    "GroupId": "sg-f9cc4d9f"
+                  }
                 ],
+                "Ipv6Addresses": [],
+                "MacAddress": "02:1f:11:0f:a1:06",
+                "NetworkInterfaceId": "eni-029655e1e425bc4a1",
                 "OwnerId": "644160558196",
-                "ReservationId": "r-0de107f29ed16d364"
+                "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
+                "PrivateIpAddress": "172.31.25.97",
+                "PrivateIpAddresses": [
+                  {
+                    "Association": {
+                      "IpOwnerId": "amazon",
+                      "PublicDnsName": "ec2-18-237-70-187.us-west-2.compute.amazonaws.com",
+                      "PublicIp": "18.237.70.187"
+                    },
+                    "Primary": true,
+                    "PrivateDnsName": "ip-172-31-25-97.us-west-2.compute.internal",
+                    "PrivateIpAddress": "172.31.25.97"
+                  }
+                ],
+                "SourceDestCheck": true,
+                "Status": "in-use",
+                "SubnetId": "subnet-15452171",
+                "VpcId": "vpc-4a9ff72e"
+              }
+            ],
+            "RootDeviceName": "/dev/sda1",
+            "RootDeviceType": "ebs",
+            "SecurityGroups": [
+              {
+                "GroupName": "default",
+                "GroupId": "sg-f9cc4d9f"
+              }
+            ],
+            "SourceDestCheck": true,
+            "VirtualizationType": "hvm",
+            "CpuOptions": {
+              "CoreCount": 1,
+              "ThreadsPerCore": 1
+            },
+            "CapacityReservationSpecification": {
+              "CapacityReservationPreference": "open"
+            },
+            "HibernationOptions": {
+              "Configured": false
             }
+          }
         ],
-        "ResponseMetadata": {
-            "RequestId": "a33aeeb3-fe37-48ca-9a13-906771862572",
-            "HTTPStatusCode": 200,
-            "HTTPHeaders": {
-                "content-type": "text/xml;charset=UTF-8",
-                "transfer-encoding": "chunked",
-                "vary": "Accept-Encoding",
-                "date": "Thu, 28 Feb 2019 14:28:00 GMT",
-                "server": "AmazonEC2"
-            },
-            "RetryAttempts": 0
-        }
-    }
+        "OwnerId": "644160558196",
+        "ReservationId": "r-0de107f29ed16d364"
+      }
+    ],
+    "ResponseMetadata": {}
+  }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_ec2_permissions/ec2.DescribeTags_1.json b/tests/data/placebo/test_ec2_permissions/ec2.DescribeTags_1.json
--- a/tests/data/placebo/test_ec2_permissions/ec2.DescribeTags_1.json
+++ b/tests/data/placebo/test_ec2_permissions/ec2.DescribeTags_1.json
@@ -1,54 +1,44 @@
 {
-    "status_code": 200,
-    "data": {
-        "Tags": [
-            {
-                "Key": "aws:autoscaling:groupName",
-                "ResourceId": "i-0aca1d4f8f943d73e",
-                "ResourceType": "instance",
-                "Value": "test2"
-            },
-            {
-                "Key": "aws:ec2launchtemplate:id",
-                "ResourceId": "i-0aca1d4f8f943d73e",
-                "ResourceType": "instance",
-                "Value": "lt-0877401c93c294001"
-            },
-            {
-                "Key": "aws:ec2launchtemplate:version",
-                "ResourceId": "i-0aca1d4f8f943d73e",
-                "ResourceType": "instance",
-                "Value": "4"
-            },
-            {
-                "Key": "aws:autoscaling:groupName",
-                "ResourceId": "i-0db184a69847143fb",
-                "ResourceType": "instance",
-                "Value": "test2"
-            },
-            {
-                "Key": "aws:ec2launchtemplate:id",
-                "ResourceId": "i-0db184a69847143fb",
-                "ResourceType": "instance",
-                "Value": "lt-0877401c93c294001"
-            },
-            {
-                "Key": "aws:ec2launchtemplate:version",
-                "ResourceId": "i-0db184a69847143fb",
-                "ResourceType": "instance",
-                "Value": "4"
-            }
-        ],
-        "ResponseMetadata": {
-            "RequestId": "9ba8de1b-bd9d-43f1-a69f-e94707e4c481",
-            "HTTPStatusCode": 200,
-            "HTTPHeaders": {
-                "content-type": "text/xml;charset=UTF-8",
-                "content-length": "1573",
-                "date": "Thu, 28 Feb 2019 14:28:01 GMT",
-                "server": "AmazonEC2"
-            },
-            "RetryAttempts": 0
-        }
-    }
+  "status_code": 200,
+  "data": {
+    "Tags": [
+      {
+        "Key": "aws:autoscaling:groupName",
+        "ResourceId": "i-0aca1d4f8f943d73e",
+        "ResourceType": "instance",
+        "Value": "test2"
+      },
+      {
+        "Key": "aws:ec2launchtemplate:id",
+        "ResourceId": "i-0aca1d4f8f943d73e",
+        "ResourceType": "instance",
+        "Value": "lt-0877401c93c294001"
+      },
+      {
+        "Key": "aws:ec2launchtemplate:version",
+        "ResourceId": "i-0aca1d4f8f943d73e",
+        "ResourceType": "instance",
+        "Value": "4"
+      },
+      {
+        "Key": "aws:autoscaling:groupName",
+        "ResourceId": "i-0db184a69847143fb",
+        "ResourceType": "instance",
+        "Value": "test2"
+      },
+      {
+        "Key": "aws:ec2launchtemplate:id",
+        "ResourceId": "i-0db184a69847143fb",
+        "ResourceType": "instance",
+        "Value": "lt-0877401c93c294001"
+      },
+      {
+        "Key": "aws:ec2launchtemplate:version",
+        "ResourceId": "i-0db184a69847143fb",
+        "ResourceType": "instance",
+        "Value": "4"
+      }
+    ],
+    "ResponseMetadata": {}
+  }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_ec2_permissions/iam.GetRole_1.json b/tests/data/placebo/test_ec2_permissions/iam.GetRole_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_ec2_permissions/iam.GetRole_1.json
@@ -0,0 +1,19 @@
+{
+    "status_code": 200,
+    "data": {
+    "Role": {
+        "Path": "/",
+        "RoleName": "CloudCustodianRole",
+        "RoleId": "AROAI7ZS26HIW5UKQGHRM",
+        "Arn": "arn:aws:iam::644160558196:role/CloudCustodianRole",
+        "CreateDate": "2016-10-29T19:39:43Z",
+        "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22ec2.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+        "MaxSessionDuration": 3600,
+        "RoleLastUsed": {
+            "LastUsedDate": "2020-04-01T17:38:54Z",
+            "Region": "us-east-1"
+        }
+    },
+	"ResponseMetadata": {}
+    }
+}
diff --git a/tests/data/placebo/test_ec2_permissions/iam.ListInstanceProfiles_1.json b/tests/data/placebo/test_ec2_permissions/iam.ListInstanceProfiles_1.json
--- a/tests/data/placebo/test_ec2_permissions/iam.ListInstanceProfiles_1.json
+++ b/tests/data/placebo/test_ec2_permissions/iam.ListInstanceProfiles_1.json
@@ -1,55 +1,44 @@
 {
-    "status_code": 200,
-    "data": {
-        "InstanceProfiles": [
-            {
-                "Path": "/",
-                "InstanceProfileName": "CloudCustodianRole",
-                "InstanceProfileId": "AIPAJO5EMJ3YEMNUF5ZWM",
-                "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
-                "CreateDate": {
-                    "__class__": "datetime",
-                    "year": 2016,
-                    "month": 10,
-                    "day": 29,
-                    "hour": 19,
-                    "minute": 39,
-                    "second": 43,
-                    "microsecond": 0
-                },
-                "Roles": [
-                    {
-                        "Path": "/",
-                        "RoleName": "CloudCustodianRole",
-                        "RoleId": "AROAI7ZS26HIW5UKQGHRM",
-                        "Arn": "arn:aws:iam::644160558196:role/CloudCustodianRole",
-                        "CreateDate": {
-                            "__class__": "datetime",
-                            "year": 2016,
-                            "month": 10,
-                            "day": 29,
-                            "hour": 19,
-                            "minute": 39,
-                            "second": 43,
-                            "microsecond": 0
-                        },
-                        "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%5B%22arn%3Aaws%3Aiam%3A%3A644160558196%3Auser%2Fkapil%22%2C%22arn%3Aaws%3Aiam%3A%3A644160558196%3Aroot%22%5D%2C%22Service%22%3A%5B%22ec2.amazonaws.com%22%2C%22lambda.amazonaws.com%22%5D%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D"
-                    }
-                ]
-            }
-        ],
-        "IsTruncated": false,
-        "ResponseMetadata": {
-            "RequestId": "0ddc0758-3b65-11e9-a5d1-cd2bc2e0baac",
-            "HTTPStatusCode": 200,
-            "HTTPHeaders": {
-                "x-amzn-requestid": "0ddc0758-3b65-11e9-a5d1-cd2bc2e0baac",
-                "content-type": "text/xml",
-                "content-length": "9732",
-                "vary": "Accept-Encoding",
-                "date": "Thu, 28 Feb 2019 14:28:01 GMT"
+  "status_code": 200,
+  "data": {
+    "InstanceProfiles": [
+      {
+        "Path": "/",
+        "InstanceProfileName": "CloudCustodianRole",
+        "InstanceProfileId": "AIPAJO5EMJ3YEMNUF5ZWM",
+        "Arn": "arn:aws:iam::644160558196:instance-profile/CloudCustodianRole",
+        "CreateDate": {
+          "__class__": "datetime",
+          "year": 2016,
+          "month": 10,
+          "day": 29,
+          "hour": 19,
+          "minute": 39,
+          "second": 43,
+          "microsecond": 0
+        },
+        "Roles": [
+          {
+            "Path": "/",
+            "RoleName": "CloudCustodianRole",
+            "RoleId": "AROAI7ZS26HIW5UKQGHRM",
+            "Arn": "arn:aws:iam::644160558196:role/CloudCustodianRole",
+            "CreateDate": {
+              "__class__": "datetime",
+              "year": 2016,
+              "month": 10,
+              "day": 29,
+              "hour": 19,
+              "minute": 39,
+              "second": 43,
+              "microsecond": 0
             },
-            "RetryAttempts": 0
-        }
-    }
-}
+            "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%5B%22arn%3Aaws%3Aiam%3A%3A644160558196%3Auser%2Fkapil%22%2C%22arn%3Aaws%3Aiam%3A%3A644160558196%3Aroot%22%5D%2C%22Service%22%3A%5B%22ec2.amazonaws.com%22%2C%22lambda.amazonaws.com%22%5D%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D"
+          }
+        ]
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_ec2_permissions/iam.SimulatePrincipalPolicy_1.json b/tests/data/placebo/test_ec2_permissions/iam.SimulatePrincipalPolicy_1.json
--- a/tests/data/placebo/test_ec2_permissions/iam.SimulatePrincipalPolicy_1.json
+++ b/tests/data/placebo/test_ec2_permissions/iam.SimulatePrincipalPolicy_1.json
@@ -1,38 +1,28 @@
 {
-    "status_code": 200,
-    "data": {
-        "EvaluationResults": [
-            {
-                "EvalActionName": "lambda:CreateFunction",
-                "EvalResourceName": "*",
-                "EvalDecision": "allowed",
-                "MatchedStatements": [
-                    {
-                        "SourcePolicyId": "AdministratorAccess",
-                        "StartPosition": {
-                            "Line": 3,
-                            "Column": 17
-                        },
-                        "EndPosition": {
-                            "Line": 8,
-                            "Column": 6
-                        }
-                    }
-                ],
-                "MissingContextValues": []
+  "status_code": 200,
+  "data": {
+    "EvaluationResults": [
+      {
+        "EvalActionName": "lambda:CreateFunction",
+        "EvalResourceName": "*",
+        "EvalDecision": "allowed",
+        "MatchedStatements": [
+          {
+            "SourcePolicyId": "AdministratorAccess",
+            "StartPosition": {
+              "Line": 3,
+              "Column": 17
+            },
+            "EndPosition": {
+              "Line": 8,
+              "Column": 6
             }
+          }
         ],
-        "IsTruncated": false,
-        "ResponseMetadata": {
-            "RequestId": "0de7c754-3b65-11e9-8491-8fd0be1cf9c2",
-            "HTTPStatusCode": 200,
-            "HTTPHeaders": {
-                "x-amzn-requestid": "0de7c754-3b65-11e9-8491-8fd0be1cf9c2",
-                "content-type": "text/xml",
-                "content-length": "995",
-                "date": "Thu, 28 Feb 2019 14:28:01 GMT"
-            },
-            "RetryAttempts": 0
-        }
-    }
+        "MissingContextValues": []
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
 }
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_check_permission/iam.GetPolicyVersion_1.json b/tests/data/placebo/test_lambda_check_permission/iam.GetPolicyVersion_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/iam.GetPolicyVersion_1.json
@@ -0,0 +1,21 @@
+{
+  "status_code": 200,
+  "data": {
+    "PolicyVersion": {
+      "Document": "%7B%0A%20%20%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sid%22%3A%20%22VisualEditor0%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Deny%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListRoleTags%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListServerCertificates%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListPoliciesGrantingServiceAccess%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListServiceSpecificCredentials%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListMFADevices%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListSigningCertificates%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListVirtualMFADevices%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListInstanceProfilesForRole%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListSSHPublicKeys%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListAttachedRolePolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListAttachedUserPolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListAttachedGroupPolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListRolePolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListAccessKeys%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListPolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListSAMLProviders%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListGroupPolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListEntitiesForPolicy%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListRoles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListUserPolicies%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListInstanceProfiles%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListPolicyVersions%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListOpenIDConnectProviders%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListGroupsForUser%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListAccountAliases%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListUsers%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListGroups%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AGetLoginProfile%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AListUserTags%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22iam%3AGetAccountSummary%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%0A%7D",
+      "VersionId": "v2",
+      "IsDefaultVersion": true,
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 1,
+        "hour": 17,
+        "minute": 29,
+        "second": 45,
+        "microsecond": 0
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_check_permission/iam.GetPolicy_1.json b/tests/data/placebo/test_lambda_check_permission/iam.GetPolicy_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/iam.GetPolicy_1.json
@@ -0,0 +1,36 @@
+{
+  "status_code": 200,
+  "data": {
+    "Policy": {
+      "PolicyName": "BlackListIamList",
+      "PolicyId": "ANPAZL6XWCR2MBNLHREFH",
+      "Arn": "arn:aws:iam::644160558196:policy/BlackListIamList",
+      "Path": "/",
+      "DefaultVersionId": "v2",
+      "AttachmentCount": 0,
+      "PermissionsBoundaryUsageCount": 1,
+      "IsAttachable": true,
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 1,
+        "hour": 17,
+        "minute": 29,
+        "second": 23,
+        "microsecond": 0
+      },
+      "UpdateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 1,
+        "hour": 17,
+        "minute": 29,
+        "second": 45,
+        "microsecond": 0
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_check_permission/iam.GetRole_1.json b/tests/data/placebo/test_lambda_check_permission/iam.GetRole_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/iam.GetRole_1.json
@@ -0,0 +1,41 @@
+{
+  "status_code": 200,
+  "data": {
+    "Role": {
+      "Path": "/",
+      "RoleName": "custodian-mu",
+      "RoleId": "AROAJQ7B35GGHTQXLQCNY",
+      "Arn": "arn:aws:iam::644160558196:role/custodian-mu",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2016,
+        "month": 8,
+        "day": 27,
+        "hour": 12,
+        "minute": 2,
+        "second": 50,
+        "microsecond": 0
+      },
+      "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22lambda.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+      "MaxSessionDuration": 3600,
+      "PermissionsBoundary": {
+        "PermissionsBoundaryType": "Policy",
+        "PermissionsBoundaryArn": "arn:aws:iam::644160558196:policy/BlackListIamList"
+      },
+      "RoleLastUsed": {
+        "LastUsedDate": {
+          "__class__": "datetime",
+          "year": 2020,
+          "month": 4,
+          "day": 1,
+          "hour": 16,
+          "minute": 59,
+          "second": 24,
+          "microsecond": 0
+        },
+        "Region": "us-east-1"
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_check_permission/iam.SimulatePrincipalPolicy_1.json b/tests/data/placebo/test_lambda_check_permission/iam.SimulatePrincipalPolicy_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/iam.SimulatePrincipalPolicy_1.json
@@ -0,0 +1,44 @@
+{
+  "status_code": 200,
+  "data": {
+    "EvaluationResults": [
+      {
+        "EvalActionName": "iam:ListUsers",
+        "EvalResourceName": "arn:aws:iam::644160558196:user/",
+        "EvalDecision": "explicitDeny",
+        "MatchedStatements": [
+          {
+            "SourcePolicyId": "IAMReadOnlyAccess",
+            "SourcePolicyType": "IAM Policy",
+            "StartPosition": {
+              "Line": 3,
+              "Column": 17
+            },
+            "EndPosition": {
+              "Line": 15,
+              "Column": 6
+            }
+          },
+          {
+            "SourcePolicyId": "PermissionsBoundaryPolicyInputList.1",
+            "SourcePolicyType": "Permissions Boundary Policy",
+            "StartPosition": {
+              "Line": 1,
+              "Column": 41
+            },
+            "EndPosition": {
+              "Line": 1,
+              "Column": 908
+            }
+          }
+        ],
+        "MissingContextValues": [],
+        "PermissionsBoundaryDecisionDetail": {
+          "AllowedByPermissionsBoundary": false
+        }
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_lambda_check_permission/lambda.ListFunctions_1.json b/tests/data/placebo/test_lambda_check_permission/lambda.ListFunctions_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/lambda.ListFunctions_1.json
@@ -0,0 +1,31 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {},
+        "Functions": [
+            {
+                "FunctionName": "custodian-log-age",
+                "FunctionArn": "arn:aws:lambda:us-east-1:644160558196:function:custodian-log-age",
+                "Runtime": "python3.7",
+                "Role": "arn:aws:iam::644160558196:role/custodian-mu",
+                "Handler": "custodian_policy.run",
+                "CodeSize": 567503,
+                "Description": "cloud-custodian lambda policy",
+                "Timeout": 900,
+                "MemorySize": 512,
+                "LastModified": "2019-06-18T21:05:47.053+0000",
+                "CodeSha256": "ntfYchL2tf+xoRRXip+C2uEjAdWa73zdKFJMbRCrwJg=",
+                "Version": "$LATEST",
+                "VpcConfig": {
+                    "SubnetIds": [],
+                    "SecurityGroupIds": [],
+                    "VpcId": ""
+                },
+                "TracingConfig": {
+                    "Mode": "PassThrough"
+                },
+                "RevisionId": "51415854-cfdd-4461-ab71-e45bf9e6bc0e"
+            }
+        ]
+    }
+}
diff --git a/tests/data/placebo/test_lambda_check_permission/tagging.GetResources_1.json b/tests/data/placebo/test_lambda_check_permission/tagging.GetResources_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_lambda_check_permission/tagging.GetResources_1.json
@@ -0,0 +1,8 @@
+{
+    "status_code": 200,
+    "data": {
+        "PaginationToken": "",
+        "ResourceTagMappingList": [],
+        "ResponseMetadata": {}
+    }
+}
diff --git a/tests/test_lambda.py b/tests/test_lambda.py
--- a/tests/test_lambda.py
+++ b/tests/test_lambda.py
@@ -156,6 +156,24 @@ def test_delete_layer(self):
 
 class LambdaTest(BaseTest):
 
+    def test_lambda_check_permission(self):
+        # lots of pre-conditions, iam role with iam read only policy attached
+        # and a permission boundary with deny on iam read access.
+        factory = self.replay_flight_data('test_lambda_check_permission')
+        p = self.load_policy(
+            {
+                'name': 'lambda-check',
+                'resource': 'lambda',
+                'filters': [
+                    {'FunctionName': 'custodian-log-age'},
+                    {'type': 'check-permissions',
+                     'match': 'allowed',
+                     'actions': ['iam:ListUsers']}]
+            },
+            session_factory=factory)
+        resources = p.run()
+        assert not resources
+
     def test_lambda_config_source(self):
         factory = self.replay_flight_data("test_aws_lambda_config_source")
         p = self.load_policy(