CFnでIAMRoleを作成する際にConditionのKeyにパラメータを設定する書き方

## はじめに

普段、CFnでAWSリソースをコード管理することが多いのですが、IAMロールを作成する際にConditionを利用して厳密なアクセス制限が必要なケースがあります。Condition内には[このドキュメント](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html)にあるように様々な条件演算子を利用して、KeyValueの形式で条件を指定することができます。
今回はこのKeyの部分にCFnのパラメータを利用したいと考えたものの一筋縄でいかず、ふとした拍子に降ってきた書き方でうまく書けたので記事にしました。
小ネタ感強めの内容です。


## 実際にぶつかった問題

私は[このドキュメント](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/create-service-account-iam-policy-and-role.html)にある、EKSのServiceAccount用のIAMロールを作成する際にこの問題にぶつかりました。
例では以下のようなIAMロールを作成する手順となっています。

```bash
read -r -d '' TRUST_RELATIONSHIP <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:<my-namespace>:<my-service-account>"
        }
      }
    }
  ]
}
EOF
echo "${TRUST_RELATIONSHIP}" > trust.json
```

ドキュメントでは、CLIでIAMロールを作成する手順が紹介されていますが、これをCFnで管理したいと考えました。
しかし、冒頭でも紹介した通り、以下の部分をCFnで再現する上手い書き方が思いつかなかったのです。

```json
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:<my-namespace>:<my-service-account>"
        }
      }
```

直感的には以下のように書きたくなります。しかしこの方法だと、StringEqualsが不正であるというエラーが発生し、IAMロールの作成に失敗します。

```yaml
  IAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'role-name'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Federated: !Sub 'arn:aws:iam::${AWS::AccountId}:oidc-provider/${OIDCProvider}'
            Action: 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:
                !Sub '${OIDCProvider}:sub: system:serviceaccount:${Namespace}:${ServiceAccount}'

```

OidcProviderを変数化せずにベタ書きするのは複数の環境に対応させる必要があるため、手段として取れませんでした。
サポートケースを起票して何か良い書き方がないか確認したところ、Lambda-backed カスタムリソースを利用すれば書ける旨をご教示いただき、確かに実現できそうではありましたが、このためだけに決して短くはないコードを書くのは純粋に手間ですし、CFnの可読性も損ねるのであまり前向きな気持ちになれませんでした。


## 降ってきた書き方

特にきっかけもなく、「あ、そういえばSub使えばいけるな」と閃きがありました。その時に思いついた書き方は以下のものです。

```yaml
  IAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'role-name'
      AssumeRolePolicyDocument: !Sub |
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/${OIDCProvider}"
              },
              "Action": "sts:AssumeRoleWithWebIdentity",
              "Condition": {
                "StringEquals": {
                  "${OIDCProvider}:sub": "system:serviceaccount:${Namespace}:${ServiceAccount}"
                }
              }
            }
          ]
        }
```

AssumeRolePolicyDocumentはJSON形式で指定できるので、これを丸ごとSubに渡してあげることで、YAML形式では実現できなかったConditionのKeyにパラメータを埋め込むということが実現できます。


## さいごに

よくみたら[knowledge-centerのブログ](https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-oidc-url-eks-cluster/)でこの記載が紹介されていました。この例ではLamdba-backed カスタムリソースが使われていたため、欲しい情報ではないと思い、よくみていなかったのが仇となり、気づくのが遅れてしまいました。反省です。
YAMLのままでも書けたよ、もっといい書き方あるよなどあれば、教えて頂けると幸いです。