【AWS IoT Core】マルチアカウントモードのCA証明書登録をコンソール上からやってみた
はじめに
こんにちは、コンサルティング部の神野です。
皆さんは、マルチアカウント環境でそれぞれおのアカウントのAWS IoT Coreに対して、同じCA証明書を登録したことはありますか?
試してみようと思ってコンソール上のAWS IoT Coreでセキュリティ
> 認証機関
を選択すると下記画面が表示されました。
この画面を見て、安直にマルチアカウントで使用するからマルチアカウントモードでCAを登録する
で1アカウントで登録したら全アカウントでできるやろ!と言ったウルトラ勘違いをしたので改めて記事にしてみました。
ちなみにマルチアカウントモード = SNI_ONLY
モードでもあります。
すでにSNI_ONLY
モードの記事もあり大変参考になりました。
コンソール上からも登録できるようになったので、今回はコンソール上から登録してみた記事でもあります。
先に結論
- マルチアカウントモードで登録すれば同一CA証明書を複数アカウントで使用可能
- アカウントAとアカウントBで同じCA証明書をマルチアカウントモードで登録することで、同じCA証明書で発行したデバイス証明書が両方のアカウントで使用できます
- シングルアカウントモードでアカウントAに登録、マルチアカウントモードでアカウントBに登録といったケースも可能です
- アカウントAとアカウントBで同じCA証明書をマルチアカウントモードで登録することで、同じCA証明書で発行したデバイス証明書が両方のアカウントで使用できます
- 各アカウントで個別にCA証明書を登録する必要がある
- 1つのアカウントで登録したからといって、他のアカウントに自動的に反映されるわけではありません
同一CA証明書をそれぞれのアカウントでマルチアカウントモードで登録すれば、問題なく利用できると言った形ですね。
逆に1つのアカウントで登録するだけで無条件で使えたら勝手に証明書がどんどん登録されて、大変なことになりますよね・・・
アカウント整理
今回の検証では、以下の2つのAWSアカウントを使用します。
- アカウントA
- CA証明書を最初に作成・登録するアカウント
- アカウントB
- 同じCA証明書をマルチアカウントモードで登録するアカウント
この2つのアカウントで同一のCA証明書を使って、それぞれのアカウントでデバイス証明書の自動登録が動作するかを検証します。
簡単な絵としてはこんなイメージです。
前提条件
この記事では以下の環境・前提で実装を進めていきます。
- 2つのAWSアカウントを保持していること(アカウントA、アカウントB)
- AWS CLIがインストールされていること
- OpenSSLがインストールされていること
- 各アカウントで適切なIAM権限を持っていること
実装
CA証明書の作成
まずはOpenSSLで独自CA証明書を作成します。
CA証明書の作成コマンド
# CA証明書用のディレクトリを作成
mkdir -p ~/aws-iot-multi-account/ca
cd ~/aws-iot-multi-account/ca
# CAプライベートキーの生成
openssl genrsa -out rootCA.key 2048
# CA証明書の作成 (例: Common Name を MyDeviceRootCA とする場合)
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.pem -subj "/CN=MyDeviceRootCA"
これでCA証明書が作成できました。今度はこれをAWS IoT Coreに登録してみます。
アカウントAでマルチアカウントモードでCA証明書を登録する
まず、アカウントAでCA証明書を登録します。AWS IoT Coreのコンソール画面から、セキュリティ > 認証機関
を選択し、CA証明書を登録
ボタンをクリックします。
クリック後はマルチアカウントモードでCAを登録する
を選択し、先ほど作成したCA証明書をアップロードします。
またCAステータスはアクティブ
、証明書の自動登録はオン
にします。
この状態で右下の登録
ボタンを選択します。
コンソール上で完結するかつ、検証証明書も登録不要で、これだけで登録できるのは簡単ですね!
登録が完了したら一覧にCA証明書が追加されます。
アカウントAでのデバイス証明書の作成とテスト
まずはアカウントAで、簡単なスクリプトを書いて、証明書の自動登録が行われるのか試してみます。
登録したCAからデバイス証明書を作成します。
デバイス証明書の作成
# デバイス証明書用のディレクトリを作成
mkdir -p ~/aws-iot-multi-account/device
cd ~/aws-iot-multi-account/device
# デバイスプライベートキーの生成
openssl genrsa -out validDevice.key 2048
# 証明書署名要求(CSR)の作成(デバイスIDを埋め込む)
openssl req -new -key validDevice.key -out validDevice.csr -subj "/CN=ValidDevice/serialNumber=DEVICE123456"
# CSRをCA証明書で署名 (rootCA.pem と rootCA.key は事前に作成したものを参照)
openssl x509 -req -in validDevice.csr -CA ~/aws-iot-multi-account/ca/rootCA.pem -CAkey ~/aws-iot-multi-account/ca/rootCA.key -CAcreateserial -out validDevice.crt -days 365 -sha256
# デバイス証明書とCA証明書を結合
cat validDevice.crt ~/aws-iot-multi-account/ca/rootCA.pem > validDeviceAndCA.crt
AWS IoT Core接続情報の取得(アカウントA)
まず、アカウントAのAWS IoT Coreのエンドポイント情報を取得します。
# アカウントAにスイッチしてから実行
aws iot describe-endpoint --endpoint-type iot:Data-ATS
実行結果の例
{ "endpointAddress": "account-a-endpoint-ats.iot.ap-northeast-1.amazonaws.com" }
出力されたendpointAddress
をメモしておきます。
AWS のルートCA証明書をダウンロード
AWSのルートCA証明書をダウンロードします。接続時にはこのルートCA証明書を使用します。
curl -o ~/aws-iot-multi-account/device/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
アカウントAでの接続テストコードの実行
事前に使用するIoT Coreのライブラリをインストールしておきます。
pip install awsiotsdk
下記コードを記載して、実行します。
import json
import time
import threading
from awscrt import io, mqtt
from awsiot import mqtt_connection_builder
# AWS IoT Core endpoint (アカウントA)
IOT_ENDPOINT = "account-a-endpoint-ats.iot.ap-northeast-1.amazonaws.com"
# Certificate paths
CA_CERT = "AmazonRootCA1.pem" # AWS IoT Root CA
DEVICE_CERT = "validDeviceAndCA.crt" # Certificate chain
DEVICE_KEY = "validDevice.key"
# Device ID
CLIENT_ID = "TestDevice001"
# Topics
TELEMETRY_TOPIC = f"device/{CLIENT_ID}/telemetry"
COMMAND_TOPIC = f"device/{CLIENT_ID}/commands"
# Global variables
received_count = 0
received_all_event = threading.Event()
# Callback when connection is accidentally lost
def on_connection_interrupted(connection, error, **kwargs):
print(f"Connection interrupted. error: {error}")
# Callback when an interrupted connection is re-established
def on_connection_resumed(connection, return_code, session_present, **kwargs):
print(f"Connection resumed. return_code: {return_code} session_present: {session_present}")
if return_code == mqtt.ConnectReturnCode.ACCEPTED and not session_present:
print("Session did not persist. Resubscribing to existing topics...")
resubscribe_future, _ = connection.resubscribe_existing_topics()
resubscribe_future.add_done_callback(on_resubscribe_complete)
def on_resubscribe_complete(resubscribe_future):
resubscribe_results = resubscribe_future.result()
print(f"Resubscribe results: {resubscribe_results}")
for topic, qos in resubscribe_results['topics']:
if qos is None:
print(f"Server rejected resubscribe to topic: {topic}")
# Callback when the subscribed topic receives a message
def on_message_received(topic, payload, dup, qos, retain, **kwargs):
print(f"Received message on topic {topic}: {payload.decode()}")
global received_count
received_count += 1
if __name__ == '__main__':
# Spin up resources
event_loop_group = io.EventLoopGroup(1)
host_resolver = io.DefaultHostResolver(event_loop_group)
client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver)
# Create MQTT connection
mqtt_connection = mqtt_connection_builder.mtls_from_path(
endpoint=IOT_ENDPOINT,
cert_filepath=DEVICE_CERT,
pri_key_filepath=DEVICE_KEY,
client_bootstrap=client_bootstrap,
ca_filepath=CA_CERT,
on_connection_interrupted=on_connection_interrupted,
on_connection_resumed=on_connection_resumed,
client_id=CLIENT_ID,
clean_session=False,
keep_alive_secs=30
)
print(f"Connecting to {IOT_ENDPOINT} with client ID '{CLIENT_ID}'...")
# Connect
connect_future = mqtt_connection.connect()
# Future.result() waits until a result is available
connect_future.result()
print("Connected!")
# Subscribe to command topic
print(f"Subscribing to topic '{COMMAND_TOPIC}'...")
subscribe_future, packet_id = mqtt_connection.subscribe(
topic=COMMAND_TOPIC,
qos=mqtt.QoS.AT_LEAST_ONCE,
callback=on_message_received
)
subscribe_result = subscribe_future.result()
print(f"Subscribed with {str(subscribe_result['qos'])}")
# Publish test messages
print(f"Publishing messages to topic '{TELEMETRY_TOPIC}'...")
message_count = 5
for i in range(message_count):
test_message = {
"deviceId": CLIENT_ID,
"timestamp": int(time.time()),
"message": f"Test message {i+1}/{message_count}",
"temperature": 20.0 + i,
"humidity": 50.0 + i
}
message_json = json.dumps(test_message)
mqtt_connection.publish(
topic=TELEMETRY_TOPIC,
payload=message_json,
qos=mqtt.QoS.AT_LEAST_ONCE
)
print(f"Published: {message_json}")
time.sleep(1)
# Keep the connection alive
print("\nConnection established. Press Ctrl+C to disconnect...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nDisconnecting...")
# Disconnect
disconnect_future = mqtt_connection.disconnect()
disconnect_future.result()
print("Disconnected!")
コードはパスやエンドポイントの値に応じて修正してください。
実行すると、下記接続エラーが出ます。
awscrt.exceptions.AwsCrtError: AWS_ERROR_MQTT_UNEXPECTED_HANGUP: The connection was closed unexpectedly.
エラーは出ますが、初回接続時にデバイスの証明書は登録されているのでアカウントAのコンソールを確認してみます。
無事登録されていますね!!これでアカウントAでの動作確認ができました。
今回はマルチアカウントで同一CA証明書を作成することに主眼を置いているので、Lambda関数やプロビジョニングテンプレートを使って有効化などは行いません。
もし行いたい場合は下記ブログをご参照いただければと思います。
アカウントBでも同じCA証明書を登録してみる
アカウントBでも同じCA証明書をマルチアカウントモードで登録して、同じCA証明書で発行したデバイス証明書が使用できるかを検証してみます。
アカウントBでのCA証明書登録
アカウントBにログインして、AWS IoT Coreのコンソールからセキュリティ > 認証機関
を選択し、CA証明書を登録
ボタンをクリックします。
マルチアカウントモードでCAを登録する
を選択し、アカウントAで使用したのと同じCA証明書(rootCA.pem)をアップロードします。
CAステータスはアクティブ
、証明書の自動登録はオン
にして、登録
ボタンを選択します。
同じCA証明書がアカウントBでも登録できました!同一証明書のため、CA証明書 IDも一致しています。
アカウントBでのデバイス証明書作成とテスト
今度はアカウントBで、同じCA証明書を使って別のデバイス証明書を作成してみます。
cd ~/aws-iot-multi-account/device
# アカウントB用のデバイスプライベートキーの生成
openssl genrsa -out deviceB.key 2048
# 証明書署名要求(CSR)の作成
openssl req -new -key deviceB.key -out deviceB.csr -subj "/CN=DeviceB/serialNumber=DEVICEB001"
# CSRを同じCA証明書で署名(重要:同じrootCA.pemとrootCA.keyを使用)
openssl x509 -req -in deviceB.csr -CA ~/aws-iot-multi-account/ca/rootCA.pem -CAkey ~/aws-iot-multi-account/ca/rootCA.key -CAcreateserial -out deviceB.crt -days 365 -sha256
# デバイス証明書とCA証明書を結合
cat deviceB.crt ~/aws-iot-multi-account/ca/rootCA.pem > deviceBAndCA.crt
アカウントBでの接続テスト
アカウントBのエンドポイントを取得します。
# アカウントBにスイッチしてから実行
aws iot describe-endpoint --endpoint-type iot:Data-ATS
実行結果の例:
{ "endpointAddress": "account-b-endpoint-ats.iot.ap-northeast-1.amazonaws.com" }
アカウントB用にPythonスクリプトを修正して実行します。
# AWS IoT Core endpoint (アカウントB)
IOT_ENDPOINT = "account-b-endpoint-ats.iot.ap-northeast-1.amazonaws.com"
# Certificate paths
CA_CERT = "AmazonRootCA1.pem" # AWS IoT Root CA
DEVICE_CERT = "deviceBAndCA.crt" # アカウントB用証明書チェーン
DEVICE_KEY = "deviceB.key"
# Device ID
CLIENT_ID = "DeviceB001"
実行すると同様にエラーが出ますが、アカウントBのコンソールで証明書が登録されているかを確認してみます。
awscrt.exceptions.AwsCrtError: AWS_ERROR_MQTT_UNEXPECTED_HANGUP: The connection was closed unexpectedly.
アカウントBでも同じCA証明書で発行したデバイス証明書が自動登録されました!!
マルチアカウントモードで登録することで、複数のAWSアカウント間で同一のCA証明書を使用できることが無事確認できましたね!!
SNI拡張について
マルチアカウントモード = SNI_ONLY
モードでもあるため補足に記載します。
AWS IoT CoreのMQTT接続では、SNI拡張を使用してエンドポイントを指定する必要があります。ただ、AWS IoT Device SDKを使っている場合は、取得したエンドポイントを引数で渡してMQTTクライアントを生成すれば、問題なく接続可能なので、そこまで意識することはないかもしれませんね。
SNI拡張が要求されることについては下記ドキュメントに記載があるので、気になる方はご参照ください。
おわりに
今回はAWS IoT CoreでマルチアカウントのCA証明書登録について、2つのAWSアカウントを使って実際に検証してみました。
最初は「1つのアカウントで登録したら全アカウントで使える」というウルトラ勘違いをしていましたが、実際に手を動かして検証することで理解できました。
以下がポイントかと思います!
- 同一のCA証明書を複数のアカウントで使用するには、各アカウントでマルチアカウントモードでの登録が必要
- 各アカウントで登録された同一CA証明書で発行されたデバイス証明書は、それぞれのアカウントで自動登録が動作する
- マルチアカウントモード =
SNI_ONLY
モードでもあるが、SNI拡張については、SDKを使っていればそれほど意識する必要がない
検証証明書の登録なしに複数アカウントで同一CA証明書を登録できるのは便利ですよね。複数会社が存在して商流が複雑なケースや開発・検証・本番で同一CA証明書を使用できるのはありがたいと思います。
本記事を最後までご覧いただきありがとうございましたー!!
少しでもお役に立てれば幸いです。