As a cloud engineer you sometimes find yourself in a rabbit hole. Typically you fall from one problem into the next, and then the next and so on. Personally I quite enjoy the experience because you never know what you’ll learn. The only real problem is that I sometimes forget why I got there in the first place. This is such an occasion.
Here is my reconstruction of how I think I got here: I was trying to implement SSM parameter replication. At a certain point I needed to navigate the Organization tree. Then I found out that I have to create a resource policy on the Organization in the management account. And – if you want to do that using Infrastructure as Code – you can use the AWS::Organizations::ResourcePolicy
resource type for that. The AWS documentation mentions the attributes of this resource includes its arn
and the arn
has the format arn:aws:organizations::111111111111:resourcepolicy/o-exampleorgid/rp-examplepolicyid111
. And because this includes the o-exampleorgid
which is a good candidate for such replication.
Then I stumbled over a github issue where a feature is discussed for CloudFormation to have an additional pseudo parameter for the Id of the Organization. I decided to dig a little further. Although rabbit holes can be quite deep your are never alone…
In the github issue they are asking for an {AWS::OrgId}
pseudo parameter to be implemented. Having seen the ResourcePolicy
s attribute Arn
I thought I had a perfect solution for the stated problem so I decided to share that as a comment on the github issue. Here is my initial take at solving the problem:
(for readers only interested in the final solution and not so much for the narrative: skip this one and scroll down)
Resources:
OrganizationsResourcePolicy:
Type: AWS::Organizations::ResourcePolicy
Properties:
Content:
Version: 2012-10-17
Statement:
- Sid: AllowReadonlyNavigateOrganization
Effect: Allow
Principal:
AWS: '*'
Action:
- organizations:ListRoots
- organizations:ListOrganizationalUnitsForParent
- organizations:ListAccountsForParent
Resource: '*'
Condition:
StringEquals:
aws:PrincipalOrgID": "${aws:PrincipalOrgID}"
OrganizationParameter:
Type: AWS::SSM::Parameter
Properties:
Name: /organizations/id
Value: !Select
- 1
- !Split
- /
- !GetAtt OrganizationsResourcePolicy.Arn
Type: String
With a nifty Select
on a Split
intrinsic I yank out the Id
of the organization and create an SSM parameter of it. If you bootstrap your management account with this you’ll have this ssm parameter available for use as parameter default or dynamic parameter in CloudFormation. But… sadly… only in the same region and only in this account. But maybe my upcoming SSM parameter replication feature will save the day! Well… maybe there is an easier solution.
While reading the documentation I saw that AWS::Organizations::Organization
is a better candidate to use when solving this because apart from a direct Id
attribute it also has the RootId
attribute which can be very useful. But this resource cannot be simply used when you already have an Organization in your account. (See how we fell into the next problem here?) To solve this you have to import
the Organization resource. To do that you need to create a template where it is the only resource, add a DeletionPolicy
and use the import feature.
Resources:
Organization:
DeletionPolicy: Retain
Type: AWS::Organizations::Organization
Properties:
FeatureSet: ALL
Once you have done the initial import you can add more resources and update
the stack. In this stack you can do all you want with all the attributes the Organization has: Arn, Id, ManagementAccountArn, ManagementAccountEmail, ManagementAccountId and RootId
.
So the goal is to let other accounts/regions know about the Id
. Going through the list of CloudFormation resources I also encountered StackSet
and though that might be a good candidate to do the distributing. After fiddling around a bit I came up with this:
(For the skipping reader you can’t use this template directly. You’ll need to start with importing the template right above this one)
Parameters:
Regions:
Type: CommaDelimitedList
Default: us-east-1,eu-central-1,eu-west-1,eu-west-2,eu-west-3
Resources:
Organization:
DeletionPolicy: Retain
Type: AWS::Organizations::Organization
Properties:
FeatureSet: ALL
ParameterStackSet:
Type: AWS::CloudFormation::StackSet
Properties:
StackSetName: OrganizationParameterStack
PermissionModel: SERVICE_MANAGED
AutoDeployment:
Enabled: True
RetainStacksOnAccountRemoval: True
ManagedExecution:
Active: true
StackInstancesGroup:
- DeploymentTargets:
OrganizationalUnitIds:
- !GetAtt Organization.RootId
Accounts:
- !Sub '${AWS::AccountId}'
Regions: !Ref Regions
TemplateBody: !Sub
- |
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"AtLeastOneResource": {
"Type": "AWS::CloudFormation::WaitConditionHandle"
}
},
"Outputs": {
"OrganizationId": {
"Description": "Id of the Organization",
"Value": "${OrganizationId}",
"Export": {
"Name": "organizationId"
}
}
}
}
- OrganizationId: !GetAtt Organization.Id
It works!!
I decided to go for an output
instead of a ssm parameter. Because outputs are a little stricter (better?) You can’t change or remove the while they are imported. (This is a feature I am also looking for my ssm parameter replication feature.) Internally CloudFormation does reference counting on them.
A problem with this solution is that the StackSet will not be distributed to the management account itself. Maybe you can get it to do that if you are not using SERVICE_MANAGED
but I refused to fall further down 😉
Note: I did experimented Fn::ToJsonString
and have it all in yaml
which works but you also need the extra Transform: 'AWS::LanguageExtensions'
. Which somehow needs all the IAM warning checkboxes with every deploy. So I decided not to use that. No more time for additional holes to fall into because it is almost time to cook dinner.
You could add many parameters here and they would be shared with the whole organization across regions. Is my ssm parameter replicator
feature obsolete now? I think not because this solution still cannot handle my use case: create resource in one account, share an attribute of that resource across (part) of the organization, have the use of it reference counted, handle race conditions correctly. So I guess I will still be stuck implementing it if I can keep out of rabbit holes long enough.