How to share your organizations ID
Tags: aws, cloudformation, organizations, cloud
Published: 2023-11-12 14:57:48

As a cloud engineer you sometimes find yourself in a [rabbit hole](https://en.wikipedia.org/wiki/Down_the_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](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-organizations-resourcepolicy.html) 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](https://github.com/aws-cloudformation/cfn-language-discussion/issues/57) 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)*
```yaml
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.

```yaml
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)*
```yaml
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
          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.