@ -27,6 +27,14 @@ options:
description :
- Provide a description of the new role
version_added : " 2.5 "
boundary :
description :
- Add the ARN of an IAM managed policy to restrict the permissions this role can pass on to IAM roles / users that it creates .
- Boundaries cannot be set on Instance Profiles , so if this option is specified then C ( create_instance_profile ) must be false .
- This is intended for roles / users that have permissions to create new IAM objects .
- For more information on boundaries , see U ( https : / / docs . aws . amazon . com / IAM / latest / UserGuide / access_policies_boundaries . html )
aliases : [ boundary_policy_arn ]
version_added : " 2.7 "
assume_role_policy_document :
description :
- The trust relationship policy document that grants an entity permission to assume the role .
@ -150,9 +158,9 @@ iam_role:
'''
from ansible . module_utils . _text import to_native
from ansible . module_utils . basic import Ansible Module
from ansible . module_utils . aws. core import AnsibleAWS Module
from ansible . module_utils . ec2 import camel_dict_to_snake_dict , ec2_argument_spec , get_aws_connection_info , boto3_conn , sort_json_policy_dict
from ansible . module_utils . ec2 import HAS_BOTO3, AWSRetry
from ansible . module_utils . ec2 import AWSRetry
import json
import traceback
@ -160,7 +168,7 @@ import traceback
try :
from botocore . exceptions import ClientError , BotoCoreError
except ImportError :
pass # caught by imported HAS_BOTO3
pass # caught by AnsibleAWSModule
def compare_assume_role_policy_doc ( current_policy_doc , new_policy_doc ) :
@ -218,13 +226,14 @@ def remove_policies(connection, module, policies_to_remove, params):
def create_or_update_role ( connection , module ) :
params = dict ( )
params [ ' Path ' ] = module . params . get ( ' path ' )
params [ ' RoleName ' ] = module . params . get ( ' name ' )
params [ ' AssumeRolePolicyDocument ' ] = module . params . get ( ' assume_role_policy_document ' )
if module . params . get ( ' description ' ) is not None :
params [ ' Description ' ] = module . params . get ( ' description ' )
if module . params . get ( ' boundary ' ) is not None :
params [ ' PermissionsBoundary ' ] = module . params . get ( ' boundary ' )
managed_policies = module . params . get ( ' managed_policy ' )
create_instance_profile = module . params . get ( ' create_instance_profile ' )
if managed_policies :
@ -339,6 +348,29 @@ def create_or_update_role(connection, module):
module . fail_json ( msg = " Unable to update description for role {0} : {1} " . format ( params [ ' RoleName ' ] , to_native ( e ) ) ,
exception = traceback . format_exc ( ) )
# Check if permission boundary needs update
if not role . get ( ' MadeInCheckMode ' ) and (
( role . get ( ' PermissionsBoundary ' ) or { } ) . get ( ' PermissionsBoundaryArn ' ) or
params . get ( ' PermissionsBoundary ' ) is not None ) :
# the existing role has a boundary
if module . params . get ( ' boundary ' ) is None :
pass
elif module . params . get ( ' boundary ' ) == ' ' :
if ( role . get ( ' PermissionsBoundary ' ) or { } ) . get ( ' PermissionsBoundaryArn ' ) :
try :
if not module . check_mode :
connection . delete_role_permissions_boundary ( RoleName = params [ ' RoleName ' ] )
changed = True
except ( BotoCoreError , ClientError ) as e :
module . fail_json_aws ( e , msg = " Unable to remove permission boundary for role {0} : {1} " . format ( params [ ' RoleName ' ] , to_native ( e ) ) )
elif ( role . get ( ' PermissionsBoundary ' ) or { } ) . get ( ' PermissionsBoundaryArn ' ) != params [ ' PermissionsBoundary ' ] :
try :
if not module . check_mode :
connection . put_role_permissions_boundary ( RoleName = params [ ' RoleName ' ] , PermissionsBoundary = params [ ' PermissionsBoundary ' ] )
changed = True
except ( BotoCoreError , ClientError ) as e :
module . fail_json_aws ( e , msg = " Unable to update permission boundary for role {0} : {1} " . format ( params [ ' RoleName ' ] , to_native ( e ) ) )
# Get the role again
if not role . get ( ' MadeInCheckMode ' , False ) :
role = get_role ( connection , module , params [ ' RoleName ' ] )
@ -352,7 +384,9 @@ def destroy_role(connection, module):
params = dict ( )
params [ ' RoleName ' ] = module . params . get ( ' name ' )
if get_role ( connection , module , params [ ' RoleName ' ] ) :
role = get_role ( connection , module , params [ ' RoleName ' ] )
if role :
# We need to remove any instance profiles from the role before we delete it
try :
@ -364,6 +398,12 @@ def destroy_role(connection, module):
module . fail_json ( msg = " Unable to list instance profiles for role {0} : {1} " . format ( params [ ' RoleName ' ] , to_native ( e ) ) ,
exception = traceback . format_exc ( ) )
if role . get ( ' PermissionsBoundary ' ) is not None :
try :
connection . delete_role_permissions_boundary ( RoleName = params [ ' RoleName ' ] )
except ( ClientError , BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Could not delete role permission boundary on role {0} : {1} " . format ( params [ ' RoleName ' ] , e ) )
# Now remove the role from the instance profile(s)
for profile in instance_profiles :
try :
@ -441,30 +481,28 @@ def get_attached_policy_list(connection, module, name):
def main ( ) :
argument_spec = ec2_argument_spec ( )
argument_spec . update (
dict (
name = dict ( type = ' str ' , required = True ) ,
path = dict ( type = ' str ' , default = " / " ) ,
assume_role_policy_document = dict ( type = ' json ' ) ,
managed_policy = dict ( type = ' list ' , aliases = [ ' managed_policies ' ] ) ,
state = dict ( type = ' str ' , choices = [ ' present ' , ' absent ' ] , default = ' present ' ) ,
description = dict ( type = ' str ' ) ,
create_instance_profile = dict ( type = ' bool ' , default = True ) ,
purge_policies = dict ( type = ' bool ' , default = True ) ,
)
argument_spec = dict (
name = dict ( type = ' str ' , required = True ) ,
path = dict ( type = ' str ' , default = " / " ) ,
assume_role_policy_document = dict ( type = ' json ' ) ,
managed_policy = dict ( type = ' list ' , aliases = [ ' managed_policies ' ] ) ,
state = dict ( type = ' str ' , choices = [ ' present ' , ' absent ' ] , default = ' present ' ) ,
description = dict ( type = ' str ' ) ,
boundary = dict ( type = ' str ' , aliases = [ ' boundary_policy_arn ' ] ) ,
create_instance_profile = dict ( type = ' bool ' , default = True ) ,
purge_policies = dict ( type = ' bool ' , default = True ) ,
)
module = AnsibleAWSModule ( argument_spec = argument_spec ,
required_if = [ ( ' state ' , ' present ' , [ ' assume_role_policy_document ' ] ) ] ,
supports_check_mode = True )
module = AnsibleModule ( argument_spec = argument_spec ,
required_if = [ ( ' state ' , ' present ' , [ ' assume_role_policy_document ' ] ) ] ,
supports_check_mode = True )
if not HAS_BOTO3 :
module . fail_json ( msg = ' boto3 required for this module ' )
region , ec2_url , aws_connect_params = get_aws_connection_info ( module , boto3 = True )
if module . params . get ( ' boundary ' ) and module . params . get ( ' create_instance_profile ' ) :
module . fail_json ( msg = " When using a boundary policy, `create_instance_profile` must be set to `false`. " )
if module . params . get ( ' boundary ' ) is not None and not module . botocore_at_least ( ' 1.10.57 ' ) :
module . fail_json ( msg = " When using a boundary policy, botocore must be at least v1.10.57. "
" Current versions: boto3- {boto3_version} botocore- {botocore_version} " . format ( * * module . _gather_versions ( ) ) )
connection = boto3_conn( module , conn_type = ' client ' , resource = ' iam ' , region = region , endpoint = ec2_url , * * aws_connect_params )
connection = module . client ( ' iam ' )
state = module . params . get ( " state " )