AWS IoT Coreの認証機能を使って、セキュリティ・トークンからAWSサービスを呼び出してみた

### やりたかったこと
- AWS IoT Coreの認証機能を使って、x.509証明書で認証して、AWSサービスを利用したかった。

### 動機
- 画像をデバイスからS3へアップロードする際の認証をアクセスキー&シークレットキーではなく、もう少し安全な方法ができないかと思った。

### やったこと
- Raspberry piに環境センサーをつけたものがあったので、そのセンサーデータをファイルに出力して、ファイルをs3 syncした。

### 参考記事
- [こちらのブログ](https://aws.amazon.com/jp/blogs/security/how-to-eliminate-the-need-for-hardcoded-aws-credentials-in-devices-by-using-the-aws-iot-credentials-provider/) "How to Eliminate the Need for Hardcoded AWS Credentials in Devices by Using the AWS IoT Credentials Provider"
- [AWSのサービスの直接呼び出しの認証](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/authorizing-direct-aws.html)

---

## AWS IoT Things（モノ）の作成
センサーデータを保存する先のAWSアカウントにThings TypeとThingsを作成する。
まずは、awscliで操作可能な端末から以下を実行し、Things Typeを作成する。

```
aws iot create-thing-type --thing-type-name EnvDataMyRoom
```

出力結果を確認して、ちゃんと所望のアカウント、リージョンにThings Typeが作成することを確認する。

次に、Thingsの作成。

```
aws iot create-thing --thing-name EnvDataMyRoomRasp --thing-type-name EnvDataMyRoom --attribute-payload "{\"attributes\": {\"Owner\":\"MyName\"}}"
```


## 証明書の登録
Certificate Authority (CA)証明書を利用してAWS IoTに接続する。
今回は簡単のために、AWS IoTのCAが発行する秘密鍵と証明書発行を行う。
登録するアカウントにaws cliで接続可能なPCから以下のコマンドを実行して、証明書と鍵を作成する。

```
aws iot create-keys-and-certificate \
    --set-as-active \
    --certificate-pem-outfile certificate_filename \
    --public-key-outfile public_key_filename \
    --private-key-outfile private_key_filename                   
```

(*ファイル名は好きなものを)

戻り値に`certificateArn`の値があるため、これと、thingsを紐づける。`certificateArn`値は、後でIoT Coreのポリシーと証明書を紐づける際に使うので、メモしておく。

```
aws iot attach-thing-principal --thing-name EnvDataMyRoomRasp --principal $certificateArn
```
(* $certificateArnには`aws iot create-keys-and-cretificate...`の戻り値の`certificateArn`の値をいいれる)

`aws iot create-keys-and-cretificate...`コマンドで出力された証明書と鍵ファイルをdeviceに転送する。

## IAMロールの構築
今回は、デバイスがS3にデータをsyncするためのロールを作成します。ロール作成にあたり、対象のロールが認証プロバイダで利用できる必要があります。

まず、認証プロバイダに付与されるIAMロールの設定をします。
以下の信頼ポリシーを対象ロールにアタッチして、内部でスイッチロールできるようにします。

```trustpolicyforiot.json
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Principal": {"Service": "credentials.iot.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }
} 
```
上記を記述した`trustpolicyforiot.json`ファイルを用意し、以下のコマンドを実行してロールの作成とポリシーのアタッチを行います。`s3-access-role`という名前のロールを作成します。

```
aws iam create-role --role-name s3-access-role --assume-role-policy-document file://trustpolicyforiot.json
```

次にS3へアクセスを許可するためのポリシーを作成し、`s3-access-role`にアタッチします。
今回はバケット名が`envdatamyroom-bucket`のバケットに対して、読み込み、書き込みを許可することとします。
以下のjsonファイルを作成します。

```accesspolicyfors3.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ListObjectsInBucket",
            "Effect": "Allow",
            "Action": ["s3:ListBucket"],
            "Resource": ["arn:aws:s3:::envdatamyroom-bucket"]
        },
        {
            "Sid": "AllObjectActions",
            "Effect": "Allow",
            "Action": "s3:*Object",
            "Resource": ["arn:aws:s3:::envdatamyroom-bucket/*"]
        }
    ]
}
```
作成した`accesspolicyfors3.json`からポリシーを作成します。

```
aws iam create-policy --policy-name accesspolicyfors3 --policy-document file://accesspolicyfors3.json
```
上記コマンドの戻り値の`Arn`の値を参考に以下のコマンドを実行します。

```
aws iam attach-role-policy --role-name s3-access-role --policy-arn arn:aws:iam::<your_aws_account_id>:policy/accesspolicyfors3
```
(* <your_aws_account_id>は利用するAWSアカウントID）

以上でIAMロールは完成です。

次にロール・エイリアスを作成します。ロール・エイリアスを作成するには、作成するユーザーに`iam:PassRole`権限と`iam:GetRole`権限が必要となる。この権限を持っていないユーザーでロール・エイリアスを作成する際には、別途、ポリシー作成とユーザーへのポリシーのアタッチが必要となる。今回はこれらの権限を持っているユーザーで実行すると想定しているため省略する。

##ロール・エイリアスの作成
AWS IoT CoreのポリシーとIAMポリシーを結ぶ、ロールエイリアスを作成する
以下のコマンドを実行する。なお、`--credential-duration-seconds`属性はセキュリティ・トークンの持続時間を示す。最大3600秒となっている。

```
aws iot create-role-alias --role-alias EnvDataMyRoom-s3-access-role-alias --role-arn arn:aws:iam::<your_aws_account_id>:role/s3-access-role --credential-duration-seconds 3600
```
(* <your_aws_account_id>は利用するAWSアカウントID）

結局はセキュリティ・トークンは制限時間があるので、後々は都度セキュリティ・トークンを作成することになる。その分のパフォーマンスのオーバーヘッドは見ておかないといけない。

## ポリシーのアタッチ
AWS IoT Coreで管理しているポリシーを作成し、Thingsにアタッチする。
まず、以下のjsonファイルを作成する。

```MyNameEnvDataMyRoompolicy.json
{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "iot:AssumeRoleWithCertificate",
    "Resource": "arn:aws:iot:ap-northeast-1:<your_aws_account_id>:rolealias/EnvDataMyRoom-s3-access-role-alias",
    "Condition": {
      "StringEquals": {
        "iot:Connection.Thing.Attributes[Owner]": "MyName",
        "iot:Connection.Thing.ThingTypeName": "EnvDataMyRoom"
      },
      "Bool": {
        "iot:Connection.Thing.IsAttached": "true"
      }
    }
  }
}
```
(* <your_aws_account_id>は利用するAWSアカウントID）

次に、以下を実行してポリシーを作成する。

```
aws iot create-policy --policy-name MyNameEnvDataMyRoomPolicy --policy-document file://MyNameEnvDataMyRoompolicy.json
```

最後に、作成したポリシーを証明書にアタッチします。

```
aws iot attach-policy --policy-name MyNameEnvDataMyRoomPolicy --target <certificate-arn>
```
(* <certificate-arn>は利用する証明書のARN。証明書を作成・登録した際に出力されたものを利用。）

これで権限まわりの設定は完了です。

## セキュリティ・トークンの取得

まずは、セキュリティトークンを取得するためのHttpリクエストを投げる先、認証プロバイダーの確認を行います。
以下のコマンドを実行して認証プロバイダのアドレスを取得します。

```
aws iot describe-endpoint --endpoint-type iot:CredentialProvider
```
戻り値は以下のようになるので、`endpointAddress`を覚えておきます。

```
{
    "endpointAddress": "xxxxxxxx.credentials.iot.ap-northeast-1.amazonaws.com"
}
```

さて、ようやくデバイスでの作業になります。
デバイス上での作業の事前準備として以下の２点を確認してください。

1. [証明書の登録](#証明書の登録)で発行した秘密鍵と証明書をデバイスに転送済みであること
1. CA証明書をダウンロード(AmazonのRootCAは[こちら](https://docs.aws.amazon.com/iot/latest/developerguide/server-authentication.html?icmpid=docs_iot_console#server-authentication-certs))してデバイスに転送済みであること
1. デバイス上でcurlが使えること


これを前提として、以下のコマンドをデバイス上で実行します。

```
curl --cert <your_certificate_file> --key <your secret key> -H "x-amzn-iot-thingname:EnvDataMyRoomRasp" --cacert AmazonRootCA1.pem https://<your_endpoint_address>/role-aliases/EnvDataMyRoom-s3-access-role-alias/credentials
```
AmazonRootCA1.pemは対応するCA証明書のことを示す。

コマンド実行により、アクセスキーとシークレットアクセスキー、セッショントークンを取得することができる。

```
{
  "credentials":{
                "accessKeyId":"access key",
                "secretAccessKey":"secret access key",
                "sessionToken":"session token",
                "expiration":"2021-04-25T03:47:26Z"
                }
}
```

## S3へのファイル転送
セキュリティトークンを取得したので、これを使ってs3 syncによるファイル連携します。
本来はコーディングすべきですが、ここは環境変数を定義して接続します。
以下のようにデバイス側で環境変数を定義します。

```
export AWS_ACCESS_KEY_ID=<access-key-id>
export AWS_SECRET_ACCESS_KEY=<secret-access-key>
export AWS_SESSION_TOKEN=<session-token>
```
(*<...>はcurlで取得した文字列)

ここで以下のコマンドを実行して、セッショントークンによる接続ができていることを確認してみます。

```
aws sts get-caller-identity
```
`UserId`, `Account`, `Arn`がなどが返ってきて、接続できていることがわかります。
では、以下のコマンドでs3 syncしてみましょう。

```
aws s3 sync . s3://envdatamyroom-bucket/envdatamyroom_raspberrypi/
```
これでs3へのデータ連携ができました。

# まとめ
今回はデバイスにIAMユーザーのアクセスキー、シークレトキーをハードコードせず、x.509証明書を使った認証行い、セキュリティ・トークンを発行することで、awscliを使ったデータ連携ができることを試してみました。これによって、鍵をデバイスにハードコードすることによる、セキュリティの脆弱性を回避して、より自由にAWSサービスをデバイスから利用できるようになります。IoT Coreのpayloadはメッセージ上限があるため、GreengrassのStreamManagerなどを使わないと容量の大きなファイルの転送などはできませんでした。こちらの方法を使うことで、セキュリティを保った上で、容量の大きなファイルのアップロードやより柔軟なAWSサービスの利用が可能になります。ご参考になれば幸いです。