shadowとwillとretainを活用してAWS IoT CoreとPubSubする②AWSリソースの構築

## この記事について

[shadowとwillとretainを活用してAWS IoT CoreとPubSubする①（全体説明）](https://qiita.com/Saito5656/items/0a9a4a1dcb9b267c81de)で示した全体構成の各論に入ります。

今回はラズパイGW broker <-> AWS IoT間のpubsubに必要となる図のAWSリソースを構築し証明書でラズパイとAWS IoTを紐づけます。

<img width= 700 src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/8430576c-10c1-fe44-b6be-2ae3ab205176.png>

## 環境(エッジ)  

### RaspberryPi  

```sh: cat /proc/cpuinfo
Revision        : c04170
Serial          : xxxxxxxxxxxxxxxx
Model           : Raspberry Pi 5 Model B Rev 1.0
```
### RaspberryPi OS Imager

* Device：RASPBERRY PI 5  
* OS：RASPBERRY PI OS(64-BIT)  
* Storage：microSDカード32GB  
* Wi-Fi使用  
* 公開鍵認証のみ使用  

### インストール

[これ](https://qiita.com/Saito5656/items/6cba1de67374ef2012cd)や[このへん](https://qiita.com/Saito5656/items/957ae69909973d1e814b)とほぼ同じものをインストールして使います。普段使いの環境です。

awscliとmqtt borokerをインストールします、boto3は無くてもいいです。

最低限欲しいのはこの辺りです。

```sh
sudo apt update  
sudo apt -y upgrade  

sudo apt install -y python3-dev python3-pip git unzip
sudo python3 -m pip install --upgrade pip setuptools wheel typing-extensions

sudo apt install -y mosquitto mosquitto-clients inotify-tools
pip3 install paho-mqtt boto3 awsiotsdk --upgrade

sudo apt install -y expect bc jq
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
```

python仮想環境を使わない場合は以下も必要です。

```
mkdir ~/.pip
echo "[global]" | tee -a ~/.pip/pip.conf
echo "break-system-packages = true" | tee -a ~/.pip/pip.conf
```

## 環境(AWS)

* IAM userをIoT開発を許可したuser groupに追加  
* またはSSO useをIoT開発を許可した許可セットに紐づけ  

します。

使っている方はデフォになっていると思われますが、推奨はsession tokenを取得してプロファイル指定による接続です。

詳細は省略、だと端折り過ぎな気もするので、概ね以下のようなpolicyで実行可能だと思います。iotは細かく縛るとjsonが長くなるのでFullAccess表記とさせてもらいました。

xx.xx.xx.xx/32は自分の端末をつないでいるGlobal ipアドレスです。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowPassRole",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::*:role/*",
            "Condition": {
                "StringLike": {
                    "iam:PassedToService": [
                        "iot.amazonaws.com",
                        "iam.amazonaws.com"
                    ]
                },
                "IpAddress": {
                    "aws:SourceIp": [
                        "xx.xx.xx.xx/32"
                    ]
                }
            }
        },
        {
            "Sid": "AllowPolicyRoleActions",
            "Effect": "Allow",
            "Action": [
                "iam:UpdateAssumeRolePolicy",
                "iam:UntagRole",
                "iam:TagRole",
                "iam:UpdateRoleDescription",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:AttachRolePolicy",
                "iam:PutRolePolicy",
                "iam:CreatePolicy",
                "iam:ListInstanceProfilesForRole",
                "iam:DetachRolePolicy",
                "iam:ListAttachedRolePolicies",
                "iam:DeleteRolePolicy",
                "iam:UpdateRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/*",
                "arn:aws:iam::*:policy/*"
            ],
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "xx.xx.xx.xx/32"
                    ]
                }
            }
        },
        {
            "Sid": "AllowAssumRole",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::*:role/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "xx.xx.xx.xx/32"
                    ]
                }
            }
        },
        {
            "Sid": "AllowIoTFullAccess",
            "Effect": "Allow",
            "Action": "iot:*",
            "Resource": "*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "xx.xx.xx.xx/32"
                    ]
                }
            }
        }
    ]
}
```

## AWSリソース構築  

本題です。AWSリソースをラズパイのコマンドラインからawscliで構築します。

default profileを使う前提で進めます。

事前準備としてcli操作で参照するparameterをファイルで準備しておきます。

```sh:parameters.conf
PROJECT_NAME=MQTTServer
THING_NAME=MQTTServer1
AWS_AccountId=123456789012
REGION_DATA=ap-northeast-1
```

初めに証明書を格納するdirectory(今回はcert)を作り移動します。

```sh
mkdir cert
cd cert
```

事前準備したparameterを読み込みます。

```sh
THING_NAME=$(cat ../parameters.conf | grep THING_NAME | awk -F'=' '{print $2}' | sed -e "s/[\r\n]\+//g")
PROJECT_NAME=$(cat ../parameters.conf | grep PROJECT_NAME | awk -F'=' '{print $2}' | sed -e "s/[\r\n]\+//g")
AccountId=$(cat ../parameters.conf | grep AWS_AccountId | awk -F'=' '{print $2}' | sed -e "s/[\r\n]\+//g")
region=$(cat ../parameters.conf | grep REGION_DATA | awk -F'=' '{print $2}' | sed -e "s/[\r\n]\+//g")
```

ラズパイGW brokerのThingを作ります。

```sh
# create the thing
aws iot  --region ${region} create-thing --thing-name ${THING_NAME} | tee create-thing.json
```

証明書を作りクライアント証明書と秘密鍵をダウンロードします。
createもdeleteもコマンド操作で一発完了ダウンロード付き、現時点で自分的にはcli一択です。

```sh
# create and download the keys and device certificate
aws iot  --region ${region} create-keys-and-certificate --certificate-pem-outfile ${THING_NAME}-certificate.pem.crt --public-key-outfile ${THING_NAME}-public.pem.key --private-key-outfile ${THING_NAME}-private.pem.key --set-as-active | tee create-keys-and-certificate.json
```

証明書をThingに紐づけます。

```sh
# attach the certificate to the thing
CERT_ARN=$(jq -r '.certificateArn' < ./create-keys-and-certificate.json)
aws iot  --region ${region} attach-thing-principal --thing-name ${THING_NAME} --principal ${CERT_ARN}
```

IoT policyを作ります。名前付きshadowを使うので名前付きshadowの予約topicのワイルドカードも組み込みます。

```sh
# create the policy
policy_stm="\"Statement\": [
  {
    \"Effect\": \"Allow\",
    \"Action\": \"iot:Connect\",
    \"Resource\": [ 
      \"arn:aws:iot:${region}:${AccountId}:client/\${iot:Connection.Thing.ThingName}\"
    ]
  },
  {
    \"Effect\": \"Allow\",
    \"Action\": [
      \"iot:Publish\",
      \"iot:RetainPublish\"
    ],
    \"Resource\": [
      \"arn:aws:iot:${region}:${AccountId}:topic/${PROJECT_NAME}/\${iot:Connection.Thing.ThingName}/*\",
      \"arn:aws:iot:${region}:${AccountId}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/name/*\"
    ]
  },
  {
    \"Effect\": \"Allow\",
    \"Action\": \"iot:Subscribe\",
    \"Resource\": [
      \"arn:aws:iot:${region}:${AccountId}:topicfilter/${PROJECT_NAME}/\${iot:Connection.Thing.ThingName}/*\",
      \"arn:aws:iot:${region}:${AccountId}:topicfilter/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/name/*\"
    ]
  },
  {
    \"Effect\": \"Allow\",
    \"Action\": [
      \"iot:Receive\",
      \"iot:GetRetainedMessage\"
    ],
    \"Resource\": [
      \"arn:aws:iot:${region}:${AccountId}:topic/${PROJECT_NAME}/\${iot:Connection.Thing.ThingName}/*\",
      \"arn:aws:iot:${region}:${AccountId}:topic/\$aws/things/\${iot:Connection.Thing.ThingName}/shadow/name/*\"
    ]
  },
  {
    \"Effect\": \"Allow\",
    \"Action\": \"iot:ListRetainedMessages\",
    \"Resource\": [
      \"arn:aws:iot:${region}:${AccountId}:client/\${iot:Connection.Thing.ThingName}\"
    ]
  }
]"
aws iot  --region ${region} create-policy --policy-name "${THING_NAME}" --policy-document "{\"Version\": \"2012-10-17\",${policy_stm}}"
```

IoT policyを証明書に紐づけます。

```sh
# attach policy to the certificate
aws iot  --region ${region} attach-policy --policy-name "${THING_NAME}" --target ${CERT_ARN}
```

サーバー証明書(rooCA)をダウンロードします。

```sh
# download the amazon root ca
wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
```

AWS accountのiot endpointを取得します。

```sh
# find out what endpoint we need to connect to
echo $(aws iot  describe-endpoint --endpoint-type iot:Data-ATS --region ${region}) | tee end_point.json
```

## (おまけ)ラズパイ側環境変数

次回以降の投稿内の説明で使わないかもしれませんが環境変数設定の説明をしておきます。  

自分の普段使いでは、Pythonの検証コードを自動起動する際にはcron、繰り返しコマンドライン実行する際には.plofileでloadした環境変数を読み込んでいます。この2つの設定も事前に済ませておきます。

```sh: crontab -e
# creating cron_mod.conf
sudo cp ~/mcert/cron_mod.conf ./cron_mod.conf
echo "" | tee -a cron_mod.conf

# addind cron configurations
echo HOST_ENDPOINT=$(jq -r '.endpointAddress' < ./end_point.json) | tee -a cron_mod.conf
echo CACERT=~/cert/AmazonRootCA1.pem | tee -a cron_mod.conf
echo CLIENTCERT=~/cert/${THING_NAME}-certificate.pem.crt | tee -a cron_mod.conf
echo CLIENTKEY=~/cert/${THING_NAME}-private.pem.key | tee -a cron_mod.conf
echo "" | tee -a cron_mod.conf
echo PROJECTNAME=${PROJECT_NAME} | tee -a cron_mod.conf
echo HOSTNAME=${THING_NAME} | tee -a cron_mod.conf
echo "" | tee -a cron_mod.conf
echo @reboot sh ~/py_app/watch_alive.sh | tee -a cron_mod.conf
echo @reboot sh ~/.profile | tee -a cron_mod.conf
echo "" | sudo tee -a cron_mod.conf

# adding .profile
echo "" | sudo tee -a ../.profile
echo export HOST_ENDPOINT=$(jq -r '.endpointAddress' < ./end_point.json) | tee -a ../.profile
echo export CACERT=~/cert/AmazonRootCA1.pem | tee -a ../.profile
echo export CLIENTCERT=~/cert/${THING_NAME}-certificate.pem.crt | tee -a ../.profile
echo export CLIENTKEY=~/cert/${THING_NAME}-private.pem.key | tee -a ../.profile
echo export "" | tee -a ../.profile
echo export PROJECTNAME=${PROJECT_NAME} | tee -a ../.profile
echo export HOSTNAME=${THING_NAME} | tee -a ../.profile
echo export "" | tee -a ../.profile

crontab -r
crontab ./cron_mod.conf
```

以上でラズパイGW broker <-> AWS IoT間でMQTT通信をする準備が整いました。

## 次回

[shadowとwillとretainを活用してAWS IoT CoreとPubSubする③pahoでpubsub](https://qiita.com/Saito5656/items/6b3d0d70fb3eeaeb5532)