【AWS】CloudFormationでIAM RoleのPolicy定義に変数を用いる【小ネタ】

# はじめに
CloudFormationのResourceのPropertyの指定において、Typeが「Json」のものがいくつかあります。
IAM Roleの「AssumeRolePolicyDocument」や「Policies」などがこれに該当します。

しかしながら、IAM Entityに対して指定するPolicyドキュメントでCFnの関数を使いたいことが多く（PolicyドキュメントのResourceに、他のCFn Stackで作成したリソースを指定する、など）、JSONでは指定が難しいことも多々あります。

Typeが「Json」となっているProperyには、同等の内容をyamlとして定義して渡すことも可能です。
この仕様を利用して、PolicyドキュメントでCFn関数を指定することが可能です。

AWS::IAM::Role - AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-assumerolepolicydocument
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-policies

AWS::IAM::Role Policy - AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-policy.html#cfn-iam-policies-policydocument

# IAM Role定義サンプル
以下の2つのCloudFormationテンプレート定義は等価です。

## JSON形式でPolicyドキュメントを指定する

```yaml:
Resources:
  # AWS::IAM::Role - AWS CloudFormation
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
  SampleAdminRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": "arn:aws:iam::123456789012:root"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        }
      Description:
        Describe what you want
      Policies: 
        - PolicyName: AllowAllActions
          PolicyDocument:
            {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Action": "*",
                        "Resource": "*"
                    }
                ]
            }

```

## yaml形式でPolicyドキュメントを指定する

```yaml:

AWSTemplateFormatVersion: '2010-09-09'
Description: Describe what you want.

Parameters:
  # Define what you want.

Resources:
  # AWS::IAM::Role - AWS CloudFormation
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
  SampleAdminRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: "arn:aws:iam::123456789012:root"
            Action: "sts:AssumeRole"
      Description:
        Describe what you want
      Policies: 
        - PolicyName: AllowAllActions
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "*"
                Resource: "*"
```

# AssumeRolePolicyDocumentのPrincipalでCFn関数を使う
上記のように、TypeとしてJsonが指定されているPropertyをyamlに変換したことで、CFn関数を利用しやすくなります。
先ほどの例ですと、AssumeRoleする主体となるPrincipalのAWSアカウントIDを、Parameterとして渡したくなると思います。
この場合、以下のように!Sub関数を利用することが可能です。

```yaml:cfn-iam-role-policy-with-function.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Describe what you want.

Parameters:
  # Define what you want.
  PrincipalAwsAccountId:
    Description:
      An AWS account id will be a principal to invoke the AssumeRole API.
    Type: Number
    Default: 123456789012

Resources:
  # AWS::IAM::Role - AWS CloudFormation
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
  SampleAdminRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: !Sub arn:aws:iam::${PrincipalAwsAccountId}:root
            Action: "sts:AssumeRole"
      Description:
        Describe what you want
      Policies: 
        - PolicyName: AllowAllActions
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "*"
                Resource: "*"
```

Fn::Sub - AWS CloudFormation
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html

CFnスタック作成時に、下記のようにPrincipalAwsAccountIdパラメータを指定することで、任意のAWSアカウントIDに対応することができるようになります。

```
$ aws cloudformation create-stack \
  --stack-name iam-role-policy-with-function \
  --template-body file://cfn-iam-role-policy-with-function.yml \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameters ParameterKey=PrincipalAwsAccountId,ParameterValue=987654321098,UsePreviousValue=true

```

# おわりに
CDKが出たので、今後はCDKを使うことでこのような用途はProgrammaticに解決できるようになると思います。
しかしながら、いきなり全て移行することは難しいため、このようなテクニックはまだ暫く必要になるでしょう。