AWS IoT CoreのCustom Auth(カスタム認証)機能の使い方

# AWS IoT Core カスタム認証とは
AWS IoT Coreを認証局の認証/tokenをもとに、操作権限を渡す認証方式になります。詳細は[こちら](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-custom-authentication.html)
ここでは認証局を省き、簡単に設定と動作検証ができるまでの流れを説明します。
ワークフローは以下のような感じになります。
![スクリーンショット 2019-01-14 23.27.41.png](https://qiita-image-store.s3.amazonaws.com/0/100893/cb6a545d-079d-0995-2c75-69e413944b5a.png)


# 設定に必要となるもの

- 認証局 (本投稿では詳細は諸略し発行されるTokenをtextで作成する)
- AWS Lambda
- 証明書(秘密鍵、公開鍵)
    - この証明書セットはJITRなどのようにAWS IoTの情報を埋める必要がなく、独自の運用局で問題ないですが、公開鍵をAWS IoTへ登録できることは必要です。


# 設定
公開鍵の作成をLinux上で実施、Lambdaを登録し、CLI(GUI)で登録していく流れになります。
AWS IoTへの登録は公開鍵、Authorizerを関連付けします。
![スクリーンショット 2019-01-14 23.12.21.png](https://qiita-image-store.s3.amazonaws.com/0/100893/af311f29-12bb-604f-1e0c-1d31c0234349.png)


## 鍵の作成

以下のコマンドで、秘密/公開鍵の生成を行う

```bash

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
```

## Authorizer Lambdaの作成

AWS Lambdaを以下で作成します。Sampleとして、tokenにはallow/denyが入ってくる前提、AWS IoTも固定値を作成して返信していますが、このあたりの中身は各自必要に合わせて検討してください。
responseのJSONフォーマットは規定されているので、ご確認ください。

- IsAuthenticated：要求が認証されたかどうかを示すブール（true / false）属性です。
- PrincipalId：これは英数字の文字列です。最小長は1文字、最大長は128文字です。カスタム許可要求で受信されたトークンに関連したIDとして機能します。
- PolicyDocuments：WS IoTポリシー規則に従ったJSON形式のポリシードキュメントのリストです。このリストには最大10個のポリシー文書が含まれています。各ポリシー文書は最大2,048文字です。
- DisconnectAfterInSeconds：AWS IoTゲートウェイへの接続が切断されるまでの最大接続時間（秒単位）を示します。最小値は300秒、最大値は86,400秒です。
- RefreshAfterInSeconds：これは、ポリシーの更新間隔です。期限が切れると、Lambda関数はポリシーの更新を可能にするために再度呼び出されます。最小値は300秒、最大値は86,400秒です。

詳細は[こちら](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/custom-authorizer.html)

```python
from __future__ import print_function
import uuid
import json

response = {
    "isAuthenticated": True,
    "principalId": str(uuid.uuid1()).replace("-", ""),
    "disconnectAfterInSeconds": 84000,
    "refreshAfterInSeconds": 300,
    "policyDocuments": [],
    "context": {
    }
}

policy = {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action":["iot:Publish", "iot:Connect", "iot:Subscribe"],
        "Resource": ["*"]
    }]
}

def auth_main(event, context):
    print("your event payload:{}".format(json.dumps(event)))
    print("this request token:{}".format(event["token"]))
    
    response["policyDocuments"].append(policy)
    return response
```

eventには、リクエストのtoken情報が入ってきます。tokenの中身をみて判断することもできます。denyならisAuthenticatedをFalseなど。

## AWS LambdaをAWS IoTのAuthorizerの呼び出し

以下のコマンドで登録します。 token_key_nameはリクエスターがどのAuthorizerを使うかの識別子として使われますので、任意の1-128文字で設定します。
token-signing-public-keysはjsonでも表現可能です。また鍵情報に改行などもあるのでコマンド実行時に注意してください。
cliマニュアルは[こちら](https://docs.aws.amazon.com/cli/latest/reference/iot/create-authorizer.html)

```bash
aws iot create-authorizer --authorizer-name <authorizer_name> --authorizer-function-arn <Lambda_function_arn> --token-key-name <token_key_name> --token-signing-public-keys FIRST_KEY="<public_key_pem>" --status ACTIVE
```

登録の確認

```bash
aws iot describe-authorizer --authorizer-name <authorizer_name>
```
### option設定としてdefaultのAuthorizer設定

以下で指定するとauthorizer nameを省略して呼び出した場合のdefault authorizerの設定にもできる。

```bash
aws iot set-default-authorizer --authorizer-name <authorizer_name>
```

## AWS LambdaのPermission設定
```bash
aws lambda add-permission --function-name <lambda_function_name> --principal iot.amazonaws.com --source-arn <authorizer_arn> --statement-id Id-123 --action "lambda:InvokeFunction"
```

ここまでで、設定完了です。

# 実行
AWS IoTコマンドにtest invokeが用意されています。また、ここではcurlでの実行方法を記載しておきます。

## テスト実行
呼び出しには、tokenとtokenを秘密鍵で暗号化し、Base64化した情報をpayloadとする必要があります。
ここではtest invokeコマンドを使って確認してみます。tokenは認証局がallowを返却すると仮定します。

## token暗号情報とBase64の作成

```bash
echo -n allow > token.txt
openssl dgst -sha256 -sign private.pem -out token.sign token.txt
base64 token.sign > token.sign.b64
```

## testコマンド実行

tokenのb64化したファイルに改行コードが混じっているのでtrコマンド改行を抜いてファイルを開いています。


```bash
aws iot test-invoke-authorizer --authorizer-name <authorizer_name> --token allow --token-signature `tr -d "\n" < token.sign.b64`
```

Tokenが返却されていれば成功です。本来はAWS IoTがLambdaから受け取るものです。
test invokeの成功例

```json
{
    "isAuthenticated": true, 
    "refreshAfterInSeconds": 300, 
    "policyDocuments": [
        "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":[\"iot:Publish\",\"iot:Connect\",\"iot:Subscribe\"],\"Resource\":[\"*\"],\"Effect\":\"Allow\"}]}"
    ], 
    "disconnectAfterInSeconds": 84000, 
    "principalId": "78e0d676180b11e9aef91ac3a063ec3a"
}
```

## curlでの実行例

以下のような形でpostしてhttp 200レスポンスがあれば成功です。

```bash
curl -X POST -k -i -N -H "Host: <your_aws_iot_endpoint>" -H "X-Amz-CustomAuthorizer-Name: <authorizer_name>" -H "X-Amz-CustomAuthorizer-Signature: <token_signature>" -H "<token_key_name>: allow" -H "User-Agent: aws-cli/1.10.65 Python/2.7.11 Darwin/16.1.0 botocore/1.4.55" --data 'Hello from custom auth' https://<your_aws_iot_endpoint>/topics/customauthtesting
```

# 免責
本投稿は、個人の意見で、所属する企業や団体は関係ありません。
また掲載しているsampleプログラムの動作に関しても保障いたしませんので、参考程度にしてください。