Repository: cloud-custodian/cloud-custodian

Issue:
AWS Role deletion fails
We're trying to code a policy that immediately deletes any role trusting a non-whitelisted account id, something along the lines of:

```
policies:
  - name: iam-delete-roles-with-unauthorized-cross-account-access
    resource: iam-role
    description: |
      Remove roles trusting an unauthorized account
    region: eu-west-1
    filters:
      - type: cross-account
        whitelist:
          - "XXXXXXXXXXX" 
          - "YYYYYYYYYYY" 
          - "WWWWWWWW"
    actions:
      - delete
```

Filtering works all right, but we get an error when deleting the role that says:
```
2019-05-29 15:22:06,751: custodian.actions:WARNING Role:arn:aws:iam::506838722335:role/xxxxxxxxxx cannot be deleted, must remove role from instance profile first
2019-05-29 15:22:06,826: c7n_org:ERROR Exception running policy:iam-delete-roles-with-unauthorized-cross-account-access account:Prod region:eu-west-1 error:An error occurred (DeleteConflict) when calling the DeleteRole operation: Cannot delete entity, must detach all policies first.
```
First error doesn't make sense for a cross-account role, as they're not linked to instance policies, and it's actually caused by a hardcoded message in Custodian's code [here](https://github.com/cloud-custodian/cloud-custodian/blob/ad2a8d5a18ac1a6b9a7ce76e7b1544d99791143b/c7n/resources/iam.py#L854).The second error makes more sense as there's a policy attached to this role, but we'd expected Custodian to detach policies and then delete the role. After all, I'm not sure how frequently roles will be empty when attempting to delete them. Or am I missing something somewhere?


Discussion:
Hello @dvianello , when you create an IAM role through the console, AWS automatically creates an instance profile and gives it the same name as the role. Afaics, you shouldn't be able to delete the role until the instance profile is deleted since instance profile contains the iam role. I think the first error won't be seen if you try to delete a role which isn't linked to any instance profile.
Hi @PratMis, 

it _think_ that's the default for roles that have ec2 as a trust, not for roles where the trust is a different AWS account. Manually trying to delete the role through the CLI fails with an error mentioning that there are still policies attached to the role and it can't be deleted, and I assume that's the error Custodian is getting too, but it's partially masked by how the code is currently dealing with `DeleteConflictException`. 
Ah, got it! Thanks for the clarification. I'll put a PR to make the error message more explicit if that works.
more relevant than the logging is actually handling the scenario for detaching associated resources from the role aka `force: true`
Sounds good! 

Resolving patch:
diff --git a/c7n/resources/iam.py b/c7n/resources/iam.py
--- a/c7n/resources/iam.py
+++ b/c7n/resources/iam.py
@@ -778,6 +778,9 @@ class SetPolicy(BaseAction):
             - type: no-specific-managed-policy
               value: my-iam-policy
           actions:
+            - type: set-policy
+              state: detached
+              arn: *
             - type: set-policy
               state: attached
               arn: arn:aws:iam::123456789012:policy/my-iam-policy
@@ -791,23 +794,38 @@ class SetPolicy(BaseAction):
 
     permissions = ('iam:AttachRolePolicy', 'iam:DetachRolePolicy',)
 
+    def validate(self):
+        if self.data.get('state') == 'attached' and self.data.get('arn') == "*":
+            raise PolicyValidationError(
+                '* operator is not supported for state: attached on %s' % (self.manager.data))
+
     def process(self, resources):
         client = local_session(self.manager.session_factory).client('iam')
         policy_arn = self.data['arn']
         state = self.data['state']
-
         for r in resources:
             if state == 'attached':
                 client.attach_role_policy(
                     RoleName=r['RoleName'],
                     PolicyArn=policy_arn)
-            elif state == 'detached':
+            elif state == 'detached' and policy_arn != "*":
                 try:
                     client.detach_role_policy(
                         RoleName=r['RoleName'],
                         PolicyArn=policy_arn)
                 except client.exceptions.NoSuchEntityException:
-                    pass
+                    continue
+            elif state == 'detached' and policy_arn == "*":
+                try:
+                    self.detach_all_policies(client, r)
+                except client.exceptions.NoSuchEntityException:
+                    continue
+
+    def detach_all_policies(self, client, resource):
+        attached_policy = client.list_attached_role_policies(RoleName=resource['RoleName'])
+        policy_arns = [p.get('PolicyArn') for p in attached_policy['AttachedPolicies']]
+        for parn in policy_arns:
+            client.detach_role_policy(RoleName=resource['RoleName'], PolicyArn=parn)
 
 
 @Role.action_registry.register('delete')
@@ -827,21 +845,28 @@ class RoleDelete(BaseAction):
               match-operator: all
               LastAuthenticated: null
           actions:
-            - delete
+            - type: delete
+              force: True
 
     """
-    schema = type_schema('delete')
+    schema = type_schema('delete',
+        force={'type': 'boolean', 'default': False})
+
     permissions = ('iam:DeleteRole',)
 
     def process(self, resources):
         client = local_session(self.manager.session_factory).client('iam')
         error = None
+        if self.data.get('force', False):
+            policy_setter = self.manager.action_registry['set-policy'](
+                {'state': 'detached', 'arn': '*'}, self.manager)
+            policy_setter.process(resources)
         for r in resources:
             try:
                 client.delete_role(RoleName=r['RoleName'])
             except client.exceptions.DeleteConflictException as e:
                 self.log.warning(
-                    "Role:%s cannot be deleted, must remove role from instance profile first"
+                    "Role:%s cannot be deleted, set force to detach policy and delete"
                     % r['Arn'])
                 error = e
             except client.exceptions.NoSuchEntityException:


Test patch:
diff --git a/tests/data/placebo/test_force_delete_role/iam.DeleteRole_1.json b/tests/data/placebo/test_force_delete_role/iam.DeleteRole_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.DeleteRole_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "b7b99cfc-9cfb-11e9-bf4f-b57e787d7894",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b7b99cfc-9cfb-11e9-bf4f-b57e787d7894",
+                "content-type": "text/xml",
+                "content-length": "200",
+                "date": "Tue, 02 Jul 2019 19:00:54 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_1.json b/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_1.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "b774a4be-9cfb-11e9-82f6-8f5e7da59408",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b774a4be-9cfb-11e9-82f6-8f5e7da59408",
+                "content-type": "text/xml",
+                "content-length": "212",
+                "date": "Tue, 02 Jul 2019 19:00:53 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_2.json b/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.DetachRolePolicy_2.json
@@ -0,0 +1,16 @@
+{
+    "status_code": 200,
+    "data": {
+        "ResponseMetadata": {
+            "RequestId": "b7840ea2-9cfb-11e9-82f6-8f5e7da59408",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b7840ea2-9cfb-11e9-82f6-8f5e7da59408",
+                "content-type": "text/xml",
+                "content-length": "212",
+                "date": "Tue, 02 Jul 2019 19:00:53 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.GetRole_1.json b/tests/data/placebo/test_force_delete_role/iam.GetRole_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.GetRole_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200,
+    "data": {
+        "Role": {
+            "Path": "/aws-service-role/support.amazonaws.com/",
+            "RoleName": "AWSServiceRoleForSupport",
+            "RoleId": "AROAJWGLHKEUYCDFCDPXG",
+            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
+            "CreateDate": {
+                "__class__": "datetime",
+                "year": 2019,
+                "month": 1,
+                "day": 19,
+                "hour": 23,
+                "minute": 27,
+                "second": 57,
+                "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%22support.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+            "Description": "Enables resource access for AWS to provide billing, administrative and support services",
+            "MaxSessionDuration": 3600
+        },
+        "ResponseMetadata": {
+            "RequestId": "b71f5981-9cfb-11e9-82f6-8f5e7da59408",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b71f5981-9cfb-11e9-82f6-8f5e7da59408",
+                "content-type": "text/xml",
+                "content-length": "1015",
+                "date": "Tue, 02 Jul 2019 19:00:53 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.GetRole_2.json b/tests/data/placebo/test_force_delete_role/iam.GetRole_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.GetRole_2.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "Role": {
+            "Path": "/",
+            "RoleName": "c7n-pratyush-test",
+            "RoleId": "AROA4WFP4XXCUSREXX2XL",
+            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/c7n-pratyush-test",
+            "CreateDate": {
+                "__class__": "datetime",
+                "year": 2019,
+                "month": 7,
+                "day": 2,
+                "hour": 18,
+                "minute": 46,
+                "second": 58,
+                "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",
+            "Description": "Allows Lambda functions to call AWS services on your behalf.",
+            "MaxSessionDuration": 3600,
+            "Tags": [
+                {
+                    "Key": "Name",
+                    "Value": "Pratyush"
+                }
+            ]
+        },
+        "ResponseMetadata": {
+            "RequestId": "b72bb4d5-9cfb-11e9-82f6-8f5e7da59408",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b72bb4d5-9cfb-11e9-82f6-8f5e7da59408",
+                "content-type": "text/xml",
+                "content-length": "1017",
+                "date": "Tue, 02 Jul 2019 19:00:53 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.GetRole_3.json b/tests/data/placebo/test_force_delete_role/iam.GetRole_3.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.GetRole_3.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 404,
+    "data": {
+        "Error": {
+            "Type": "Sender",
+            "Code": "NoSuchEntity",
+            "Message": "The role with name c7n-pratyush-test cannot be found."
+        },
+        "ResponseMetadata": {
+            "RequestId": "b7f483ac-9cfb-11e9-92c3-fff4205c7789",
+            "HTTPStatusCode": 404,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b7f483ac-9cfb-11e9-92c3-fff4205c7789",
+                "content-type": "text/xml",
+                "content-length": "297",
+                "date": "Tue, 02 Jul 2019 19:00:54 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.ListAttachedRolePolicies_1.json b/tests/data/placebo/test_force_delete_role/iam.ListAttachedRolePolicies_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.ListAttachedRolePolicies_1.json
@@ -0,0 +1,27 @@
+{
+    "status_code": 200,
+    "data": {
+        "AttachedPolicies": [
+            {
+                "PolicyName": "AlexaForBusinessFullAccess",
+                "PolicyArn": "arn:aws:iam::aws:policy/AlexaForBusinessFullAccess"
+            },
+            {
+                "PolicyName": "AdministratorAccess",
+                "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
+            }
+        ],
+        "IsTruncated": false,
+        "ResponseMetadata": {
+            "RequestId": "b768be81-9cfb-11e9-82f6-8f5e7da59408",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b768be81-9cfb-11e9-82f6-8f5e7da59408",
+                "content-type": "text/xml",
+                "content-length": "715",
+                "date": "Tue, 02 Jul 2019 19:00:53 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_force_delete_role/iam.ListRoles_1.json b/tests/data/placebo/test_force_delete_role/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_force_delete_role/iam.ListRoles_1.json
@@ -0,0 +1,57 @@
+{
+    "status_code": 200,
+    "data": {
+        "Roles": [
+            {
+                "Path": "/aws-service-role/support.amazonaws.com/",
+                "RoleName": "AWSServiceRoleForSupport",
+                "RoleId": "AROAJWGLHKEUYCDFCDPXG",
+                "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
+                "CreateDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 19,
+                    "hour": 23,
+                    "minute": 27,
+                    "second": 57,
+                    "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%22support.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+                "Description": "Enables resource access for AWS to provide billing, administrative and support services",
+                "MaxSessionDuration": 3600
+            },
+            {
+                "Path": "/",
+                "RoleName": "c7n-pratyush-test",
+                "RoleId": "AROA4WFP4XXCUSREXX2XL",
+                "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/c7n-pratyush-test",
+                "CreateDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 7,
+                    "day": 2,
+                    "hour": 18,
+                    "minute": 46,
+                    "second": 58,
+                    "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",
+                "Description": "Allows Lambda functions to call AWS services on your behalf.",
+                "MaxSessionDuration": 3600
+            }
+        ],
+        "IsTruncated": false,
+        "ResponseMetadata": {
+            "RequestId": "b6eb030c-9cfb-11e9-aa62-71a42e37a73f",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "b6eb030c-9cfb-11e9-aa62-71a42e37a73f",
+                "content-type": "text/xml",
+                "content-length": "1797",
+                "date": "Tue, 02 Jul 2019 19:00:52 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_1.json b/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200,
+    "data": {
+        "Role": {
+            "Path": "/aws-service-role/support.amazonaws.com/",
+            "RoleName": "AWSServiceRoleForSupport",
+            "RoleId": "AROAJWGLHKEUYCDFCDPXG",
+            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
+            "CreateDate": {
+                "__class__": "datetime",
+                "year": 2019,
+                "month": 1,
+                "day": 19,
+                "hour": 23,
+                "minute": 27,
+                "second": 57,
+                "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%22support.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+            "Description": "Enables resource access for AWS to provide billing, administrative and support services",
+            "MaxSessionDuration": 3600
+        },
+        "ResponseMetadata": {
+            "RequestId": "0be4a9d6-9cfb-11e9-a119-f17d98cf0d01",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0be4a9d6-9cfb-11e9-a119-f17d98cf0d01",
+                "content-type": "text/xml",
+                "content-length": "1015",
+                "date": "Tue, 02 Jul 2019 18:56:05 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_2.json b/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_set_policy_wildcard/iam.GetRole_2.json
@@ -0,0 +1,41 @@
+{
+    "status_code": 200,
+    "data": {
+        "Role": {
+            "Path": "/",
+            "RoleName": "c7n-pratyush-test",
+            "RoleId": "AROA4WFP4XXCUSREXX2XL",
+            "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/c7n-pratyush-test",
+            "CreateDate": {
+                "__class__": "datetime",
+                "year": 2019,
+                "month": 7,
+                "day": 2,
+                "hour": 18,
+                "minute": 46,
+                "second": 58,
+                "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",
+            "Description": "Allows Lambda functions to call AWS services on your behalf.",
+            "MaxSessionDuration": 3600,
+            "Tags": [
+                {
+                    "Key": "Name",
+                    "Value": "Pratyush"
+                }
+            ]
+        },
+        "ResponseMetadata": {
+            "RequestId": "0bef3063-9cfb-11e9-a119-f17d98cf0d01",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0bef3063-9cfb-11e9-a119-f17d98cf0d01",
+                "content-type": "text/xml",
+                "content-length": "1017",
+                "date": "Tue, 02 Jul 2019 18:56:05 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_1.json b/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_1.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "AttachedPolicies": [],
+        "IsTruncated": false,
+        "ResponseMetadata": {
+            "RequestId": "0c2730a8-9cfb-11e9-8e8b-a729701c2096",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0c2730a8-9cfb-11e9-8e8b-a729701c2096",
+                "content-type": "text/xml",
+                "content-length": "360",
+                "date": "Tue, 02 Jul 2019 18:56:06 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_2.json b/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_set_policy_wildcard/iam.ListAttachedRolePolicies_2.json
@@ -0,0 +1,18 @@
+{
+    "status_code": 200,
+    "data": {
+        "AttachedPolicies": [],
+        "IsTruncated": false,
+        "ResponseMetadata": {
+            "RequestId": "0c759f5c-9cfb-11e9-ba31-51c4e4fb1518",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0c759f5c-9cfb-11e9-ba31-51c4e4fb1518",
+                "content-type": "text/xml",
+                "content-length": "360",
+                "date": "Tue, 02 Jul 2019 18:56:07 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_set_policy_wildcard/iam.ListRoles_1.json b/tests/data/placebo/test_set_policy_wildcard/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_set_policy_wildcard/iam.ListRoles_1.json
@@ -0,0 +1,57 @@
+{
+    "status_code": 200,
+    "data": {
+        "Roles": [
+            {
+                "Path": "/aws-service-role/support.amazonaws.com/",
+                "RoleName": "AWSServiceRoleForSupport",
+                "RoleId": "AROAJWGLHKEUYCDFCDPXG",
+                "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/aws-service-role/support.amazonaws.com/AWSServiceRoleForSupport",
+                "CreateDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 1,
+                    "day": 19,
+                    "hour": 23,
+                    "minute": 27,
+                    "second": 57,
+                    "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%22support.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D",
+                "Description": "Enables resource access for AWS to provide billing, administrative and support services",
+                "MaxSessionDuration": 3600
+            },
+            {
+                "Path": "/",
+                "RoleName": "c7n-pratyush-test",
+                "RoleId": "AROA4WFP4XXCUSREXX2XL",
+                "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/c7n-pratyush-test",
+                "CreateDate": {
+                    "__class__": "datetime",
+                    "year": 2019,
+                    "month": 7,
+                    "day": 2,
+                    "hour": 18,
+                    "minute": 46,
+                    "second": 58,
+                    "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",
+                "Description": "Allows Lambda functions to call AWS services on your behalf.",
+                "MaxSessionDuration": 3600
+            }
+        ],
+        "IsTruncated": false,
+        "ResponseMetadata": {
+            "RequestId": "0bb31225-9cfb-11e9-bb7b-c1e9cddbe175",
+            "HTTPStatusCode": 200,
+            "HTTPHeaders": {
+                "x-amzn-requestid": "0bb31225-9cfb-11e9-bb7b-c1e9cddbe175",
+                "content-type": "text/xml",
+                "content-length": "1797",
+                "date": "Tue, 02 Jul 2019 18:56:05 GMT"
+            },
+            "RetryAttempts": 0
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_iam.py b/tests/test_iam.py
--- a/tests/test_iam.py
+++ b/tests/test_iam.py
@@ -1462,6 +1462,58 @@ def test_delete_role(self):
         self.assertEqual(len(resources), 1)
         self.assertRaises(ClientError, client.get_role, RoleName=resources[0]['RoleName'])
 
+    def test_set_policy_wildcard(self):
+        factory = self.replay_flight_data("test_set_policy_wildcard")
+        policy = self.load_policy(
+            {
+                'name': 'iam-force-delete-role',
+                'resource': 'iam-role',
+                'filters': [{'tag:Name': 'Pratyush'}],
+                "actions": [
+                    {
+                        "type": "set-policy",
+                        "state": "detached",
+                        "arn": "*",
+                    }
+                ]
+            },
+            session_factory=factory
+        )
+        resources = policy.run()
+        self.assertEqual(len(resources), 1)
+        client = factory().client("iam")
+        self.assertEqual(
+            len((client.list_attached_role_policies(RoleName=resources[0]['RoleName']))
+            ['AttachedPolicies']), 0)
+
+    def test_set_policy_validation_error(self):
+        self.assertRaises(
+            PolicyValidationError,
+            self.load_policy,
+            {
+                "name": "iam-policy-error",
+                "resource": "iam-role",
+                'filters': [{'tag:Name': 'Pratyush'}],
+                "actions": [{"type": "set-policy", "state": "attached", "arn": "*"}],
+            }
+        )
+
+    def test_force_delete_role(self):
+        factory = self.replay_flight_data("test_force_delete_role")
+        policy = self.load_policy(
+            {
+                'name': 'iam-force-delete-role',
+                'resource': 'iam-role',
+                'filters': [{'tag:Name': 'Pratyush'}],
+                "actions": [{"type": "delete", "force": True}],
+            },
+            session_factory=factory
+        )
+        resources = policy.run()
+        self.assertEqual(len(resources), 1)
+        client = factory().client("iam")
+        self.assertRaises(ClientError, client.get_role, RoleName=resources[0]['RoleName'])
+
     def test_delete_role_error(self):
         factory = self.replay_flight_data("test_delete_role_error")
         p = self.load_policy(