Greengrass V2 Docker Container で Token Exchange Service を使ってみた

# はじめに
AWS IoT Greengrass V2は、Docker Containerに対応し、コンテナベースのコンポーネントを作成できます。また、GreengrassコンポーネントからAWSリソースにアクセスする際、認証処理を簡素化するため、TokenExchangeServiceというコンポーネントが用意されています。今回、コンテナベースのコンポーネントから、TokenExchangeServiceを使い、AWSリソースにアクセスする方法を試してみました。AWSリソースへのアクセスとして、DynamoDBへレコードを追加するという例を用いて確認しました。

# まとめ
Greengrass deviceで、認証情報（Token）をコンテナ内へ渡すためには、(1)認証情報を環境変数としてコンテナへ引き渡し、(2)コンテナのネットワーク接続をホストモードで接続する、事で可能となります。認証情報をコンテナ内に渡すことで、コード内に認証情報を記述することなくAWSサービスにアクセスすることができます。
1. コンテナへ引き渡す環境変数：  
  `--env AWS_CONTAINER_AUTHORIZATION_TOKEN --env AWS_CONTAINER_CREDENTIALS_FULL_URI`
1. クレデンシャルへのアクセスを可能とするためのネットワーク接続設定：  
  `--net=host`

# 概要
Greengrass Device から直接、DynamoDB（テーブル名：SampleDynamoTable）にレコードをPutするコンポーネントを作成し、デプロイします。コンポーネントのコンテナイメージをECR（レポジトリ名：dynamo_access_sample）に格納し、Greengrass Deviceへ配信します。

## 権限設定：IAMポリシー、IAMロールの作成
まずは、Greengrass デバイスに対して付与するAWSリソースへのアクセス権限ポリシーを作成します。今回、コンテナイメージレポジトリとしてECRを使うためECRへのアクセス権限と、DynamoDBへのレコード追加権限を付与するIAMポリシーを作成します。
```json:token_exchange_policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:*:table/SampleDynamoTable",
                "arn:aws:ecr:ap-northeast-1:*:repository/dynamodb_access_sample"
            ]
        }
    ]
}
```

上記で作成した、IAMポリシーを登録します。

```bash
aws iam create-policy \
  --policy-name MyGreengrassV2AccessPolicy \
  --policy-document file://token_exchange_policy.json
```

続いて、Greengrass device のIAMロール（GreengrassV2TokenExchangeRole）に、上記ポリシーをアタッチし権限を付与します。
![qiita_iam_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2843415/8829e5a1-053c-c225-1856-fe15790bf883.png)

![qiita_iam_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2843415/efedd29e-9b2b-e328-64a7-b2f8ce667e3a.png)


## コンポーネント作成：コンテナの作成、登録
AWSリソースとして、DynamoDBにレコードを追加するサンプルコードを用意します。

```python:dynamodb_access_sample.py
import boto3
import datetime
import os

aws_region = os.getenv("AWS_REGION", "ap-northeast-1")
device_name = os.getenv("AWS_IOT_THING_NAME", "greengrass")
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

dynamodb = boto3.resource('dynamodb', region_name=aws_region)
table = dynamodb.Table('SampleDynamoTable')

response = table.put_item(
    Item = {
        'device_name': device_name,
        'timestamp': timestamp
    }
)
print(response)
```
環境変数（`AWS_REGION`、`AWS_IOT_THING_NAME`）を用い、AWSのリージョン名、Greengrass coreのThing名をそれぞれ取得しています。

次に、コンテナ化するためのDockerfileを書きます。
```dockerfile:Dockerfile
FROM python:3.9-slim-buster
WORKDIR /root
RUN python -m pip install --upgrade pip && python -m pip install boto3
COPY dynamodb_access_sample_from_ggv2_container.py dynamodb_access_sample_from_ggv2_container.py
CMD [ "python", "/root/dynamodb_access_sample.py" ]
```

そして、Docker Container を Build し、 ECR へ Push します。

```bash
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t dynamodb_access_sample .
docker tag dynamodb_access_sample:latest <AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com/dynamodb_access_sample:latest
docker push <AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com/dynamodb_access_sample:latest
```

## Greengrass Component登録：レシピの作成、登録

Greengrass Componentのレシピを作成します。

```yaml:recipe.yaml
---
RecipeFormatVersion: '2020-01-25'
ComponentName: com.example.AccessDynamoDb
ComponentVersion: 0.0.1
ComponentDescription: A component for testing token exchange service with docker container.
ComponentDependencies:
  aws.greengrass.DockerApplicationManager:
    VersionRequirement: ^2.0.0
    DependencyType: HARD
  aws.greengrass.TokenExchangeService:
    VersionRequirement: ^2.0.0
    DependencyType: HARD
Manifests:
  - Platform:
      os: all
    Lifecycle:
      Install: |
        docker tag <YOUR AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com/dynamodb_access_sample:latest dynamodb_access_sample:latest
      Startup: |
        docker run \
          --env AWS_CONTAINER_AUTHORIZATION_TOKEN \
          --env AWS_CONTAINER_CREDENTIALS_FULL_URI \
          --env AWS_REGION \
          --env AWS_IOT_THING_NAME \
          --net=host --rm dynamodb_access_sample:latest
    Artifacts:
      - URI: "docker:<YOUR AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com/dynamodb_access_sample:latest"
```

AWSクレデンシャルへのアクセス情報をコンテナへ渡すため、環境変数（`AWS_CONTAINER_AUTHORIZATION_TOKEN`、`AWS_CONTAINER_CREDENTIALS_FULL_URI`）を指定し、アクセスを可能とするため、ネットワーク接続を`--net=host`とします。
また、`dynamodb_access_sample.py`で利用する環境変数（`AWS_REGION`,`AWS_IOT_THING_NAME`）も同時に引き渡します。

レシピを基に、Greengrass Componentを作成します。
```bash
aws greengrassv2 create-component-version --inline-recipe fileb://recipe.yaml
```

Deployment の Configuration を作成します。

```json:cli-deployment.json
{
    "targetArn": "arn:aws:iot:ap-northeast-1:<YOUR AWS ACCOUNT>:thing/<Greengrass Core Name>",
    "deploymentName": "Deployment for MyGreengrassCore",
    "components": {
        "com.example.AccessDynamoDb": {
            "componentVersion": "0.0.1"
        }
    },
    "deploymentPolicies": {
        "failureHandlingPolicy": "DO_NOTHING",
        "componentUpdatePolicy": {
            "timeoutInSeconds": 60,
            "action": "NOTIFY_COMPONENTS"
        },
        "configurationValidationPolicy": {
            "timeoutInSeconds": 60
        }
    }
}
```

Greengrass core へデプロイします。

```bash
aws greengrassv2 create-deployment \
    --cli-input-json file://cli-deployment.json
```


# 参考情報
https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/run-docker-container.html

https://docs.aws.amazon.com/ja_jp/greengrass/v2/developerguide/component-environment-variables.html

https://qiita.com/morikaz/items/f23ac05ae5430ce4c986

https://dev.classmethod.jp/articles/greengrass-v2-token-exchange-service/