IoT Device Managementのグループを利用してポリシーを管理する

#はじめに
IoT Device Managementの静的グループにはポリシーをアタッチすることができます。
グループを利用することで、モノのポリシー管理が楽になります。
> グループにポリシーをアタッチおよびデタッチすると、AWS IoT オペレーションのセキュリティをいくつかの重要な方法で強化できます。ポリシーに証明書をアタッチしてからモノにアタッチするような､デバイスごとの方法は時間がかかり、多数のデバイスにわたってポリシーを迅速に更新または変更することが困難です。モノのグループにポリシーをアタッチすると、証明書をモノに回す際のステップが節約されます。また、ポリシーはグループメンバーシップを変更すると動的に適用されるため、デバイスがグループのメンバーシップを変更するたびに複雑なアクセス許可セットを再作成する必要はありません。
引用元：[モノの静的グループ](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/thing-groups.html)

今回は、グループにポリシーをアタッチした際の挙動を確認しました。
#1　事前準備
今回の検証用にグループとポリシーを用意します。
##1-1　グループの用意
今回は以下のパターンを検証するため、4階層のグループを用意します。

* モノが所属しているグループにポリシーをアタッチする
* モノが所属しているグループの1階層上の親グループにポリシーをアタッチする
* モノが所属しているグループの2階層上のルートグループにポリシーをアタッチする
* モノが所属しているグループの2階層上の親グループ（ルートではない）にポリシーをアタッチする

1.IoT Coreのマネジメントコンソールで左のメニューから 管理 > モノのグループ の順にクリック
2.「作成」をクリック
3.「モノのグループを作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/9d5b0ab7-bc01-431a-6063-ea319939409f.png)
※動的なモノのグループにはポリシーをアタッチできない
　参考：[モノの動的グループ](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/dynamic-thing-groups.html)
4.任意のグループ名を入力して「モノのグループを作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/64d65d07-2e9c-1ca9-2086-a3a204bc7c7d.png)
5.作成されたモノのグループの詳細画面で グループ > グループの追加 の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/6b26a85d-d3ef-6e40-2f18-8249cf633545.png)
6.手順4同様のグループ追加画面が表示されるので、任意のグループ名を入力して「モノのグループを作成」をクリック
※親グループの選択箇所に、グループの追加 をクリックしたグループが選択されている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/91bbc3a4-af2d-1a12-c672-0c7f8940f6f3.png)
7.手順5~6を繰り返して、子グループをさらに2階層下まで作成する
※今回は グループ / Test / Test2 / Test2-1 / Test2-1-1 という形で4階層作成した
##1-2　ポリシーの作成
今回はAllowとDenyのポリシーが両方適用された場合の挙動も確認したいので、ポリシーを2つ用意します。
あくまで挙動確認なので、権限は*で大きく付与しています。

1.IoT Coreのマネジメントコンソールで左のメニューから 安全性 > ポリシー の順にクリック
2.「作成」をクリック
3.以下の通り入力・選択して「作成」をクリック
※すべてのIoT Core操作をAllowするポリシー

* 名前：※任意の名前
* アクション：iot:*
* リソースARN：*
* 効果：許可

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/d83dbfb0-a630-af3a-7243-2231732a6304.png)
4.手順1~3を繰り返し、すべてのIoT Core操作をDenyするポリシーを作成する
※手順3の「効果」は拒否をチェック

なお、今回作成した両ポリシーはJSON形式だと以下の通りです。

```json:group-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:*",
      "Resource": "*"
    }
  ]
}
```
```json:deny-group-policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "iot:*",
      "Resource": "*"
    }
  ]
}
```
#2　検証
グループにアタッチしたポリシーが働くことを確認します。
今回は、AWS IoT Device SDK for Pythonのサンプル `basicPubSub.py` を利用し、デバイスからのメッセージがエラーなく届くかどうかを通してポリシーの有効性を確かめます。

なお、`basicPubSub.py` の利用に関しては以下の記事をご参照ください。
参考：[AWS IoT Device SDK for Pythonを使ってRaspberryPiとAWS IoTをつないでみる](https://dev.classmethod.jp/articles/aws-iot-device-sdk-for-python-raspberrypi-aws-iot/)
##2-1　モノの証明書へのポリシーアタッチ
グループへアタッチしての検証前に、用意したポリシーをモノの証明書にアタッチ/デタッチすることで、通信できる/できないと変わることを確認します。

1.IoT Coreのマネジメントコンソールで左のメニューから　管理 > モノ の順にクリック
2.今回対象とするモノの名前をクリック
3.セキュリティ > 証明書 の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/66726857-baba-30a9-611d-404a334c4eb6.png)
4.ポリシーをクリックし、アクション > ポリシーのアタッチ の順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/d1bf52d0-98b8-5647-a10b-d109a9819374.png)
5.先ほど作成したAllowのポリシーを選択して「アタッチ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/0c68dd64-13f3-b922-9e54-4e8d697d01dc.png)
6.IoT Coreのマネジメントコンソールで左のメニューから テストをクリック
7.トピックのフィルターに「sdk/test/Python」と入力して「サブスクライブ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/8d5e640b-b134-ba96-912c-84a2d5c5caaf.png)
8.対象のデバイスで以下のコマンドを実行し、手順7のテスト画面でメッセージをサブスクライブできることを確認する

```bash
#下記はbasicPubSub.pyを格納したディレクトリ上で実行している（Ctrl + C で停止）
python basicPubSub.py --endpoint [エンドポイントのURL] --rootCA [ルート証明書] --cert [モノの証明書] --key [秘密鍵] --clientId "[モノの名前]"

#コマンドが成功するとプロンプト上では以下の通りメッセージが表示される
2021-04-05 10:54:53,726 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
2021-04-05 10:54:58,705 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync publish...
2021-04-05 10:54:58,706 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Filling in custom puback (QoS>0) event callback...
2021-04-05 10:54:58,739 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [puback] event
2021-04-05 10:54:58,740 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [puback] event
2021-04-05 10:54:58,741 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
2021-04-05 10:54:58,742 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - This custom event callback is for pub/sub/unsub, removing it after invocation...
2021-04-05 10:54:58,760 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [message] event
2021-04-05 10:54:58,760 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [message] event
Received a new message:
b'{"message": "Hello World!", "sequence": 1}'
from topic:
sdk/test/Python
--------------
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/870cf9c4-48c5-07a3-1ed9-0b5e39b06822.png)
9.手順4の画面でポリシーをデタッチしてから再度コマンドを実行し、今度はメッセージの送信に失敗することを確認する

```bash
#以下のエラーメッセージが表示された
2021-04-05 10:55:45,969 - AWSIoTPythonSDK.core.protocol.mqtt_core - ERROR - Connect timed out
Traceback (most recent call last):
  File "C:\Users\xxxxx\Desktop\gg-test-003\basicPubSub.py", line 107, in <module>
    myAWSIoTMQTTClient.connect()
  File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python39\lib\site-packages\AWSIoTPythonSDK\MQTTLib.py", line 513, in connect
    return self._mqtt_core.connect(keepAliveIntervalSecond)
  File "C:\Users\xxxxx\AppData\Local\Programs\Python\Python39\lib\site-packages\AWSIoTPythonSDK\core\protocol\mqtt_core.py", line 199, in connect
    raise connectTimeoutException()
AWSIoTPythonSDK.exception.AWSIoTExceptions.connectTimeoutException
```
##2-2　モノが所属しているグループにポリシーをアタッチ
グループにモノを追加し、そのグループにポリシーをアタッチします。
そして、グループにアタッチしたポリシーによってメッセージの送信ができることを確認します。

今回は第3階層のグループにモノを追加し、ポリシーをアタッチします。
※グループ / Test / Test2 / Test2-1 / Test2-1-1 のうち、Test2-1にモノを追加し、ポリシーをアタッチ 

なお、先ほどの手順でモノの証明書からはポリシーがデタッチされている状況を前提としています。

1.IoT Coreのマネジメントコンソールで左のメニューから 管理 > モノのグループ の順にクリック
2.対象グループをクリック
※モノのグループ一覧にはルートグループしか表示されない
3.グループ > 第2階層のグループの順にクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/dbd61405-a952-65d4-2350-6c0ee5689a8e.png)
4.手順3同様に グループ > 第3階層のグループ（今回モノを追加するグループ）の順にクリック
5.モノ > モノの追加 の順にクリック
6.追加するモノを選択して「モノの追加」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/132ef063-334a-5d7a-6f88-fd49891fc9ba.png)
7.セキュリティ > 編集 の順にクリック
8.アタッチするポリシーを選択して「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/84bbd2f5-6916-643c-0aa3-2770366e786b.png)
ポリシーをアタッチすると以下の通り表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/321052c5-2229-d002-ce11-3aaf9e5a328d.png)
9.「2-1　モノの証明書へのポリシーアタッチ」で確認した際と同様にコマンドを実行し、メッセージを送信できることを確認する
##2-3　モノが所属しているグループの親グループにポリシーをアタッチ
今度はモノが所属しているグループの親グループ（1階層上）にポリシーをアタッチし、ポリシーが継承されることを確認します。
※グループ / Test / Test2 / Test2-1 / Test2-1-1 のうち、モノはTest2-1に追加したまま、ポリシーはデタッチ
　新たにTest2にポリシーをアタッチ

1.IoT Coreのマネジメントコンソールで左のメニューから 管理 > モノのグループ の順にクリック
2.先ほどポリシーをアタッチしたグループに移動する
3.セキュリティ > 編集 の順にクリック
4.選択しているポリシーの削除をクリックし、「保存」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/70817770-ea77-8eb7-73ff-1629bdcb37b6.png)
5.この時点でいったんコマンドを実行し、メッセージを送信できないことを確認する
6.親グループにポリシーをアタッチする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/0730338e-c5b9-b2c1-1bef-7b98ae3f0788.png)
継承したポリシーが表示されている
7.再度コマンドを実行し、メッセージを送信できることを確認する
##2-4　モノが所属しているグループと2階層以上離れた親グループにポリシーをアタッチ
先ほどはモノが所属しているグループの親グループ、つまり、1階層上のグループにポリシーをアタッチしました。
これが2階層以上離れた場合でもポリシーが継承できることを確認します。

手順は同じなので、ここまでの結果とあわせて、以下に確認した結果をまとめました。
2階層以上離れていても親グループからポリシーを継承していることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/a90d17d0-be60-206e-3c37-2204eb13f622.png)
##2-5　AllowとDenyのポリシーが混在する場合
継承したポリシー、所属しているグループのポリシー、モノの証明書にアタッチしたポリシーの間で、AllowとDenyが混在している場合の挙動を確認します。

なお、IAM公式ドキュメントではAllowとDenyについて以下の記述があります。
> 該当するポリシーに Deny ステートメントが含まれている場合、リクエストは明示的に拒否されます。リクエストに適用されるポリシーに Allow ステートメントと Deny ステートメントが含まれている場合は、Deny ステートメントが Allow ステートメントより優先されます。リクエストは明示的に拒否されます。
引用元：[ポリシーの評価論理](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_evaluation-logic.html)

手順は今までと同様なので、以下に確認した結果をまとめました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/e1aaaa71-a729-32ae-9207-971824582afa.png)
グループの階層関係、グループか証明書か、ということはいずれも関係なくDenyが優先されました。
ちなみに、Allow/Denyのポリシーが継承によっていずれもアタッチされている場合、グループの画面では以下のように表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1001693/d4b19565-e0da-8866-cce6-5c3692b24e03.png)
#3　おわりに
以上、グループによるポリシー管理を確認しました。
モノが増えてくると、個別にデバイスに証明書アタッチ→証明書にポリシーアタッチの流れは煩雑かつ抜け漏れも起きそうなので、グループを使ってうまく管理できるとよさそうです。