[AWS CDK]RaspberryPiからIoT Coreを経由してLambdaまで到達する構成をCDKで構築してみた

RaspberryPiからIoT Coreを経由してLambdaまで到達する構成をCDKで構築してみました。ここではAWS側を主に扱うため、RaspberryPi自身の設定は扱いません。

全コードはGithubを参照してください。
[Githubはここ](https://github.com/ShinyaT1994/cdk-iot.git)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/660703/4fe35b69-5c9f-e7aa-4524-cd1323d521e0.png)

## 前提条件
- AWS CDK v.2 がインストールされていること
- Python 3.x がインストールされていること
- AWSアカウントがあり、AWS CLIが設定されていること

※Cloud9を使うとこの辺りがPassできるため、Cloud9を使って今回の記事の内容は作成しています。

## 構築手順
### 1. CDKアプリの初期化
先ずはCDKアプリの初期化を行います。

```bash
$ mkdir cdk-iot
$ cd cdk-iot
$ cdk init --language python
```

### 2.必要なパッケージをインストール

続いて必要パッケージのインストールになります。
ここでは、CDKアプリを初期化した際に作成された.venvを有効化しています。

```bash
$ source .venv/bin/activate
$ pip install -r requirements.txt
```

### 3.証明書の作成
CDKで証明書を作成することもできますが少し煩雑に見えたため、今回はコマンドで証明書の作成を行っています。
詳しくは↓↓を参照してください。
[AWS IoT 証明書の作成 (CLI)](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/device-certs-create.html#:~:text=%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%97%E3%81%BE%E3%81%99%E3%80%82-,AWS%20IoT%20%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E4%BD%9C%E6%88%90%20(CLI),-AWS%20CLI%20%E3%81%AB)

AWS CLIを使って証明書を作成するコマンドは以下です。
```bash
$ aws iot create-keys-and-certificate \
    --set-as-active \
    --certificate-pem-outfile certificate_filename.pem \
    --public-key-outfile public_filename.key \
    --private-key-outfile private_filename.key
```
ここでは、3つのファイルが作成されますので、Raspberry Pi上で実行していない場合はRaspberry Pi上にファイルを転送する必要があります。
また、ここで作成されるファイル以外にCA証明書ファイルが必要になりますが、上記のコマンドでは作成されないため別でダウンロードします。
[ルートCA証明書のダウンロード](https://catalog.us-east-1.prod.workshops.aws/workshops/b3e0b830-79b8-4c1d-8a4c-e10406600035/ja-JP/phase3/step2/step2-b#3.2.2-ca)

```bash
$ wget https://www.amazontrust.com/repository/AmazonRootCA1.pem -O AmazonRootCA1.pem
```

### 4. スタックの記述
「3.証明書の作成」で作成したIoTの証明書のARNを直接書き込んでいます。ARNについてはコンソール等で確認してください。
```python
from aws_cdk import (
    Stack,
    aws_iam as iam,
    aws_iot as iot,
    aws_lambda as lambda_,
)
from constructs import Construct


class CdkIotStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        
        # 今回は簡単のため、証明書をCLIで作成している
        cert_arn = 'xxxxxxxxx'
        
        region = 'ap-northeast-1'
        account_id = 'xxxxxxxxx'
        
        topic_name = 'test/topic'
        
        # モノ
        # https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_iot/CfnThing.html
        thing = iot.CfnThing(
            self,
            'MyThing',
            thing_name='my-first-thing',
        )
        
        # モノと証明書の紐づけ
        # https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_iot/CfnThingPrincipalAttachment.html
        thing_principal_attachment = iot.CfnThingPrincipalAttachment(
            self,
            'AttachCertificateToMyThing',
            principal=cert_arn,
            thing_name=thing.thing_name,
        )
        
        # ポリシー
        # https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_iot/CfnPolicy.html
        # https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/pub-sub-policy.html
        policy = iot.CfnPolicy(
            self,
            'IotPolicy',
            policy_document={
                "Version":"2012-10-17",
				"Statement":[
					{
						"Effect":"Allow",
						"Action":[
							"iot:Connect"
						],
						"Resource":[
							f"arn:aws:iot:{region}:{account_id}:client/*"
						]
					},
					{
						"Effect": "Allow",
						"Action": [
							"iot:Publish",
							"iot:Receive"
						],
						"Resource": [
							f"arn:aws:iot:{region}:{account_id}:topic/{topic_name}"
						]
				    },
					{
						"Effect": "Allow",
						"Action": [
							"iot:Subscribe"
						],
						"Resource": [
							f"arn:aws:iot:{region}:{account_id}:topicfilter/{topic_name}"
						]
				    }
				]
            },
			policy_name='iot-policy',
        )
        
        # ポリシーと証明書の紐づけ
        # https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_iot/CfnPolicyPrincipalAttachment.html
        policy_principal_attachment = iot.CfnPolicyPrincipalAttachment(
            self,
            'AttachCertificateToPolicy',
            policy_name=policy.policy_name,
            principal=cert_arn,
        )
        
        # 依存関係の挿入
        thing_principal_attachment.add_dependency(thing)
        policy_principal_attachment.add_dependency(policy)
        
        # Test lambda function
        test_function = lambda_.Function(
            self,
            'TestFunction',
            code=lambda_.Code.from_inline(
                code='def lambda_handler(event, context): print(f"Event: {event}\nContext: {context}", )',
            ),
            handler='index.lambda_handler',
            runtime=lambda_.Runtime.PYTHON_3_10,
            function_name='test-function-iot-core'
        )
        
        # Topic Rule
        topic_rule = iot.CfnTopicRule(
            self,
            'TopicRule',
            topic_rule_payload=iot.CfnTopicRule.TopicRulePayloadProperty(
                actions=[
                    iot.CfnTopicRule.ActionProperty(
                        lambda_=iot.CfnTopicRule.LambdaActionProperty(
                            function_arn=test_function.function_arn,
                        ),
                    )
                ],
                sql=f"SELECT * FROM '{topic_name}'",
                aws_iot_sql_version='2016-03-23',
            ),
            rule_name='test_topic_rule',
        )

        # add lambda trigger
        test_function.add_permission(
            'AddIotTopicRuleTrigger',
            principal=iam.ServicePrincipal(service='iot.amazonaws.com'),
            source_arn=topic_rule.attr_arn,
        )
```

### 5. CDKのデプロイ
```bash
cdk deploy
```

以上で構築完了です。

### 6. Raspberry Piから接続検証
Raspberry Piからの接続検証については、下の記事が分かりやすいためご参照ください。

[AWS IoT CoreをRaspberry Piで使ってみた その2](https://cami.jp/tech/4389/)

今回はTopicRuleにて特定のTopicに対してクエリを行っているため、[この部分で](https://cami.jp/tech/4389/#:~:text=%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AEbasicPubSub.py%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B)でプログラム実行を行う際に下のようにTopicを指定して下さい。

```bash
python3 basicPubSub.py \
    --endpoint <endpoint> \
    --rootCA <Root CA file path> \
    --cert <cert file path> \
    --key  <key file path> \
    --topic <topic name>　　　←←追加
```

## まとめ
この記事では、Raspberry PiからIoT Coreを経由してLambdaまで到達する構成をCDKで構築した際の記録を残しております。
どなたかの参考になれば幸いです。