Azure Device Provisioning Service (DPS) でX.509証明書を使用したデバイス自動登録をやってみた
はじめに
コンサルティング部の神野です。
前回はAzure Device Provisioning Service (DPS) で対称キーを使った自動登録をご紹介しましたが、今回はX.509証明書を使った方法をブログにしてみました。
証明書ベースの認証は少し手順が変わりますが、証明書による認証はIoTデバイスのセキュリティ向上に欠かせない要素なので試してみます!
今回やることの概要
下記ステップで進めていきます。証明書周りの手順が追加されていますが、前回と大きくは変わっておりません。
-
証明書の準備
X.509証明書のチェーンを作成します(ルート証明書→デバイス証明書) -
Azure IoT Hubの作成 (前回同様のため割愛)
デバイスからのメッセージを受け取る「玄関」を作ります。 -
Azure IoT Hub Device Provisioning Service (DPS) の作成 (前回同様のため割愛)
デバイスをIoT Hubに自動で案内してくれる「受付」を作ります。 -
IoT HubとDPSのリンク (前回同様のため割愛)
「玄関」と「受付」を繋ぎます。 -
DPSにグループ登録の作成
「受付」に、特定の証明書ツリーに属するデバイスを自動登録する設定をします。 -
Pythonスクリプトでデバイスをシミュレート
証明書を使ってDPSに接続し、IoT Hubへの自動登録を試します。 -
動作確認
無事にIoT Hubにデバイスが登録され、メッセージを送れるか確認します。
プロビジョニングのイメージ
イメージも前回から変わっておりません。
あくまで登録する方法が対称キーから証明書へ変わったことが差分となります。
準備するもの
- Azureアカウント
- Python実行環境: Python 3.10以上
- OpenSSL: 証明書の作成に使用
- Azure CLI
- テキストエディタ: Pythonスクリプトを編集するために使います。
証明書の準備
X.509証明書ベースの認証では、以下のような証明書チェーンを作成する必要があります。
- ルート証明書
- 認証の頂点となる証明書
- デバイス証明書
- ルート証明書によって署名された、各デバイス固有の証明書
今回はOpenSSLを使って自己署名のテスト証明書を作成します。本番環境では、適切な認証局から発行された証明書を使用することをお勧めします。
ルート証明書の作成
まず、ルート証明書を作成します。ターミナルまたはコマンドプロンプトを開き、以下のコマンドを順に実行します。
# 作業ディレクトリの作成
mkdir azure-iot-certs
cd azure-iot-certs
# プライベートキーの作成
openssl genrsa -out root-ca.key 2048
# 証明書署名要求(CSR)の作成
openssl req -new -sha256 -key root-ca.key -out root-ca.csr -subj "/CN=Root CA for IoT"
# 自己署名ルート証明書の作成(.pem拡張子で作成)
openssl x509 -req -sha256 -days 3650 -in root-ca.csr -signkey root-ca.key -out root-ca.pem
root-ca.key
(プライベートキー)とroot-ca.pem
(証明書)が作成されました。
デバイス証明書の作成
次に、実際のデバイスが使用する証明書を作成します。
# デバイスのプライベートキーの作成
openssl genrsa -out device1.key 2048
# デバイスのCSRの作成
# CN(Common Name)にデバイスIDを設定
openssl req -new -key device1.key -out device1.csr -subj "/CN=device1"
# ルート証明書でデバイス証明書に署名
openssl x509 -req -in device1.csr -CA root-ca.pem -CAkey root-ca.key -CAcreateserial -out device1.pem -days 365
device1.key
(デバイスのプライベートキー)とdevice1.pem
(デバイス証明書)が作成されました。
Azure IoT Hub を作成する ~ IoT Hub と DPS をリンクする
前回と全く同じ手順で作成するため、割愛します。
リソース名も同じものを使用するため、下記記事を見ながら進めていただければと思います。
DPSにX.509証明書を登録する
IoT Hub と DPS をリンクした後は、DPSに証明書を登録して、その証明書で署名されたデバイス証明書を持つデバイスを自動的に認証できるようにします。
-
DPSのポータルページを開きます。
-
左側のメニューから「設定」セクションの「証明書」を選択します。
-
上部にある「+追加」ボタンをクリックします。
-
証明書の詳細
- 証明書名: 任意の名前を入力します(例:
root-ca-cert
)。 - 証明書の .pem または .cer ファイル: 作成したルート証明書ファイル(
root-ca.pem
)をアップロードします。 - 「保存」をクリックします。
- 証明書名: 任意の名前を入力します(例:
-
証明書の検証
- 証明書が追加されますが、「未確認」の状態になっています。証明書の所有権を証明するために、検証が必要です。
- 先ほど追加した証明書の行で名前のリンクをクリックし、「確認コードを生成」を選択します。
- 表示された確認コードをメモします。
-
検証証明書の作成
- ターミナルで以下のコマンドを実行して、検証証明書を作成します。
<verification_code>
は先ほどメモした確認コードに置き換えてください。
- ターミナルで以下のコマンドを実行して、検証証明書を作成します。
# 検証用の秘密鍵を作成
openssl genrsa -out verification-cert.key 2048
# 検証コードをCN(Common Name)に設定したCSRを作成
openssl req -new -key verification-cert.key -out verification-cert.csr -subj "/CN=<verification_code>"
# ルート証明書で検証証明書に署名(.pem拡張子で作成)
openssl x509 -req -in verification-cert.csr -CA root-ca.pem -CAkey root-ca.key -CAcreateserial -out verification-cert.pem -days 365
- 検証証明書のアップロード
- DPSの証明書画面に戻り、先ほど追加した証明書の行で、検証証明書ファイルをアップロードします。
- 作成した検証証明書ファイル(verification-cert.pem)をアップロードします。
- 正常に処理されると、証明書の状態が「確認済み」に変わります。
GUI上で追加できてかなりお手軽ですね!
無事準備ができました!!
DPSに登録グループを作成する
検証済みの証明書を使って、登録グループを作成します。
-
DPSのポータルページを開きます。
-
左側のメニューから「設定」セクションの「登録を管理します」を選択します。
-
上部にある「+登録グループの追加」ボタンをクリックします。
-
登録とプロビジョニング
- グループ名: 任意の名前を入力します(例: cert-enrollment-group)。
- 構成対称メカニズム: X.509 証明書がこのデバイス プロビジョニング サービス インスタンスにアップロードされました
- X.509: 検証済みの証明書を選択(例: root-ca-cert)
- プロビジョニングの状態: この登録を有効にするにチェックをいれる
-
IoT ハブのリンクを割り当てる
- IoT ハブ: 事前に作成したIoT ハブをターゲット IoT ハブとして選択
- 割り当てポリシー: デフォルトのままでOKです。
-
デバイスID設定
- 今回は特に設定せずそのまま進めます。
- 今回は特に設定せずそのまま進めます。
-
確認と作成
- 登録する内容を確認して問題なければ作成ボタンを押下します。
- 登録する内容を確認して問題なければ作成ボタンを押下します。
これで、証明書ベースの登録グループの設定が完了しました。この登録グループに属するデバイス証明書を持つデバイスは、DPSに接続すると自動的にIoT Hubに登録されるようになります。
Pythonスクリプトでデバイスをシミュレートする
いよいよ、パソコン上でPythonスクリプトを実行して、証明書を使って仮想的なIoTデバイスとしてDPSに接続し、自動プロビジョニングを試します。
必要なPythonライブラリのインストール
ターミナル(WindowsならコマンドプロンプトやPowerShell、Macならターミナル.app)を開き、以下のコマンドを実行して、Azure IoT Device SDKをインストールします。
pip install azure-iot-device
「Successfully installed...」のようなメッセージが出ればOKです。
Pythonスクリプトの作成
お好きなテキストエディタを開き、以下のコードを貼り付けて、dps_cert_device_test.py
という名前で保存します。保存場所はどこでも構いません(例: デスクトップ)。
import os
import asyncio
import datetime
import random
import json
from azure.iot.device import X509
from azure.iot.device.aio import ProvisioningDeviceClient
from azure.iot.device.aio import IoTHubDeviceClient
# DPSの設定情報 - ご自身の環境に合わせて変更してください
PROVISIONING_HOST = "global.azure-devices-provisioning.net"
ID_SCOPE = "xxx" # DPSインスタンスのIDスコープ
# 証明書とキーのパス - ご自身の環境に合わせて変更してください
DEVICE_CERT_PATH = "./device1.pem" # デバイス証明書のパス
DEVICE_KEY_PATH = "./device1.key" # デバイスのプライベートキーのパス
REGISTRATION_ID = "device1" # 証明書のCN(Common Name)と一致する必要があります
async def provision_device():
print("X.509証明書を使ってDPSに接続し、IoT Hubへの登録を開始します...")
# X.509証明書の設定
x509 = X509(
cert_file=DEVICE_CERT_PATH,
key_file=DEVICE_KEY_PATH,
pass_phrase=None # パスフレーズがある場合は指定
)
# DPSクライアントの作成
provisioning_device_client = ProvisioningDeviceClient.create_from_x509_certificate(
provisioning_host=PROVISIONING_HOST,
registration_id=REGISTRATION_ID, # 証明書のCNと一致する登録IDを指定
id_scope=ID_SCOPE,
x509=x509,
)
# DPSに接続して登録
print("DPSに接続して登録を行っています...")
registration_result = await provisioning_device_client.register()
print(f"DPS登録ステータス: {registration_result.status}")
if registration_result.status == "assigned":
print(f"デバイスがIoT Hubに登録されました: {registration_result.registration_state.assigned_hub}")
print(f"デバイスID: {registration_result.registration_state.device_id}")
# IoT Hubに接続してテストメッセージを送信
# X.509証明書で認証されたIoT Hubクライアントを作成
device_client = IoTHubDeviceClient.create_from_x509_certificate(
x509=x509,
hostname=registration_result.registration_state.assigned_hub,
device_id=registration_result.registration_state.device_id,
)
# IoT Hubに接続
await device_client.connect()
print(f"IoT Hub ({registration_result.registration_state.assigned_hub}) に接続しました!")
# テストメッセージを送信
for i in range(3):
message = {
"temperature": round(random.uniform(20.0, 30.0), 1),
"humidity": round(random.uniform(50.0, 70.0), 1),
"message": f"Hello from Python X.509 DPS device! (message {i+1}/3)",
"timestamp": datetime.datetime.now().isoformat(),
"deviceId": registration_result.registration_state.device_id
}
print(f"メッセージを送信しています: {json.dumps(message)}")
await device_client.send_message(json.dumps(message))
await asyncio.sleep(1)
# 切断
await device_client.disconnect()
print("IoT Hubから切断しました")
else:
print(f"デバイスの登録に失敗しました。ステータス: {registration_result.status}")
# DPSクライアントの切断は不要
print("処理を終了します")
if __name__ == "__main__":
asyncio.run(provision_device())
DPSに接続してデバイス登録を行い、完了したらテストメッセージを送信するスクリプトとなっています。
スクリプトの修正
保存した dps_cert_device_test.py
をテキストエディタで開き、以下の4箇所を、ご自身の情報に書き換えてください。
-
ID_SCOPE
- Azure Portalで、作成したDPSインスタンス(例: cert-dps-test)の「概要」ページを開きます。
- そこに表示されている「IDスコープ」の値をコピーして、スクリプトの
"PROVISIONING_ID_SCOPE"
の部分に貼り付けます。
-
DEVICE_CERT_PATH
- 作成したデバイス証明書のパスを指定します。
-
DEVICE_KEY_PATH
- 作成したデバイスのプライベートキーのパスを指定します。
-
REGISTRATION_ID
- 証明書のCN(Common Name)と一致する必要があるため、今回は
device1
としています。この辺りは実際の運用でどう値を入れ込むのかは検討が必要かと思いました。ベタ書きよりかは環境変数などのファイルを参照する形になるかと思います。
- 証明書のCN(Common Name)と一致する必要があるため、今回は
これらの値が間違っていると、スクリプトは正しく動作しませんので実行前にご確認ください。
Pythonスクリプトの実行
ターミナルで、dps_cert_device_test.py
を保存したディレクトリに移動し(cdコマンドを使います)、以下のコマンドでスクリプトを実行します。
python dps_cert_device_test.py
スクリプトが実行されると、ターミナルに以下のようなログが表示されるはずです(一部の値はご自身の環境のものになります)。
X.509証明書を使ってDPSに接続し、IoT Hubへの登録を開始します...
DPSに接続して登録を行っています...
DPS登録ステータス: assigned
デバイスがIoT Hubに登録されました: sample-iot-hub.azure-devices.net
デバイスID: device1
IoT Hub (sample-iot-hub.azure-devices.net) に接続しました!
メッセージを送信しています: {"temperature": 24.6, "humidity": 67.3, "message": "Hello from Python X.509 DPS device! (message 1/3)", "timestamp": "2025-05-22T08:06:59.626893", "deviceId": "device1"}
メッセージを送信しています: {"temperature": 21.5, "humidity": 62.8, "message": "Hello from Python X.509 DPS device! (message 2/3)", "timestamp": "2025-05-22T08:07:00.857368", "deviceId": "device1"}
メッセージを送信しています: {"temperature": 29.0, "humidity": 58.1, "message": "Hello from Python X.509 DPS device! (message 3/3)", "timestamp": "2025-05-22T08:07:02.027735", "deviceId": "device1"}
IoT Hubから切断しました
処理を終了します
DPS登録ステータス: assigned と表示され、割り当てられたIoT Hub名とデバイスIDが表示されれば、DPSによるプロビジョニングは成功です! その後、IoT Hubにテストメッセージが送信されています。
もしエラーが出た場合は、エラーメッセージをよく読み、スクリプト内のIDスコープ、証明書パス、プライベートキーパスが正しいか、証明書形式は適切か、DPSの登録設定が正しいかなどを確認してみてください。
動作確認
最後に、Azure PortalやAzure Cloud Shellを使って、本当にデバイスがIoT Hubに登録され、メッセージが届いているか確認しましょう。
IoT Hubでのデバイス自動作成確認 (Azure Portal)
- Azure Portalで、作成したIoT Hub(例: cert-iot-hub)のページを開きます。
- 左側のメニューから「デバイス管理」セクションの「デバイス」を選択します。
- デバイスの一覧に、Pythonスクリプトのログで確認できたデバイスID(例: device1)でデバイスが自動的に作成され、「有効」状態になっていることを確認します。
IoT Hubへのメッセージ到着確認 (Azure Cloud Shell)
IoT Hubに送信されたメッセージを確認する簡単な方法は、Azure Cloud Shellを使うことです。
-
Azure ポータルで右上の [>_] アイコンをクリックするか、https://portal.azure.com/#cloudshell/ にアクセスしてAzure Cloud Shellを起動します。
-
初めて使用する場合は、ストレージアカウントの設定を求められるので、指示に従ってセットアップを完了します。
-
次に、IoT Hub拡張機能をインストールします(初回のみ)。
az extension add --name azure-iot
- 以下のコマンドを実行して、IoT Hubに到着するメッセージを監視します。YourIoTHubName の部分は、ご自身のIoT Hub名(例: sample-iot-hub)に置き換えてください。
az iot hub monitor-events --hub-name YourIoTHubName
- Pythonスクリプトを再度実行すると、Cloud Shellに以下のようなイベントが表示されるはずです。
{
"event": {
"origin": "device1",
"module": "",
"interface": "",
"component": "",
"payload": {
"temperature": 24.6,
"humidity": 58.4,
"message": "Hello from Python X.509 DPS device! (message 1/3)",
"timestamp": "2025-05-22T10:45:12.345678",
"deviceId": "device1"
}
}
}
payload の部分に、Pythonスクリプトから送信したメッセージの内容が含まれていれば、無事メッセージがIoT Hubに届いていることが確認できました!
監視を停止するには、Ctrl+C を押します。
デバイスの登録およびメッセージの送信も問題なく実行できましたね!
おわりに
今回は、Azure IoT Hub Device Provisioning Service (DPS) を使って、X.509証明書ベースのデバイス自動登録を試してみました。あまりコマンドを使うことなく、割と直感的にCA証明書の登録および自動登録できてよいなといった所感でした!
この記事が少しでも役立てば幸いです!
次回は、DPSのその他の機能についても紹介していけたらと思います!!