AWS IoT 証明書のJust In Time登録

# AWS IoTのJust in timeについて

基本的には[こちらのブログ](https://aws.amazon.com/jp/blogs/iot/just-in-time-registration-of-device-certificates-on-aws-iot/)を元にしています。

この機能の必要性を考察すると、AWS IoTにはもともと証明書発行機能がありました。が、IoTの実情を考えると、AWS IoTで証明書を生成すると接続デバイスのプロビジョニングやセットアップなど、大量の"モノ"を管理するのと証明書発行の管理(生成、受け渡し、紐付け)が大変になることが考えられます。

具体的には、AWS IoTで事前に大量発行する証明書（実際にどの位の数が必要かわからない、どのデイバスにinstallされるか）、またこの証明書の名前がランダムな英数字で作られる。
そのランダムな名称の証明書をどのモノにinstallされるかなど、管理の側面を考えると問題になりそうでした。

この機能を使えば、初めてメッセージpublishするタイミングでAWSIoTへの証明書登録作業/policy管理などをすることで事前にAWS IoTでの証明書発行作業をせずともAWS IoTを利用することができます

# 概念的な依存関係整理

今回はEC2を認証局として利用しますので、EC2でopensslコマンド、AWS CLIを実行します。
※EC2の構築、AWS CLIの設定方法は今回対象外とします。[CLI環境構築のご参考](http://qiita.com/tcsh/items/4bea17afecbc8fc8abe0)

１）認証局での作業
証明証、中間証明書、Device用の証明書作成

2)AWS IoTでの作業
registartioncodeの払い出し
自動登録の有効化

![スクリーンショット 2016-12-31 21.00.16.png](https://qiita-image-store.s3.amazonaws.com/0/100893/b06d472f-f8e7-5804-4795-ee7aa998ef36.png)


# どういった動きになるのか略図

AWS IoT上で自動登録が有効化となっているCAからチェーンされているデバイス証明書からのrequestを受けると自動的に"$aws/events/certificates/registered/<caCertificateID>"topicへpublishメッセージが発行されます。
今回はこのメッセージを受けたら、Lambdaへ通知し、LambdaからAWS IoTのポリシーと証明書の紐付けと有効化をする処理をします。
＃注意点として、証明証が有効化されるまでの間はconnection closeされるので、送れなくても問題が無い、もしくは初期化用topicなども検討してください。

![スクリーンショット 2016-12-31 22.02.47.png](https://qiita-image-store.s3.amazonaws.com/0/100893/b168693a-80ba-9ed3-ae73-c4d3b184e353.png)

では、実際に作業してみましょう。

# 自前証明局作成作業
以下、EC2上でのAWS CLIが必要となりますので、CLI環境の確保をお願いします。
ここで取得する registration codeをCAのCN(Common Name)で利用しますので、メモを取っておいてください。
CA証明書をAWS IoTに登録するために使用される登録コードとなります。

```bash:コマンド:
aws iot get-registration-code
```
コマンドの実行結果は以下、

```bash

{
    "registrationCode": "YOUR_REGISTRATION_CODE" 
}
```
# 証明局(CA)操作

## CA作成

```bash:
mkdir /cert
cd /cert
mkdir /CA
cd /CA
openssl genrsa -out CAroot.key 2048
openssl req -x509 -new -nodes -key CAroot.key -sha256 -days 365 -out CAroot.pem
```
ここで証明書情報の入力が求められますが、全て空白/defaultのままで問題ありません


## 中間証明書

中間証明書の作成
以下のコマンド発行します

```bash
openssl genrsa -out Verify.key 2048
openssl req -new -key Verify.key -out Verify.csr

中間証明書csr作成時のCommon Nameに先程のAWS IoTのregistration codeを入力します。その他は空白でも問題ありません
|Organization Name (eg, company) []:
|Organizational Unit Name (eg, section)
|Common Name (e.g. server FQDN or YOUR name) []: "YOUR_REGISTRATION_CODE"
|EMAIL ADDRESS []:

署名の実行
openssl x509 -req -in Verify.csr -CA CAroot.pem -CAkey CAroot.key -CAcreateserial -out Verify.crt -days 365 -sha256

```

# 作成した証明書をAWS IoTへ登録

証明書をAWS IoTへ登録します

```bash

aws iot register-ca-certificate --ca-certificate file://CAroot.pem --verification-certificate file://Verify.crt

実行すると登録した証明証の情報がJSONで返却されます。CertificatedIdは利用しますのでメモを。
{
    "certificateArn": "arn:aws:iot:$REGION:$YOUR_AWS_ACCOUNT_ID:cacert/$YOUR_CA_CERT_ID", 
    "certificateId": "$YOUR_CA_CERT_ID" 
}
```

登録内容の確認

```bash
aws iot describe-ca-certificate --certificate-id <CERTIFICATE-ID>

#実行結果として、以下の様にJSONが返却されます
{
    "certificateDescription": {
        "certificateArn": "arn:aws:iot:$REGION:$YOUR_AWS_ACCOUNT_ID:cacert/$YOUR_CA_CERT_ID", 
        "status": "INACTIVE", 
        "autoRegistrationStatus": "DISABLE", 
        "certificateId": "$YOUR_CA_CERT_ID", 
        "certificatePem": "-----BEGIN CERTIFICATE-----<省略>-----END CERTIFICATE-----\n", 
        "ownedBy": "", 
        "creationDate": 1482929974.225
    }
}
```

statusとautoRegistrationStatusが有効化されていないので、これらを以下のコマンド有効にします。$YOUR_CA_CERT_IDは上記コマンドで取得したcertificateIdを利用して下さい

```bash
aws iot update-ca-certificate --certificate-id $YOUR_CA_CERT_ID --new-status ACTIVE
aws iot update-ca-certificate --certificate-id $YOUR_CA_CERT_ID --new-auto-registration-status ENABLE
```




# Device用証明書


```bash
cd /cert
mkdir thing_cert
cd thing_cert
openssl genrsa -out deviceCert.key 2048
openssl req -new -key deviceCert.key -out deviceCert.csr
openssl x509 -req -in deviceCert.csr -CA ../CA/CAroot.pem -CAkey ../CA/CAroot.key -CAcreateserial -out deviceCert.crt -days 365 -sha256
```

Just in Timeで利用する証明書を作成します

```bash
cat deviceCert.crt ../CA/CAroot.pem > deviceCertAndCA.crt
```

# AWS設定
以降はAWSコンソールからの作業

## IAM

IAMからロールの設定:この設定ではロール名を Lambda_JustInTimeとして以下で作成しています。

```bash
{  
   "Version":"2012-10-17",
   "Statement":[  
      {  
         "Effect":"Allow",
         "Action":[  
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents" 
         ],
         "Resource":"arn:aws:logs:*:*:*" 
      },
      {  
         "Effect":"Allow",
         "Action":[  
            "iot:UpdateCertificate",
            "iot:CreatePolicy",
            "iot:AttachPrincipalPolicy" 
         ],
         "Resource":"*" 
      }
   ]
}
```
## AWS Lambda

Lambdaの設定ですが、この時点では特にtriggerの設定をせずにlambda functionの設定のみで大丈夫です
後ほど、AWS IoTのrule設定でこのLambdaのtriggerセットをします。
"Create a Lambda function" -> "Configure function"
nameは任意で、ここではIoT_JustInTimeとしてます
Runtimeはpython2.7を選択
プログラムは以下です。いつにも増して異常系を検証しておりません。

```py:IoT_JustInTime.py
import boto3
import sys

IOT   = boto3.client('iot')

def attach_Policy(awsID, CertID):
    POLICY_NAME = 'JustInTime'
    # 下記のus-west-2はご自分の環境に合わせて修正してください
    CertificateARN = 'arn:aws:iot:us-west-2:' + awsID + ':cert/' + CertID

    try:
        IOT.attach_principal_policy(
              policyName = POLICY_NAME,
              principal  = CertificateARN
            )

    except Exception as e:
        print e.message
        raise

def activate_CA(ID):
    try:
        IOT.update_certificate(
              certificateId = ID,
              newStatus     = 'ACTIVE'
            )

    except Exception as e:
        print e.message
        raise

def lambda_handler(event, context):
''' just in time event structure
 {
  "certificateId": "<certificateID>",
  "caCertificateId": "<caCertificateId>",
  "timestamp": "<timestamp>",
  "certificateStatus": "PENDING_ACTIVATION",
  "awsAccountId": "<awsAccountId>",
  "certificateRegistrationTimestamp": "<certificateRegistrationTimestamp>"
}
'''
    awsID  = event['awsAccountId']
    certID = event['certificateId']
    try:
        print 'attach_poclicy'
        attach_Policy(awsID, certID)
        print 'activate_CA'
        activate_CA(certID)

    except Exception as e:
        print 'Error on Lambda'
        raise
```

ロールは先程作成したものを設定してください。

## AWS IoTの設定

### policyの作成
Security -> policies -> create
Nameに JustInTime
Advanced modeを選択して、以下のJSONを貼り付けてください
(Resource情報のregion/AWSaccountIDは各自の環境に合わせてください)

```json:JustInTime
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect",
        "iot:Publish",
        "iot:UpdateThingShadow"
      ],
      "Resource": "arn:aws:iot:REGION:AWSaccountID:*"
    }
  ]
}
```

※上のLambdaでpolicynameをJustInTimeとしていますので、ここで名前にJustInTime以外を指定した場合はLambda側の変数POLICY_NAMEも変更してください。

### IoTのrule作成
AWS IoT -> Rules -> create
Nameは任意
SQL versionはbeta
Attributeは *
Topic filterには　$aws/events/certificates/registered/__YOUR_CA_CERT_ID__
を設定YOUR_CA_CERT_IDは証明証登録で得られたものです。
画面下部で、
Add Actions -> Select an actionで AWS Lambda -> Configure action
Function nameに先程作成したlambda(=IoT_JustInTime)を選択します

#動作確認
これで設定は完了となります。
証明局を作成したEC2からmosquitto_pubでメッセージを投げてみます。
証明書が有効化されまで、数秒程度かかると思います。その間、Connectionエラーとなると思いますが、そのうちpublishに成功します。publishに成功すればOKです。
エラーが長時間表示され場合は Cloudwatch logsからAWS IoT/Lambdaのエラーを確認して見てください。
当たり前ですが、証明書のパスは実行時のディレクトリを考慮してください。

root.pemには、AWS IoTご利用者にはお馴染みの[ VeriSign ルート CA 証明書](https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem)です
curl/wgetなどで取得ください。

```bash
mosquitto_pub --cafile root.pem --cert deviceCertAndCA.crt --key deviceCert.key -h YOUR_AWSIOT_ENDPOINT.amazonaws.com -p 8883 -q 1 -t test/topic -i JustInTime --tls-version tlsv1.2 -m "test" -d

```

amazon linuxにmosquiito_clientを入れるには、以下のコマンドを実行

```
$ sudo curl http://download.opensuse.org/repositories/home:/oojah:/mqtt/CentOS_CentOS-6/home:oojah:mqtt.repo -o /etc/yum.repos.d/mqtt.repo
$ sudo yum install -y mosquitto-clients mosquitto
```

# 自前の証明書の期限が切れた場合の動き (追記)

SDKを利用してもmosquittoを使っても、client側でエラーが発生します。そのためにAWS IoTのログから検知することは出来ません。
pythonのDevice SDKの場合、Tracebackで以下が出ています。

```
ERROR:root:Traceback (most recent call last):
  File "dummy.py", line 107, in Init_MQTT
    MQTTclient.connect()
  File "/usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/MQTTLib.py", line 355, in connect
    return self._mqttCore.connect(keepAliveIntervalSecond)
  File "/usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/core/protocol/mqttCore.py", line 282, in connect
    self._pahoClient.connect(self._host, self._port, keepAliveInterval)  # Throw exception...
  File "/usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/core/protocol/paho/client.py", line 655, in connect
    return self.reconnect()
  File "/usr/local/lib/python2.7/site-packages/AWSIoTPythonSDK/core/protocol/paho/client.py", line 798, in reconnect
    ciphers=self._tls_ciphers)
  File "/usr/lib64/python2.7/ssl.py", line 933, in wrap_socket
    ciphers=ciphers)
  File "/usr/lib64/python2.7/ssl.py", line 601, in __init__
    self.do_handshake()
  File "/usr/lib64/python2.7/ssl.py", line 830, in do_handshake
    self._sslobj.do_handshake()
SSLError: [SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] sslv3 alert certificate unknown (_ssl.c:590)
```

mosquittoでは
```
Error: Problem setting TLS options.
```

が発生しています


# 最後に

基本的には証明局や証明書発行についての知識を求められますので、CA周りについては理解をしていただきたいと思います。AWSブログのLambdaではpolicyの生成もしていましたが、今回こちらでは事前にpolicyを作成した上で、証明書との紐付け/activateをするようにしました。
topicから分かる通り、CA単位での撃ち分けはルールエンジン/topicの組み合わせで可能ですが、逆にそれ以上のことは出来ません。例えばマルチテナント型でAWS IoTを使う場合は、CAを顧客単位で分けると、それによりAWS IoTのpolicyを利用した権限管理ができそうです。

# 免責

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