【AWS CDK】CDKメリット IAM Grant Methodが熱い

## はじめに
AWS CDKにおいて個人的に「これいいよね」、「これ便利だよね」ってものをご紹介できればと思います。
「あ、これ使ってみたいかも」とか思われたらAWS CDKのご利用をご検討頂ければと思います。
今回は「IAM Grant Method」です。
※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。

## IAM Grant Methodとは 
CDKで用意しているシナリオに沿って、対象のRoleに簡単にPolicyを投入出来ます。
ようは、このリソースをこんな感じで触りたいというシナリオに沿ったPolicyを自動生成し、Roleにアタッチするためのものになります。
言葉だけだと分かり辛いので、ユースケースに沿ってご紹介させて頂きます。

## ユースケース
### ユースケースイメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635217/4960f7b0-8735-9692-48f7-de7928d23f6d.png)


上記イメージのようにWebFargateからMainSQSにメッセージSend(Producer)、BatchFargateはMainSQSからReceive(Consumer)としたい場合の構成を例にとります。

PolicyとRoleを整理すると以下のようになります。
### Policy
|Policy名  |許可するAction  |Resource  |
|---|---|---|
|WebFargate-Policy  |sqs:SendMessage, sqs:GetQueueAttributes, sqs:GetQueueUrl  |MainSQS  |
|BatchFargate-Policy  |sqs:ChangeMessageVisibility, sqs:DeleteMessage, sqs:ReceiveMessage, sqs:GetQueueAttributes, sqs:GetQueueUrl  |MainSQS  |

### Role
|Role名  |アタッチするリソース  |アタッチするPolicy  |
|---|---|---|
|WebFargate-Role  |WebFargate  |WebFargate-Policy  |
|BatchFargate-Role  |BatchFargate  |BatchFargate-Policy  |

どのようなDeploy手段であれPolicyを検討するために処理の流れを意識して、上記のような情報整理をされると思います。

## ユースケースに対してCloudFormationでの実装イメージ
### MainSQS
~~~json:usecase-stack.template.json
  "mainSQS": {
   "Type": "AWS::SQS::Queue",
   "Properties": {
    "QueueName": "MainSQS"
   },
  }
~~~

### WebFargateからSQSにメッセージSend(Producer)の場合
~~~json:usecase-stack.template.json
  "webFargateRole": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "RoleName": "WebFargateRole"
   }
  },
  "webFargateRoleDefaultPolicy": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": [
        "sqs:GetQueueAttributes",
        "sqs:GetQueueUrl",
        "sqs:SendMessage"
       ],
       "Effect": "Allow",
       "Resource": {
        "Fn::GetAtt": [
         "mainSQS",
         "Arn"
        ]
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "webFargateRoleDefaultPolicy",
    "Roles": [
     {
      "Ref": "webFargateRole"
     }
    ]
   }
  },
  ~~~

### BatchFargateはSQSからReceive(Consumer）の場合
~~~json:usecase-stack.template.json
  "batchFargateRole": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "RoleName": "BatchFargateRole"
   }
  },
  "batchFargateRoleDefaultPolicy": {
   "Type": "AWS::IAM::Policy",
   "Properties": {
    "PolicyDocument": {
     "Statement": [
      {
       "Action": [
        "sqs:ChangeMessageVisibility",
        "sqs:DeleteMessage",
        "sqs:GetQueueAttributes",
        "sqs:GetQueueUrl",
        "sqs:ReceiveMessage"
       ],
       "Effect": "Allow",
       "Resource": {
        "Fn::GetAtt": [
         "mainSQS",
         "Arn"
        ]
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "PolicyName": "batchFargateRoleDefaultPolicy",
    "Roles": [
     {
      "Ref": "batchFargateRole"
     }
    ]
   }
  },
  ~~~

上記イメージのようになかなか、書く量が多いですね。めんどくさいですね。
これに対して、「IAM Grant Method」を使うとすごくシンプルかつサクッと書けてしまいます。


## ユースケースに対してAWS CDKでの実装イメージ
「IAM Grant Method」には様々なシナリオが用意されています。
選んだシナリオに沿って、Policyが構成され、対象にアタッチされます。
### MainSQS
~~~typescript:usecase-role.ts
    const mainQueue = new sqs.Queue(this, 'SQS', {
      queueName: "MainSQS",
      encryption: sqs.QueueEncryption.KMS,
      encryptionMasterKey: key
    });
~~~

### WebFargateからSQSにメッセージSend(Producer)の場合

~~~typescript:usecase-role.ts
    const webFargateRole = new iam.Role(this, `webFargateRole`, {   // Role生成
      assumedBy: new iam.ServicePrincipal(`ecs-tasks.amazonaws.com`),
      roleName: "WebFargateRole"
    })
    
    mainQueue.grantSendMessages(webFargateRole);  // Grant Method
~~~

上段でWebFargate用のRoleを生成しています。
そのうえで、WebFargateはMainSQSに対してMesseageをSendするので、SQSが持つ「grantSendMessages Method」を利用しています。
MainSQSの「grantSendMessages Method」を利用することにより、MainSQSに対する以下Actionを許可する設定が自動で、WebFargate用のRoleのDefalutPolicyに与えられます。
* sqs:GetQueueAttributes
* sqs:GetQueueUrl
* sqs:SendMessage

ようは、上段のCloudFormationと同レベルのDefalutPolicyが生成され、Roleにアタッチされます。

### BatchFargateはSQSからReceive(Consumer）の場合
~~~typescript:usecase-role.ts
    const batchFargateRole = new iam.Role(this, `batchFargateRole`, {  // Role生成
      assumedBy: new iam.ServicePrincipal(`ecs-tasks.amazonaws.com`),
      roleName: "BatchFargateRole"
    })
    
    mainQueue.grantConsumeMessages(batchFargateRole);  // Grant Method
~~~

上段でBatchargate用のRoleを生成しています。
そのうえで、BatchFargateはMainSQSに対して、MessageをReceiveするので、SQSが持つ[grantConsumeMessages Method」を利用しています。
MainSQSの「grantConsumeMessages Method」を利用することにより、MainSQSに対する以下Actionを許可する設定が自動で、BatchFargate用のRolのDefalutPolicyに与えられます。
* sqs:ChangeMessageVisibility
* sqs:DeleteMessage
* sqs:GetQueueAttributes
* sqs:GetQueueUrl
* sqs:ReceiveMessage

ようは、上段のCloudFormationと同レベルのDefalutPolicyが生成され、Roleにアタッチされます。



## 利用する際のポイント
AWS CDKのAPPを書く時は、基本的にベストプラクティスに従って使い勝手を考慮し、Constructsコードに分割し、Stackコードでつなげていく構成を取ると思います。
今回のユースケースイメージでは、「MainSQS」、「WebFargate関連リソース」、「BatchFargate関連リソース」全て同一Constructsコードで書いてしまっていますが、実際は以下のように別Constructsコードで書くことが多いと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635217/75b93f15-0108-b4e2-09fb-06f3e9cf3e2a.png)


### Stackコードイメージ
 ~~~typescript:usecase-connections-stack.ts
~~略~~
    // NW Constructsコードのリソース生成
    const nw = new nw(this, "usercase-nw", {
    });
    // MainSQS Constructsコードのリソース生成
    const sqs = new sqs(this, "usecase-sqs", {
    });
    // WebFargate Constructsコードのリソース生成
    const webfargate = new webfargate(this, "usecase-webfargate", {
      vpc: nw.vpc,
      mainsqs: sqs.mainsqs,
    });
    // BatchFargate Constructsコードのリソース生成
    const batchfargate = new batchfargate(this, "usecase-batchfargate", {
      vpc: nw.vpc,
      mainsqs: sqs.mainsqs,
    });
~~~~

上記Stackコードイメージの場合、MainSQSが先に生成され、その後「WebFargate関連リソース」、「BatchFargate関連リソース」(「それぞれのFargateにアタッチされるIAM Role(TaskRole)を含む)が生成されます。
 IAM Grant Methodを利用する場合、シナリオに沿った権限を与えたいリソースとRoleそれぞれが必要になります。
よって、今回の場合「MainSQS」のあとに生成されるConstructsでないと IAM Grant Methodは利用できません。
このようにStackコードで生成される後段で書く必要があり、Constructsコード分割、Stackコード構成検討の際に意識しておく必要があります。

また、複数人で1APPを書くときは、「基本的にRoleを生成する側でIAM Grant Methodを書く」等決めておくとスムーズに進みます。

## まとめ
以上より、IAM Grant Metodを用いることにより、シナリオに沿ったPolicy設定が簡単にできることをご紹介させて頂きました。
付随するメリットですが、このリソースはどのリソースに対してどんなことするの？ということさえ整理しておけばいいので、概要設計的なイメージだけで十分です。
つまり、ユースケースイメージのようなイメージが処理フロー毎にあるだけで使えます。
検討のためのPolicyやRoleの整理は必要ありません。
正直これだけで嬉しいですよね。
ご興味を持って頂けたらAWS CDKでDeployを試してみてください。

しかし、CDKが用意したシナリオに沿っている必要があります。つまり、「シナリオで定義されている許可Actionでは足りない」、「余計なActionは許可したくない」というケースではちょっと使いづらいです。
そういった課題に対応するためにシナリオが定義されていない「grant Method」が用意されています。
このMethodでは、許可するActionをこのMethodに渡すとそのActionを許可する形でPolicyが生成されます。

また、お遊びで、エスケープハッチを活用すれば、若干力技ですが、対応可能です。
その力技に関しては以下別記事でご紹介しておりますので、ご興味あれば、合わせてご参照ください。
[【AWS CDK】エスケープハッチ IAM Policyの上書](https://qiita.com/akwayne/items/2b44d348fca06a65791a "【AWS CDK】エスケープハッチ IAM Policyの上書")

※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。