diff --git a/c7n/resources/iam.py b/c7n/resources/iam.py
index f7d933969..1b507dfa3 100644
--- a/c7n/resources/iam.py
+++ b/c7n/resources/iam.py
@@ -38,7 +38,8 @@ from c7n.manager import resources
 from c7n.query import QueryResourceManager, DescribeSource, TypeInfo
 from c7n.resolver import ValuesFrom
 from c7n.tags import TagActionFilter, TagDelayedAction, Tag, RemoveTag
-from c7n.utils import local_session, type_schema, chunks, filter_empty, QueryParser
+from c7n.utils import (
+    get_partition, local_session, type_schema, chunks, filter_empty, QueryParser)
 
 from c7n.resources.aws import Arn
 from c7n.resources.securityhub import OtherResourcePostFinding
@@ -148,6 +149,59 @@ class RoleRemoveTag(RemoveTag):
                 continue
 
 
+class SetBoundary(BaseAction):
+    """Set IAM Permission boundary on an IAM Role or User.
+
+    A role or user can only have a single permission boundary set.
+    """
+
+    schema = type_schema(
+        'set-boundary',
+        state={'enum': ['present', 'absent']},
+        policy={'type': 'string'})
+
+    def validate(self):
+        state = self.data.get('state', 'present') == 'present'
+        if state and not self.data.get('policy'):
+            raise PolicyValidationError("set-boundary requires policy arn")
+
+    def process(self, resources):
+        state = self.data.get('state', 'present') == 'present'
+        client = self.manager.session_factory().client('iam')
+        policy = self.data.get('policy')
+        if policy and not policy.startswith('arn'):
+            policy = 'arn:{}:iam::{}:policy/{}'.format(
+                get_partition(self.manager.config.region),
+                self.manager.account_id, policy)
+        for r in resources:
+            method, params = self.get_method(client, state, policy, r)
+            try:
+                self.manager.retry(method, **params)
+            except client.exceptions.NoSuchEntityException:
+                continue
+
+    def get_method(self, client, state, policy, resource):
+        raise NotImplementedError()
+
+
+@Role.action_registry.register('set-boundary')
+class RoleSetBoundary(SetBoundary):
+
+    def get_permissions(self):
+        if self.data.get('state', True):
+            return ('iam:PutRolePermissionsBoundary',)
+        return ('iam:DeleteRolePermissionsBoundary',)
+
+    def get_method(self, client, state, policy, resource):
+        if state:
+            return client.put_role_permissions_boundary, {
+                'RoleName': resource['RoleName'],
+                'PermissionsBoundary': policy}
+        else:
+            return client.delete_role_permissions_boundary, {
+                'RoleName': resource['RoleName']}
+
+
 @resources.register('iam-user')
 class User(QueryResourceManager):
 
@@ -266,6 +320,24 @@ class SetGroups(BaseAction):
                 continue
 
 
+@User.action_registry.register('set-boundary')
+class UserSetBoundary(SetBoundary):
+
+    def get_permissions(self):
+        if self.data.get('state', True):
+            return ('iam:PutUserPermissionsBoundary',)
+        return ('iam:DeleteUserPermissionsBoundary',)
+
+    def get_method(self, client, state, policy, resource):
+        if state:
+            return client.put_user_permissions_boundary, {
+                'UserName': resource['UserName'],
+                'PermissionsBoundary': policy}
+        else:
+            return client.delete_user_permissions_boundary, {
+                'UserName': resource['UserName']}
+
+
 @resources.register('iam-policy')
 class Policy(QueryResourceManager):
 

Permissions Boundary Policy support for AWS IAM Roles
Problem Statement:
There doesn't appear to a c7n action that can manage permissions boundary policies for IAM roles.  

Proposed Solution:
A solution to this would likely look very similar to the aws.iam-role action called set-policy where instead of making calls like "client.attach_role_policy" and "client.detach_role_policy" it would call "client.put_role_permissions_boundary" and client.delete_role_permissions_boundary". 

Ideally, this would also support IAM users but there doesn't appear to be an existing aws.iam-user action for set-policy (likely due to best practice of not assigning policies directly to users).  The boto actions for that would be:
put_user_permissions_boundary()
delete_user_permissions_boundary()



diff --git a/tests/data/placebo/test_iam_role_remove_boundary/iam.DeleteRolePermissionsBoundary_1.json b/tests/data/placebo/test_iam_role_remove_boundary/iam.DeleteRolePermissionsBoundary_1.json
new file mode 100644
index 000000000..d6af1989f
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_remove_boundary/iam.DeleteRolePermissionsBoundary_1.json
@@ -0,0 +1,6 @@
+{
+  "status_code": 200,
+  "data": {
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_1.json b/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_1.json
new file mode 100644
index 000000000..3acdc0af6
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_1.json
@@ -0,0 +1,35 @@
+{
+  "status_code": 200,
+  "data": {
+    "Role": {
+      "Path": "/",
+      "RoleName": "accountmgr-dev",
+      "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+      "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2019,
+        "month": 5,
+        "day": 21,
+        "hour": 22,
+        "minute": 42,
+        "second": 1,
+        "microsecond": 0
+      },
+      "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%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"
+      },
+      "Tags": [
+        {
+          "Key": "Owner",
+          "Value": "kapil"
+        }
+      ],
+      "RoleLastUsed": {}
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_2.json b/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_2.json
new file mode 100644
index 000000000..721c59f5f
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_remove_boundary/iam.GetRole_2.json
@@ -0,0 +1,31 @@
+{
+  "status_code": 200,
+  "data": {
+    "Role": {
+      "Path": "/",
+      "RoleName": "accountmgr-dev",
+      "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+      "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2019,
+        "month": 5,
+        "day": 21,
+        "hour": 22,
+        "minute": 42,
+        "second": 1,
+        "microsecond": 0
+      },
+      "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%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,
+      "Tags": [
+        {
+          "Key": "Owner",
+          "Value": "kapil"
+        }
+      ],
+      "RoleLastUsed": {}
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_remove_boundary/iam.ListRoles_1.json b/tests/data/placebo/test_iam_role_remove_boundary/iam.ListRoles_1.json
new file mode 100644
index 000000000..3a8e7b7d9
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_remove_boundary/iam.ListRoles_1.json
@@ -0,0 +1,25 @@
+{
+  "status_code": 200,
+  "data": {
+    "Roles": [
+      {
+        "Path": "/",
+        "RoleName": "accountmgr-dev",
+        "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+        "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+        "CreateDate": {
+          "__class__": "datetime",
+          "year": 2019,
+          "month": 5,
+          "day": 21,
+          "hour": 22,
+          "minute": 42,
+          "second": 1,
+          "microsecond": 0
+        }
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
diff --git a/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_1.json b/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_1.json
new file mode 100644
index 000000000..721c59f5f
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_1.json
@@ -0,0 +1,31 @@
+{
+  "status_code": 200,
+  "data": {
+    "Role": {
+      "Path": "/",
+      "RoleName": "accountmgr-dev",
+      "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+      "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2019,
+        "month": 5,
+        "day": 21,
+        "hour": 22,
+        "minute": 42,
+        "second": 1,
+        "microsecond": 0
+      },
+      "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%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,
+      "Tags": [
+        {
+          "Key": "Owner",
+          "Value": "kapil"
+        }
+      ],
+      "RoleLastUsed": {}
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_2.json b/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_2.json
new file mode 100644
index 000000000..3acdc0af6
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_set_boundary/iam.GetRole_2.json
@@ -0,0 +1,35 @@
+{
+  "status_code": 200,
+  "data": {
+    "Role": {
+      "Path": "/",
+      "RoleName": "accountmgr-dev",
+      "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+      "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2019,
+        "month": 5,
+        "day": 21,
+        "hour": 22,
+        "minute": 42,
+        "second": 1,
+        "microsecond": 0
+      },
+      "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Sid%22%3A%22%22%2C%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"
+      },
+      "Tags": [
+        {
+          "Key": "Owner",
+          "Value": "kapil"
+        }
+      ],
+      "RoleLastUsed": {}
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_set_boundary/iam.ListRoles_1.json b/tests/data/placebo/test_iam_role_set_boundary/iam.ListRoles_1.json
new file mode 100644
index 000000000..18117a559
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_set_boundary/iam.ListRoles_1.json
@@ -0,0 +1,25 @@
+{
+  "status_code": 200,
+  "data": {
+    "Roles": [
+      {
+        "Path": "/",
+        "RoleName": "accountmgr-dev",
+        "RoleId": "AROAZL6XWCR2JVSTMAF3J",
+        "Arn": "arn:aws:iam::644160558196:role/accountmgr-dev",
+        "CreateDate": {
+          "__class__": "datetime",
+          "year": 2019,
+          "month": 5,
+          "day": 21,
+          "hour": 22,
+          "minute": 42,
+          "second": 1,
+          "microsecond": 0
+        }
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_role_set_boundary/iam.PutRolePermissionsBoundary_1.json b/tests/data/placebo/test_iam_role_set_boundary/iam.PutRolePermissionsBoundary_1.json
new file mode 100644
index 000000000..d6af1989f
--- /dev/null
+++ b/tests/data/placebo/test_iam_role_set_boundary/iam.PutRolePermissionsBoundary_1.json
@@ -0,0 +1,6 @@
+{
+  "status_code": 200,
+  "data": {
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_remove_boundary/iam.DeleteUserPermissionsBoundary_1.json b/tests/data/placebo/test_iam_user_remove_boundary/iam.DeleteUserPermissionsBoundary_1.json
new file mode 100644
index 000000000..d6af1989f
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_remove_boundary/iam.DeleteUserPermissionsBoundary_1.json
@@ -0,0 +1,6 @@
+{
+  "status_code": 200,
+  "data": {
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_1.json b/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_1.json
new file mode 100644
index 000000000..b96687710
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_1.json
@@ -0,0 +1,26 @@
+{
+  "status_code": 200,
+  "data": {
+    "User": {
+      "Path": "/",
+      "UserName": "devbot",
+      "UserId": "AIDAZL6XWCR2OXOEADG5A",
+      "Arn": "arn:aws:iam::644160558196:user/devbot",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 2,
+        "hour": 10,
+        "minute": 36,
+        "second": 22,
+        "microsecond": 0
+      },
+      "PermissionsBoundary": {
+        "PermissionsBoundaryType": "Policy",
+        "PermissionsBoundaryArn": "arn:aws:iam::644160558196:policy/BlackListIamList"
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_2.json b/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_2.json
new file mode 100644
index 000000000..f9cfbf313
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_remove_boundary/iam.GetUser_2.json
@@ -0,0 +1,22 @@
+{
+  "status_code": 200,
+  "data": {
+    "User": {
+      "Path": "/",
+      "UserName": "devbot",
+      "UserId": "AIDAZL6XWCR2OXOEADG5A",
+      "Arn": "arn:aws:iam::644160558196:user/devbot",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 2,
+        "hour": 10,
+        "minute": 36,
+        "second": 22,
+        "microsecond": 0
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_remove_boundary/iam.ListUsers_1.json b/tests/data/placebo/test_iam_user_remove_boundary/iam.ListUsers_1.json
new file mode 100644
index 000000000..e64de6a2d
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_remove_boundary/iam.ListUsers_1.json
@@ -0,0 +1,25 @@
+{
+  "status_code": 200,
+  "data": {
+    "Users": [
+      {
+        "Path": "/",
+        "UserName": "devbot",
+        "UserId": "AIDAZL6XWCR2OXOEADG5A",
+        "Arn": "arn:aws:iam::644160558196:user/devbot",
+        "CreateDate": {
+          "__class__": "datetime",
+          "year": 2020,
+          "month": 4,
+          "day": 2,
+          "hour": 10,
+          "minute": 36,
+          "second": 22,
+          "microsecond": 0
+        }
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_1.json b/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_1.json
new file mode 100644
index 000000000..f9cfbf313
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_1.json
@@ -0,0 +1,22 @@
+{
+  "status_code": 200,
+  "data": {
+    "User": {
+      "Path": "/",
+      "UserName": "devbot",
+      "UserId": "AIDAZL6XWCR2OXOEADG5A",
+      "Arn": "arn:aws:iam::644160558196:user/devbot",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 2,
+        "hour": 10,
+        "minute": 36,
+        "second": 22,
+        "microsecond": 0
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_2.json b/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_2.json
new file mode 100644
index 000000000..b96687710
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_set_boundary/iam.GetUser_2.json
@@ -0,0 +1,26 @@
+{
+  "status_code": 200,
+  "data": {
+    "User": {
+      "Path": "/",
+      "UserName": "devbot",
+      "UserId": "AIDAZL6XWCR2OXOEADG5A",
+      "Arn": "arn:aws:iam::644160558196:user/devbot",
+      "CreateDate": {
+        "__class__": "datetime",
+        "year": 2020,
+        "month": 4,
+        "day": 2,
+        "hour": 10,
+        "minute": 36,
+        "second": 22,
+        "microsecond": 0
+      },
+      "PermissionsBoundary": {
+        "PermissionsBoundaryType": "Policy",
+        "PermissionsBoundaryArn": "arn:aws:iam::644160558196:policy/BlackListIamList"
+      }
+    },
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_set_boundary/iam.ListUsers_1.json b/tests/data/placebo/test_iam_user_set_boundary/iam.ListUsers_1.json
new file mode 100644
index 000000000..e64de6a2d
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_set_boundary/iam.ListUsers_1.json
@@ -0,0 +1,25 @@
+{
+  "status_code": 200,
+  "data": {
+    "Users": [
+      {
+        "Path": "/",
+        "UserName": "devbot",
+        "UserId": "AIDAZL6XWCR2OXOEADG5A",
+        "Arn": "arn:aws:iam::644160558196:user/devbot",
+        "CreateDate": {
+          "__class__": "datetime",
+          "year": 2020,
+          "month": 4,
+          "day": 2,
+          "hour": 10,
+          "minute": 36,
+          "second": 22,
+          "microsecond": 0
+        }
+      }
+    ],
+    "IsTruncated": false,
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_iam_user_set_boundary/iam.PutUserPermissionsBoundary_1.json b/tests/data/placebo/test_iam_user_set_boundary/iam.PutUserPermissionsBoundary_1.json
new file mode 100644
index 000000000..d6af1989f
--- /dev/null
+++ b/tests/data/placebo/test_iam_user_set_boundary/iam.PutUserPermissionsBoundary_1.json
@@ -0,0 +1,6 @@
+{
+  "status_code": 200,
+  "data": {
+    "ResponseMetadata": {}
+  }
+}
\ No newline at end of file
diff --git a/tests/test_iam.py b/tests/test_iam.py
index 0570a1e0d..d1db62e9e 100644
--- a/tests/test_iam.py
+++ b/tests/test_iam.py
@@ -25,6 +25,7 @@ from .test_offhours import mock_datetime_now
 from dateutil import parser
 
 from c7n.exceptions import PolicyValidationError
+from c7n.executor import MainThreadExecutor
 from c7n.filters.iamaccess import CrossAccountAccessFilter, PolicyChecker
 from c7n.mu import LambdaManager, LambdaFunction, PythonPackageArchive
 from botocore.exceptions import ClientError
@@ -50,8 +51,6 @@ from c7n.resources.iam import (
     PolicyQueryParser
 )
 
-from c7n.executor import MainThreadExecutor
-
 
 class UserCredentialReportTest(BaseTest):
 
@@ -376,7 +375,7 @@ class IAMMFAFilter(BaseTest):
         self.assertEqual(len(resources), 2)
 
 
-class IamRoleFilterUsage(BaseTest):
+class IamRoleTest(BaseTest):
 
     def test_iam_role_inuse(self):
         session_factory = self.replay_flight_data("test_iam_role_inuse")
@@ -417,9 +416,6 @@ class IamRoleFilterUsage(BaseTest):
         self.assertEqual(len(resources), 1)
         self.assertEqual(resources[0]['RoleId'], "AROAIGK7B2VUDZL4I73HK")
 
-
-class IamRoleTag(BaseTest):
-
     def test_iam_role_actions(self):
         factory = self.replay_flight_data('test_iam_role_tags')
         p = self.load_policy({
@@ -452,9 +448,106 @@ class IamRoleTag(BaseTest):
             {'Application'},
             {t['Key'] for t in role['Tags']})
 
+    def test_iam_role_set_boundary(self):
+        factory = self.replay_flight_data('test_iam_role_set_boundary')
+        p = self.load_policy({
+            'name': 'boundary',
+            'resource': 'iam-role',
+            'filters': [
+                {'RoleName': 'accountmgr-dev'},
+                {'PermissionsBoundary': 'absent'}
+            ],
+            'actions': [{
+                'type': 'set-boundary',
+                'policy': 'arn:aws:iam::644160558196:policy/BlackListIamList'
+            }]},
+            session_factory=factory)
+        resources = p.run()
+        assert len(resources) == 1
+        assert resources[0]['RoleName'] == 'accountmgr-dev'
+        if self.recording:
+            time.sleep(2)
+        client = factory().client('iam')
+        assert client.get_role(RoleName='accountmgr-dev')[
+            'Role'].get('PermissionsBoundary', {}) == {
+                'PermissionsBoundaryType': 'Policy',
+                'PermissionsBoundaryArn': 'arn:aws:iam::644160558196:policy/BlackListIamList',
+        }
+
+    def test_iam_role_remove_boundary(self):
+        factory = self.replay_flight_data('test_iam_role_remove_boundary')
+        p = self.load_policy({
+            'name': 'boundary',
+            'resource': 'iam-role',
+            'filters': [
+                {'RoleName': 'accountmgr-dev'},
+                {'PermissionsBoundary': 'present'}
+            ],
+            'actions': [{
+                'type': 'set-boundary',
+                'state': 'absent',
+            }]},
+            session_factory=factory)
+        p.resource_manager.execution_factory = MainThreadExecutor
+        resources = p.run()
+        assert len(resources) == 1
+        assert resources[0]['RoleName'] == 'accountmgr-dev'
+        if self.recording:
+            time.sleep(5)
+        client = factory().client('iam')
+        assert client.get_role(RoleName='accountmgr-dev')[
+            'Role'].get('PermissionsBoundary', {}) == {}
+
 
 class IamUserTest(BaseTest):
 
+    def test_iam_user_set_boundary(self):
+        factory = self.replay_flight_data('test_iam_user_set_boundary')
+        p = self.load_policy({
+            'name': 'boundary',
+            'resource': 'iam-user',
+            'filters': [
+                {'UserName': 'devbot'},
+                {'PermissionsBoundary': 'absent'}],
+            'actions': [{
+                'type': 'set-boundary',
+                'policy': 'arn:aws:iam::644160558196:policy/BlackListIamList',
+            }]},
+            session_factory=factory)
+
+        resources = p.run()
+        assert len(resources) == 1
+        assert resources[0]['UserName'] == 'devbot'
+
+        if self.recording:
+            time.sleep(1)
+        client = factory().client('iam')
+        assert client.get_user(UserName='devbot')['User'].get('PermissionsBoundary', {}) == {
+            'PermissionsBoundaryType': 'Policy',
+            'PermissionsBoundaryArn': 'arn:aws:iam::644160558196:policy/BlackListIamList'
+        }
+
+    def test_iam_user_boundary_remove(self):
+        factory = self.replay_flight_data('test_iam_user_remove_boundary')
+        p = self.load_policy({
+            'name': 'boundary',
+            'resource': 'iam-user',
+            'filters': [
+                {'UserName': 'devbot'},
+                {'PermissionsBoundary': 'present'}],
+            'actions': [{
+                'type': 'set-boundary', 'state': 'absent'}]},
+            session_factory=factory)
+
+        resources = p.run()
+        assert len(resources) == 1
+        assert resources[0]['UserName'] == 'devbot'
+
+        if self.recording:
+            time.sleep(1)
+        client = factory().client('iam')
+        assert client.get_user(UserName='devbot')['User'].get('PermissionsBoundary', {}) == {}
+
     def test_iam_user_usage_no_such_entity(self):
         p = self.load_policy({
             'name': 'usage-check',
diff --git a/tests/zpill.py b/tests/zpill.py
index c8cd45eb7..bc183b3f9 100644
--- a/tests/zpill.py
+++ b/tests/zpill.py
@@ -272,9 +272,9 @@ class RedPill(pill.Pill):
         if 'ResponseMetadata' in response_data:
             response_data['ResponseMetadata'] = {}
 
-        response_data = json.dumps(response_data, default=self.datetime_converter)
+        response_data = json.dumps(response_data, default=serialize)
         response_data = re.sub("\d{12}", ACCOUNT_ID, response_data)  # noqa
-        response_data = json.loads(response_data)
+        response_data = json.loads(response_data, object_hook=deserialize)
 
         super(RedPill, self).save_response(service, operation, response_data,
                     http_response)

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock
apt-get update apt-get install -y gcc
3.8
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
apipkg==1.5
appdirs==1.4.3
argcomplete==1.11.1
attrs==19.3.0
aws-xray-sdk==2.4.3
bleach==3.1.4
boto3==1.12.36
botocore==1.15.36
-e git+https://github.com/cloud-custodian/cloud-custodian.git@c9a61ade52b5d6eb4c32ede4e2c69220ea38bcd7#egg=c7n
certifi==2020.4.5.1
cffi==1.14.0
chardet==3.0.4
coverage==5.0.4
cryptography==2.9
distlib==0.3.0
docutils==0.15.2
entrypoints==0.3
exceptiongroup==1.2.2
execnet==1.7.1
filelock==3.0.12
flake8==3.7.9
future==0.18.2
idna==2.9
importlib-metadata==1.6.0
iniconfig==2.1.0
jeepney==0.4.3
jmespath==0.9.5
jsonpatch==1.25
jsonpickle==1.3
jsonpointer==2.0
jsonschema==3.2.0
keyring==21.2.0
mccabe==0.6.1
mock==4.0.2
more-itertools==8.2.0
multidict==4.7.5
packaging==20.3
pkginfo==1.5.0.1
placebo==0.9.0
pluggy==1.5.0
psutil==5.7.0
py==1.8.1
pycodestyle==2.5.0
pycparser==2.20
pyflakes==2.1.1
Pygments==2.6.1
pyparsing==2.4.7
pyrsistent==0.16.0
pytest==8.3.5
pytest-cov==2.8.1
pytest-forked==1.1.3
pytest-mock==3.14.0
pytest-xdist==1.31.0
python-dateutil==2.8.1
PyYAML==5.3.1
readme-renderer==25.0
requests==2.23.0
requests-toolbelt==0.9.1
s3transfer==0.3.3
SecretStorage==3.1.2
six==1.14.0
tabulate==0.8.7
toml==0.10.0
tomli==2.2.1
tox==3.14.6
tqdm==4.45.0
twine==3.1.1
urllib3==1.25.8
vcrpy==4.0.2
virtualenv==20.0.16
wcwidth==0.1.9
webencodings==0.5.1
wrapt==1.12.1
yarl==1.4.2
zipp==3.1.0

name: cloud-custodian
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - apipkg==1.5
      - appdirs==1.4.3
      - argcomplete==1.11.1
      - attrs==19.3.0
      - aws-xray-sdk==2.4.3
      - bleach==3.1.4
      - boto3==1.12.36
      - botocore==1.15.36
      - c7n==0.9.0
      - certifi==2020.4.5.1
      - cffi==1.14.0
      - chardet==3.0.4
      - coverage==5.0.4
      - cryptography==2.9
      - distlib==0.3.0
      - docutils==0.15.2
      - entrypoints==0.3
      - exceptiongroup==1.2.2
      - execnet==1.7.1
      - filelock==3.0.12
      - flake8==3.7.9
      - future==0.18.2
      - idna==2.9
      - importlib-metadata==1.6.0
      - iniconfig==2.1.0
      - jeepney==0.4.3
      - jmespath==0.9.5
      - jsonpatch==1.25
      - jsonpickle==1.3
      - jsonpointer==2.0
      - jsonschema==3.2.0
      - keyring==21.2.0
      - mccabe==0.6.1
      - mock==4.0.2
      - more-itertools==8.2.0
      - multidict==4.7.5
      - packaging==20.3
      - pkginfo==1.5.0.1
      - placebo==0.9.0
      - pluggy==1.5.0
      - psutil==5.7.0
      - py==1.8.1
      - pycodestyle==2.5.0
      - pycparser==2.20
      - pyflakes==2.1.1
      - pygments==2.6.1
      - pyparsing==2.4.7
      - pyrsistent==0.16.0
      - pytest==8.3.5
      - pytest-cov==2.8.1
      - pytest-forked==1.1.3
      - pytest-mock==3.14.0
      - pytest-xdist==1.31.0
      - python-dateutil==2.8.1
      - pyyaml==5.3.1
      - readme-renderer==25.0
      - requests==2.23.0
      - requests-toolbelt==0.9.1
      - s3transfer==0.3.3
      - secretstorage==3.1.2
      - six==1.14.0
      - tabulate==0.8.7
      - toml==0.10.0
      - tomli==2.2.1
      - tox==3.14.6
      - tqdm==4.45.0
      - twine==3.1.1
      - urllib3==1.25.8
      - vcrpy==4.0.2
      - virtualenv==20.0.16
      - wcwidth==0.1.9
      - webencodings==0.5.1
      - wrapt==1.12.1
      - yarl==1.4.2
      - zipp==3.1.0
prefix: /opt/conda/envs/cloud-custodian

tests/test_iam.py::IamRoleTest::test_iam_role_remove_boundary tests/test_iam.py::IamRoleTest::test_iam_role_set_boundary tests/test_iam.py::IamUserTest::test_iam_user_boundary_remove tests/test_iam.py::IamUserTest::test_iam_user_set_boundary
tests/test_iam.py::UserCredentialReportTest::test_credential_access_key_multifilter_delete tests/test_iam.py::UserCredentialReportTest::test_credential_access_key_reverse_filter_delete tests/test_iam.py::IamUserTest::test_iam_user_access_key_multi_chain
tests/test_iam.py::UserCredentialReportTest::test_access_key_last_service tests/test_iam.py::UserCredentialReportTest::test_credential_report_generate tests/test_iam.py::UserCredentialReportTest::test_old_console_users tests/test_iam.py::UserCredentialReportTest::test_record_transform tests/test_iam.py::UserCredentialReportTest::test_record_transform_with_keys tests/test_iam.py::IamUserTag::test_iam_user_actions tests/test_iam.py::IamUserTag::test_iam_user_add_remove_groups tests/test_iam.py::IAMMFAFilter::test_iam_mfa_filter tests/test_iam.py::IamRoleTest::test_iam_role_actions tests/test_iam.py::IamRoleTest::test_iam_role_get_resources tests/test_iam.py::IamRoleTest::test_iam_role_inuse tests/test_iam.py::IamRoleTest::test_iam_role_unused tests/test_iam.py::IamUserTest::test_iam_user_access_key_filter tests/test_iam.py::IamUserTest::test_iam_user_access_key_multi tests/test_iam.py::IamUserTest::test_iam_user_check_permissions tests/test_iam.py::IamUserTest::test_iam_user_delete tests/test_iam.py::IamUserTest::test_iam_user_delete_some_access tests/test_iam.py::IamUserTest::test_iam_user_policy tests/test_iam.py::IamUserTest::test_iam_user_usage tests/test_iam.py::IamUserTest::test_iam_user_usage_no_such_entity tests/test_iam.py::IamUserGroupMembership::test_iam_user_group_membership tests/test_iam.py::IamInstanceProfileFilterUsage::test_iam_instance_profile_inuse tests/test_iam.py::IamInstanceProfileFilterUsage::test_iam_instance_profile_unused tests/test_iam.py::IamPolicyFilterUsage::test_iam_attached_policies tests/test_iam.py::IamPolicyFilterUsage::test_iam_policy_get_resources tests/test_iam.py::IamPolicyFilterUsage::test_iam_unattached_policies tests/test_iam.py::IamPolicyFilterUsage::test_iam_user_policy_permission tests/test_iam.py::IamPolicy::test_iam_has_allow_all_policies tests/test_iam.py::IamPolicy::test_iam_policy_delete tests/test_iam.py::IamPolicy::test_iam_query_parser tests/test_iam.py::IamGroupFilterUsage::test_iam_group_get_resources tests/test_iam.py::IamGroupFilterUsage::test_iam_group_unused_users tests/test_iam.py::IamGroupFilterUsage::test_iam_group_used_users tests/test_iam.py::IamManagedPolicyUsage::test_iam_role_has_specific_managed_policy tests/test_iam.py::IamManagedPolicyUsage::test_iam_role_no_specific_managed_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_group_has_inline_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_group_has_inline_policy2 tests/test_iam.py::IamInlinePolicyUsage::test_iam_group_no_inline_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_role_has_inline_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_role_no_inline_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_user_has_inline_policy tests/test_iam.py::IamInlinePolicyUsage::test_iam_user_no_inline_policy tests/test_iam.py::KMSCrossAccount::test_kms_cross_account tests/test_iam.py::GlacierCrossAccount::test_glacier_cross_account tests/test_iam.py::LambdaCrossAccount::test_lambda_cross_account tests/test_iam.py::ECRCrossAccount::test_ecr_cross_account tests/test_iam.py::SQSCrossAccount::test_sqs_cross_account tests/test_iam.py::SNSCrossAccount::test_sns_cross_account tests/test_iam.py::SNSCrossAccount::test_sns_cross_account_endpoint_condition tests/test_iam.py::CrossAccountChecker::test_not_principal_allowed tests/test_iam.py::CrossAccountChecker::test_s3_everyone_only tests/test_iam.py::CrossAccountChecker::test_s3_policies tests/test_iam.py::CrossAccountChecker::test_s3_policies_multiple_conditions tests/test_iam.py::CrossAccountChecker::test_s3_policies_vpc tests/test_iam.py::CrossAccountChecker::test_s3_principal_org_id tests/test_iam.py::CrossAccountChecker::test_sqs_policies tests/test_iam.py::SetRolePolicyAction::test_set_policy_attached tests/test_iam.py::SetRolePolicyAction::test_set_policy_detached tests/test_iam.py::DeleteRoleAction::test_delete_role tests/test_iam.py::DeleteRoleAction::test_delete_role_error tests/test_iam.py::DeleteRoleAction::test_force_delete_role tests/test_iam.py::DeleteRoleAction::test_set_policy_validation_error tests/test_iam.py::DeleteRoleAction::test_set_policy_wildcard
c7n/resources/iam.py
c7n/resources/iam.py