Reputation: 71
I am writing a Cloudformation template with a single EC2 instance and an EBS volume. I attach the volume later on at some point when the machine is created using Powershell script. It works when I put wildcard '*' in policy statement resource however I want to limit the access to one instance and one ebs volume. With EBS volume it's easy I can just refer it in the template and it is created before the role but with instance the problem is that instance requires the role to be created first but also to be able to create the instance we need to create the role first. What's a good way of resolving this kind of circular dependency?
Here is my template:
Resources:
InstanceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: AttachVolume
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'ec2:AttachVolume'
Resource:
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':instance/*'
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':volume/'
- !Ref DataVolume
InstanceProfile:
Type: 'AWS::IAM::InstanceProfile'
Properties:
Roles:
- !Ref InstanceRole
InstanceProfileName: InstanceProfile
Instance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref AMI
InstanceType: !Ref InstanceType
IamInstanceProfile: !Ref InstanceProfile
KeyName: ec2key
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeType: gp2
DeleteOnTermination: 'true'
VolumeSize: '30'
Tags:
- Key: Name
Value: MyInstance
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref SGId
UserData: !Base64
'Fn::Join':
- ''
- - |
<script>
- 'cfn-init.exe -v -c config -s '
- !Ref 'AWS::StackId'
- ' -r Instance'
- ' --region '
- !Ref 'AWS::Region'
- |+
- |
</script>
DataVolume:
Type: "AWS::EC2::Volume"
Properties:
AvailabilityZone: !GetAtt
- Instance
- AvailabilityZone
Size: "100"
Tags:
- Key: Name
Value: InstanceExtraVolume
Upvotes: 3
Views: 2100
Reputation: 9234
In your particular example, you have the following dependency chain: InstanceRole -> DataVolume -> Instance -> InstanceProfile -> InstanceRole
In general, when your Role depends on your Resources and your Resources depend on your Role, this is where the AWS::IAM::Policy resource type is useful. This basically decouples the particular policy on the IAM Role from being resolved at the same time as the IAM Policy itself.
To do this, you would take your InstanceRole and split it into an InstanceRole and an InstanceRolePolicy
Resources:
InstanceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: InstanceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- 'sts:AssumeRole'
Path: /
InstanceRolePolicy:
Type: 'AWS::IAM::Policy'
Properties:
Roles:
- !Ref InstanceRole
PolicyName: AttachVolume
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'ec2:AttachVolume'
Resource:
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':instance/*'
- !Join
- ''
- - 'arn:aws:ec2:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':volume/'
- !Ref DataVolume
With that, the InstanceRolePolicy depends on the InstanceRole and DataVolume, but the InstanceRole doesn't depend on anything, so the DataVolume -> Instance -> InstanceProfile -> InstanceRole
chain can resolve.
Upvotes: 2
Reputation: 78860
One common solution to circular dependencies is to do this is multiple steps: create the stack with minimal resources, then modify the template and update the stack.
So, v1 of your template creates just the basic dependent resource and in v2, you modify the template to add the depending resource and modify the original dependent resource at the same time. Then do a stack update.
Also, see more ideas.
Upvotes: 0