AWS IoTの証明書自動登録でクライアント証明書の運用を楽にする

2016.08.08

ども、大瀧です。
先日AWS IoTにデバイス証明書のジャストインタイム登録という機能が追加されました。これを利用すると、ユーザーが用意した証明書を利用するケースでAWS IoTへのクライアント証明書の登録が不要になり、運用コストを下げることができます。

AWS IoTの認証のおさらい

AWS IoTの認証は、他のAWSサービスと同じRESTful APIでの署名ベースの認証の他に、X.509クライアント証明書を利用することができます。X.509クライアント証明書はAWS IoTが発行するものとユーザーが発行して登録する2パターンがあり、今回の自動登録はユーザーが発行する証明書が対象です。

証明書自動登録とは

従来のクライアント証明書による認証は、クライアントから送付する証明書とAWS IoTに登録済みのクライアント証明書を突き合わせ、証明書が合致すれば接続OK(下図のクライアント証明書1)、合致するものがなければ接続NG(下図のクライアント証明書2)というシンプルなものでした。下図のイメージです。

awsiotautoregist01

今回の自動登録は、未登録の証明書(下図のクライアント証明書2)が送付されると、AWS IoTはクライアント証明書記載のCAと登録済みのユーザーCA証明書を突き合わせ、自動登録が有効なユーザーCA証明書があればCA証明書毎に用意される証明書の申請イベントのトピックにクライアント証明書の情報をメッセージとしてPublishしてくれます。

awsiotautoregist02

自動登録は既定で無効なので、後述のユーザーCA証明書の登録/アップデート時に有効化する必要があります。また、AWS IoTがやってくれるのはトピックへのPublishまでなので、そのメッセージをSubscribeしクライアント証明書を実際に登録する処理は、別途ユーザーが実装する必要があります。←ここ重要。サンプルとしてAWSLambdaのコードが公開されているので先述の申請 イベントのトピックにルールを追加し、Lambdaで登録処理を行うのがお手軽です。

設定手順

では、実際に試してみた手順をご紹介します。X.509証明書の発行は、今回はバージョン3になって扱いやすくなったeasy-rsaを利用します。

検証環境

  • OS : Mac OS X El Capitan
  • easy-rsa : バージョン 3.0.1
  • AWS CLI : バージョン 1.10.53

1. CAのセットアップ

手元のマシンで証明書一式を用意しましょう。easy-rsaを使うとオレオレCAとクライアント証明書が手軽に生成できます。

まずは、GitHubのリリースページからeasy-rsaのファイル一式をダウンロード、展開します。

awsiotautoregist03

$ tar zxf ~/Downloads/EasyRSA-3.0.1.tgz
$ cd EasyRSA-3.0.1/
$ ./easyrsa

Easy-RSA 3 usage and overview

USAGE: easyrsa [options] COMMAND [command-options]
  :(略)
$

続いて、CAのセットアップを実行します。CA秘密鍵のパスフレーズとCommon Nameを聞かれるので、必要に応じて入力します(パスフレーズは任意、Common Nameは既定のEasyRSA CAとしました)。

$ ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /Users/ryuta/Desktop/EasyRSA-3.0.1/pki

$ ./easyrsa build-ca
Generating a 2048 bit RSA private key
..................................................................+++
............................................................................+++
writing new private key to '/Users/ryuta/Desktop/EasyRSA-3.0.1/pki/private/ca.key.kJkKJ0Jzaf'
Enter PEM pass phrase: [パスフレーズを入力]
Verifying - Enter PEM pass phrase: [パスフレーズを入力]
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/Users/ryuta/Desktop/EasyRSA-3.0.1/pki/ca.crt
$

CAのセットアップが完了しました。続いてDHパラメータを生成します。

$ ./easyrsa gen-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
..........................+............................................................+..........+.........+..............................................+..................................
  :(略)

DH parameters of size 2048 created at /Users/ryuta/Desktop/EasyRSA-3.0.1/pki/dh.pem
$

生成されたファイルはpki/以下に配置されます。このあとAWS IoTにユーザーCA証明書ca.crtをアップロードします。

$ ls pki/
ca.crt              dh.pem              index.txt.attr      index.txt.old       private/            serial
certs_by_serial/    index.txt           index.txt.attr.old  issued/             reqs/               serial.old
$

2. クライアント証明書の発行

クライアント証明書を生成していきます。今回は、以下の2つを用意します。

  • AWS IoTのユーザーCA登録時の検証用クライアント証明書(Common Name: 後述の登録コード)
  • 動作確認用クライアント証明書(Common Name: client1)

先に、AWS IoTのユーザーCA登録コードを調べておきます。

$ aws iot get-registration-code
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$

クライアント証明書の作成、署名は./easyrsa build-client-fullで実行します。今回は、秘密鍵のパスフレーズを設定しないので、nopassを引数に付与しています。

$ ./easyrsa build-client-full XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  nopass
Generating a 2048 bit RSA private key
.................+++
............................+++
writing new private key to '/Users/ryuta/Desktop/EasyRSA-3.0.1/pki/private/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.key.NmZsQb1TKz'
-----
Using configuration from /Users/ryuta/Desktop/EasyRSA-3.0.1/openssl-1.0.cnf
Enter pass phrase for /Users/ryuta/Desktop/EasyRSA-3.0.1/pki/private/ca.key: [パスフレーズを入力]
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
Certificate is to be certified until Aug  6 02:14:49 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
$ ./easyrsa build-client-full client1 nopass
Generating a 2048 bit RSA private key
..................................................................................+++
.+++
writing new private key to '/Users/ryuta/Desktop/EasyRSA-3.0.1/pki/private/client1.key.v4OUGBqNTm'
-----
Using configuration from /Users/ryuta/Desktop/EasyRSA-3.0.1/openssl-1.0.cnf
Enter pass phrase for /Users/ryuta/Desktop/EasyRSA-3.0.1/pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'client1'
Certificate is to be certified until Aug  6 02:18:53 2026 GMT (3650 days)

Write out database with 1 new entries
Data Base Updated
$

クライアント証明書はpki/issued/、クライアント秘密鍵はpki/private/に配置されます。

$ ls pki/issued/
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.crt  client1.crt
$ ls pki/private/
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.key  client1.key
ca.key
$

できていますね。なお、ユーザー作成のクライアント証明書をAWS IoTで利用するためには、クライアント証明書にユーザーCA証明書を含める必要があるため、以下のコマンドで追加しておきます。

$ cat pki/issued/client1.crt pki/ca.crt > pki/issued/client1wca.crt
$

これでOKです。

3. AWS IoTへのユーザーCA証明書の登録

証明書周りが揃ったので、AWS IoTにユーザーCA証明書を登録します。Management ConsoleのAWS IoTの管理画面にある[+ Create a Resource] - [Use my Certificate]をクリックし、[Register your CA certificate]画面を表示します。

awsiotautoregist04

ステップ5の[Select CA certificate]ボタンをクリックしてeasy-rsaのディレクトリにあるpki/ca.crtファイルを選択、[Select verification certificate]ボタンをクリックしてpki/issued/以下の検証用クライアント証明書を選択します。登録してすぐアクティブにする[Activate CA certificate]のチェックと、今回の自動登録を有効にする[Enable auto-registration]のチェックをオン、[Upload]をクリックしてアップロードします。

awsiotautoregist05

アップロードが完了すると、ユーザーCA証明書の詳細表示が促されるので、[View Certificate]ボタンをクリックします。

awsiotautoregist06

詳細画面に表示されるARNの末尾のIDがユーザーCA証明書IDです。証明書の申請イベントのトピック名になるので、メモしておきましょう。

awsiotautoregist07

これでユーザーCA証明書の設定は完了です。

4. 申請イベントのSubscribeと証明書登録処理の実装

冒頭の概要で説明した通り、未登録のクライアント証明書によるアクセスがあると、AWS IoTはユーザーCA証明書に対応する申請イベントのトピックにメッセージをPublishします。申請イベントのトピック名は$aws/events/certificates/registered/<ユーザーCA証明書ID>なので、このトピックに対するトピックルールを設定し、トピックアクションとしてLambdaを実行するようにします。

まずは、実行するLambda関数を以下のコード、設定で作成します。regionaccountIdは手元の環境のものに置き換えてください。

  • Runtime : Node.js 4.3
  • Handler : index.handler
  • Role : 以下のポリシーを持つロールを作成

Lambda関数の実行ロールのポリシー

{  
   "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":"*"
      }
   ]
}

Lambda関数 AWSIoTCertificateAutoRegistration

const region = "ap-northeast-1";
const accountId = "XXXXXXXXXXXX";

var AWS = require('aws-sdk');

exports.handler = function(event, context, callback) {
    var iot = new AWS.Iot({'region': region, apiVersion: '2015-05-28'});
    var certificateId = event.certificateId.toString().trim();
    var certificateARN = `arn:aws:iot:${region}:${accountId}:cert/${certificateId}`;
    var policyName = 'PubSubToAnyTopic';
    
    iot.attachPrincipalPolicy({
        policyName: policyName,
        principal: certificateARN
    }, (err, data) => {
        //Ignore if the policy is already attached
        if (err && (!err.code || err.code !== 'ResourceAlreadyExistsException')) {
            console.log(err);
            callback(err, data);
            return;
        }
        console.log(data);
    });

    iot.updateCertificate({
        certificateId: certificateId,
        newStatus: 'ACTIVE'
    }, (err, data) => {
        if (err) {
            console.log(err, err.stack); 
            callback(err, data);
        }
        else {
            console.log(data);
            var message = "Success, created, attached policy and activated the certificate " + certificateId; 
            console.log(message);
            callback(null, message);
        }
    });
}
  • 8行目: クライアント証明書のIDがAWS IoTによって採番され、event.certificateIdで参照できます。これをアクティベート時に指定します
  • 12行目: 申請されたクライアント証明書とAWS IoTのポリシーを紐付けます
  • 25行目: クライアント証明書をアクティベート(有効化)します

続いてAWS IoTのトピックルールを作成し、申請イベントのトピックをSubscribeしてLambda関数を呼び出すルールを設定します。AWS IoTの管理画面にある[+ Create a Resource] - [Create a Rule]をクリックし、ルール名と説明を入力(今回はどちらもAWSIoTCertificateAutoRegistration)します。

awsiotautoregist08

ルール設定は以下の通りです。

  • SQL version: 2016-03-23-beta
  • Attribute: *
  • Topic filter: $aws/events/certificates/registered/<ユーザーCA証明書ID>

アクションには先ほど作成したLambda関数を選択します。

awsiotautoregist09

[Create]ボタンをクリックしてルールを作成すれば準備OKです。

5. 動作確認

では、mosquitto_subで未登録のクライアント証明書によるAWS IoTへの接続を試してみます。注意しなくてはならないのは、MQTTS接続時に指定するCA証明書はユーザーCA証明書ではなくルート証明書を指定する点です。事前にダウンロード (今回は../root.pemファイルに保存)しておきましょう。

$ mosquitto_sub --cafile ../root.pem --cert pki/issued/client1wca.crt --key pki/private/client1.key -h XXXXXXXX.iot.ap-northeast-1.amazonaws.com -p 8883 -q 1 -t  foo/bar -d
Client mosqsub/35337-suzaku.lo sending CONNECT
Client mosqsub/35337-suzaku.lo sending CONNECT
Client mosqsub/35337-suzaku.lo sending CONNECT
Client mosqsub/35337-suzaku.lo sending CONNECT
Client mosqsub/35337-suzaku.lo sending CONNECT
Client mosqsub/35337-suzaku.lo received CONNACK
Client mosqsub/35337-suzaku.lo sending SUBSCRIBE (Mid: 1, Topic: foo/bar, QoS: 1)
Client mosqsub/35337-suzaku.lo received SUBACK
Subscribed (mid: 1): 1

数回CONNECTを試行している様子がわかりますね。これは未登録の証明書がLambdaによって登録されるまでにかかるタイムラグによるものです。初回はエラーになり、登録次第接続が成功するという流れになります。そのため、クライアントからはエラー時の再送処理を意識する必要があります。

まとめ

AWS IoTのクライアント証明書の自動登録機能を試してみました。大量のデバイスに証明書を用意する場合は、ユーザーが証明書を持ち込む方がスムーズだと思いますので、自動登録を上手く利用できると思います。デバイスが増えるときの運用は、必ず越えなければならない壁になると思いますので、自動登録を利用して効率的な運用設計をしてみてください。

参考URL