AWS IoTのJust in timeについて
基本的にはこちらのブログを元にしています。
この機能の必要性を考察すると、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環境構築のご参考
1)認証局での作業
証明証、中間証明書、Device用の証明書作成
2)AWS IoTでの作業
registartioncodeの払い出し
自動登録の有効化
どういった動きになるのか略図
AWS IoT上で自動登録が有効化となっているCAからチェーンされているデバイス証明書からのrequestを受けると自動的に"$aws/events/certificates/registered/"topicへpublishメッセージが発行されます。
今回はこのメッセージを受けたら、Lambdaへ通知し、LambdaからAWS IoTのポリシーと証明書の紐付けと有効化をする処理をします。
#注意点として、証明証が有効化されるまでの間はconnection closeされるので、送れなくても問題が無い、もしくは初期化用topicなども検討してください。
では、実際に作業してみましょう。
自前証明局作成作業
以下、EC2上でのAWS CLIが必要となりますので、CLI環境の確保をお願いします。
ここで取得する registration codeをCAのCN(Common Name)で利用しますので、メモを取っておいてください。
CA証明書をAWS IoTに登録するために使用される登録コードとなります。
aws iot get-registration-code
コマンドの実行結果は以下、
{
"registrationCode": "YOUR_REGISTRATION_CODE"
}
証明局(CA)操作
CA作成
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のままで問題ありません
中間証明書
中間証明書の作成
以下のコマンド発行します
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へ登録します
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"
}
登録内容の確認
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を利用して下さい
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用証明書
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で利用する証明書を作成します
cat deviceCert.crt ../CA/CAroot.pem > deviceCertAndCA.crt
AWS設定
以降はAWSコンソールからの作業
IAM
IAMからロールの設定:この設定ではロール名を Lambda_JustInTimeとして以下で作成しています。
{
"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を選択
プログラムは以下です。いつにも増して異常系を検証しておりません。
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は各自の環境に合わせてください)
{
"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 証明書です
curl/wgetなどで取得ください。
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プログラムの動作に関しても保障いたしませんので、参考程度にしてください。