- Newest
- Most votes
- Most comments
There is no native mechanism to take existing role and existing policy and attach one to the other in CloudFormation. Since CFT is about creating resources, you need to be creating at least one of the two resources. You've probably seen this support question that covers the supported scenarios: How can I attach an IAM managed policy to an IAM role in AWS CloudFormation?
To accomplish what you want, you can create a lambda-backed custom resource passing in the ARN for the existing policy and role name with the function calling the IAM AttachRolePolicy API to do this.
Here is a working example I threw together. On stack delete, it will detach the policy that was attached. On stack update, it will detach the old policy from the old role and then attach the new policy to the new role allowing you to change the existing policy, existing role or both
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
ExistingPolicyArn:
Type: String
Default: arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
ExistingRoleName:
Type: String
Default: ExistingRole
Resources:
IamXtachPolicyRoleLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Condition: {}
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyDocument:
Statement:
- Action:
- iam:DetachRolePolicy
- iam:AttachRolePolicy
Effect: Allow
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
Version: 2012-10-17
PolicyName: aws-app-iam-xtach-policy
IamXtachPolicyRoleLambda:
DependsOn: IamXtachPolicyRoleLambdaRole
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Timeout: 30
Role: !GetAtt IamXtachPolicyRoleLambdaRole.Arn
Runtime: python3.8
Code:
ZipFile: |
import logging
import boto3
import cfnresponse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
def attach_policy(policyArn, roleName):
return iam.attach_role_policy(
PolicyArn=policyArn,
RoleName=roleName,
)
def detach_policy(policyArn, roleName):
return iam.detach_role_policy(
PolicyArn=policyArn,
RoleName=roleName,
)
try:
iam = boto3.client('iam')
existingPolicyArn = event['ResourceProperties'].get('ExistingPolicyArn')
existingRoleName = event['ResourceProperties'].get('ExistingRoleName')
if event['RequestType'] == 'Create':
response = attach_policy(existingPolicyArn, existingRoleName)
elif event['RequestType'] == 'Delete':
response = detach_policy(existingPolicyArn, existingRoleName)
elif event['RequestType'] == 'Update':
oldExistingPolicyArn = event['OldResourceProperties'].get('ExistingPolicyArn')
oldExistingRoleName = event['OldResourceProperties'].get('ExistingRoleName')
response = detach_policy(oldExistingPolicyArn, oldExistingRoleName)
response = attach_policy(existingPolicyArn, existingRoleName)
result = cfnresponse.SUCCESS
except Exception as error:
logger.exception(error)
response = {
'status': 500,
'error': {
'type': type(error).__name__,
'description': str(error),
},
}
result = cfnresponse.FAILED
finally:
cfnresponse.send(event, context, result, response, {})
return
AttachExistingPolicyToExistingRole:
DependsOn: IamXtachPolicyRoleLambda
Type: Custom::IamXtachPolicyRoleLambda
Properties:
ServiceToken: !GetAtt IamXtachPolicyRoleLambda.Arn
ExistingPolicyArn: !Ref ExistingPolicyArn
ExistingRoleName: !Ref ExistingRoleName
Here is another way that also shows how you can get a value from the user and then use that string to specify a policy and a permissions boundary (in my case I use the same value).
AWSTemplateFormatVersion: "2010-09-09"
Description: Example of adding a policy using a parameter
Parameters:
PermissionBoundaryPolicyArn:
Type: String
Description: (Optional) A policy arn for the account Permission Boundary policy, similar to arn:aws:iam::12345679012:policy/ProjAdminPolicy
Conditions:
AssociatePermissionBoundary:
!Not [!Equals [!Ref PermissionBoundaryPolicyArn, ""]]
# Define Resources that will be launched via this template
Resources:
# Example EC2 Server Instance Role Profile
UnixIamProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: /
Roles:
- !Ref UnixInstanceRole
# EC2 Server Instance Role that permits SSM Session Manager, S3 access, and has a permission boundary
UnixInstanceRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- ssm.amazonaws.com
- ec2.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
- !Sub arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess
- !If [
AssociatePermissionBoundary,
!Ref PermissionBoundaryPolicyArn,
!Ref "AWS::NoValue",
]
PermissionsBoundary:
!If [
AssociatePermissionBoundary,
!Ref PermissionBoundaryPolicyArn,
!Ref "AWS::NoValue",
]
Policies:
- PolicyName: CodeCommitReadOnlyAdditional
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codecommit:BatchDescribe*
- codecommit:EvaluatePullRequestApprovalRules
Resource:
- !Sub arn:${AWS::Partition}:codecommit:${AWS::Region}:${AWS::AccountId}:*
Tags:
- Key: Purpose
Value: Shows how existing policies can be added to a role
Relevant content
- Accepted Answer
- AWS OFFICIALUpdated a year ago
- AWS OFFICIALUpdated 2 months ago
- AWS OFFICIALUpdated 4 months ago
- AWS OFFICIALUpdated a year ago
Furthermore, you can ask for an ARN value via a parameter so that it can be changed at installation time. This technique is also useful for specifying the PermissionBoundary value, i.e.: PermissionsBoundary: !Ref PermissionBoundaryPolicyArn. See my detailed answer below.