【AWS IoT Core】 AWS CloudShellでデバイス証明書を一括で発行してみた
はじめに
こんにちは、コンサルティング部の神野です。
先日、「【AWS IoT Core】デバイスメーカーとデバイス利用者の2者間で認証の役割分担はどう決めるか考えてみた(独自CAとAWS管理CA、証明書管理のメリット・デメリットを比較検討)」という記事で、IoTデバイス認証の複数の方式について比較検討しました。
その中で「案2: デバイス利用者がAWS管理CAで証明書を発行するケース」について触れましたが、具体的な実装手順については「ちょっと深掘り4」セクションで簡単に説明するにとどまっていました。今回はその部分をもう少し詳しく、実際にAWS CloudShell(以下CloudShell)を使ってデバイス証明書を一括発行する方法について、手を動かして確認していきたいと思います!
前提条件・環境
今回の実装では、以下の環境・条件を前提としています。
- AWSアカウントを保有し、AWS IoT Coreが利用可能であること
- AWS CloudShellが利用可能であること
- S3及びAWS IoT Coreの権限が適切であること
また、本記事で扱うシナリオは、以前の記事で説明した「案2: デバイス利用者がAWS管理CAで証明書を発行するケース」を想定しています。つまり、デバイス利用者自身が自分のAWSアカウント内でAWS管理CAを使用して証明書を発行する構成となります。
メリットとしてはCloudShellを使用することで、特別な開発環境を用意する必要がないことです。また使用した時間に対して料金も発生しません。シンプルかつ小規模でまずはAWS IoT Coreを使用する際の有力な選択肢になるかと思います。
システム構成と処理フロー
今回実装するシステムの構成は以下のようなイメージです。
処理の流れとしては、
- AWS CloudShellでスクリプトを実行
- 指定した数だけ、AWS IoT Coreの
create-keys-and-certificate
APIを使用して証明書・秘密鍵を生成 - 生成した証明書・秘密鍵をデバイスID単位でS3バケットに保存
- 必要に応じて、AWS IoT Coreにモノ(Thing)を作成し、証明書と紐付け
シンプルな構成で検証しやすいかと思います。
実際の運用を考えた際に工夫や検討するポイントはあるかと思いますが、大量のデバイス証明書を発行することは十分可能です。
CloudShellでの実行
今回はCloudShell上で作業を進めていきます。
コンソール上の検索画面からCloudShell
と入力して、検索結果のCloudShell
を選択します。
下記のようにCloudShellの画面が表示されていればOKです!
この画面で作業を行なっていきます。
S3バケットの準備
まず、証明書と秘密鍵を安全に保管するためのS3バケットを準備します。
バケット作成とセキュリティ設定
バケット名は任意のバケット名とします。sample-yjinno-your-iot-certificates-bucket
といったところはお好みで設定ください。CloudShellで下記コマンド作成可能です。
aws s3 mb s3://sample-yjinno-your-iot-certificates-bucket
ポリシーの作成
証明書にアタッチするポリシーを事前に作成します。
検証のためポリシーはゆるゆるとしています。また今回は全てこのポリシーを参照する形としますが、要件に応じて権限はご検討ください。
cat << 'EOF' > simple_iot_policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Publish",
"iot:Subscribe",
"iot:Receive"
],
"Resource": "*"
}
]
}
EOF
aws iot create-policy \
--policy-name SimpleIoTDevicePolicy \
--policy-document file://simple_iot_policy.json
証明書一括発行スクリプトの実装
基本的なスクリプト構成
CloudShellで実行する証明書発行スクリプトを詳しく見ていきましょう。
#!/bin/bash
# --- 設定項目 ---
NUM_CERTIFICATES=10 # 発行する証明書の数
S3_BUCKET="sample-yjinno-your-iot-certificates-bucket" # 証明書を保管するS3バケット名 (事前に作成)
S3_PREFIX="certificates" # S3バケット内のプレフィックス (例: certificates/device-001/)
THING_NAME_PREFIX="my-device-" # モノの名前のプレフィックス (例: my-device-001)
IOT_POLICY_NAME="SimpleIoTDevicePolicy" # 事前に作成したIoTポリシー名
REGION="ap-northeast-1" # AWSリージョン
# --- スクリプト本体 ---
set -e # エラーが発生したらスクリプトを終了
echo "Starting certificate provisioning..."
echo "Number of certificates to create: ${NUM_CERTIFICATES}"
echo "Target S3 Bucket: s3://${S3_BUCKET}/${S3_PREFIX}"
echo "Thing name prefix: ${THING_NAME_PREFIX}"
echo "IoT Policy name: ${IOT_POLICY_NAME}"
echo "Region: ${REGION}"
# IoTポリシーの存在確認
echo "Checking if IoT policy '${IOT_POLICY_NAME}' exists..."
aws iot get-policy --policy-name "${IOT_POLICY_NAME}" --region "${REGION}" > /dev/null
if [ $? -ne 0 ]; then
echo "ERROR: IoT policy '${IOT_POLICY_NAME}' does not exist. Please create the policy first."
exit 1
fi
echo "IoT policy '${IOT_POLICY_NAME}' found."
# 一時作業ディレクトリ作成
WORK_DIR=$(mktemp -d)
echo "Working directory: ${WORK_DIR}"
cd "${WORK_DIR}"
for i in $(seq 1 ${NUM_CERTIFICATES})
do
DEVICE_ID=$(printf "%s%03d" "${THING_NAME_PREFIX}" "${i}")
echo "----------------------------------------"
echo "Processing device: ${DEVICE_ID}"
# 1. モノを作成 (存在しない場合 - オプション)
aws iot create-thing --thing-name "${DEVICE_ID}" --region "${REGION}" > /dev/null || echo "Thing ${DEVICE_ID} already exists or failed to create."
# 2. キーペアと証明書を作成 & 有効化
echo "Creating keys and certificate for ${DEVICE_ID}..."
OUTPUT=$(aws iot create-keys-and-certificate \
--set-as-active \
--region "${REGION}" \
--output json)
if [ $? -ne 0 ]; then
echo "ERROR: Failed to create keys and certificate for ${DEVICE_ID}. Skipping."
continue # エラーが発生したら次のループへ
fi
CERT_ARN=$(echo "${OUTPUT}" | jq -r '.certificateArn')
CERT_ID=$(echo "${OUTPUT}" | jq -r '.certificateId')
CERT_PEM=$(echo "${OUTPUT}" | jq -r '.certificatePem')
KEY_PAIR_PUBLIC=$(echo "${OUTPUT}" | jq -r '.keyPair.PublicKey')
KEY_PAIR_PRIVATE=$(echo "${OUTPUT}" | jq -r '.keyPair.PrivateKey') # ★★★ 秘密鍵 ★★★
echo "Certificate ID: ${CERT_ID}"
echo "Certificate ARN: ${CERT_ARN}"
# ファイルに保存
CERT_FILE="${DEVICE_ID}_${CERT_ID}_cert.pem"
PUB_KEY_FILE="${DEVICE_ID}_${CERT_ID}_pub.key"
PRV_KEY_FILE="${DEVICE_ID}_${CERT_ID}_prv.key" # ★★★ 秘密鍵ファイル ★★★
echo "${CERT_PEM}" > "${CERT_FILE}"
echo "${KEY_PAIR_PUBLIC}" > "${PUB_KEY_FILE}"
echo "${KEY_PAIR_PRIVATE}" > "${PRV_KEY_FILE}"
echo "Files created locally: ${CERT_FILE}, ${PUB_KEY_FILE}, ${PRV_KEY_FILE}"
# 3. 証明書をモノにアタッチ (オプション - モノを作成した場合)
echo "Attaching certificate ${CERT_ARN} to thing ${DEVICE_ID}..."
aws iot attach-thing-principal \
--thing-name "${DEVICE_ID}" \
--principal "${CERT_ARN}" \
--region "${REGION}"
if [ $? -ne 0 ]; then
echo "WARNING: Failed to attach certificate to thing ${DEVICE_ID}."
fi
# 4. 証明書にIoTポリシーをアタッチ ★★★ 新規追加 ★★★
echo "Attaching IoT policy '${IOT_POLICY_NAME}' to certificate ${CERT_ARN}..."
aws iot attach-principal-policy \
--policy-name "${IOT_POLICY_NAME}" \
--principal "${CERT_ARN}" \
--region "${REGION}"
if [ $? -ne 0 ]; then
echo "WARNING: Failed to attach IoT policy to certificate ${CERT_ARN}."
else
echo "IoT policy '${IOT_POLICY_NAME}' successfully attached to certificate."
fi
# 5. S3にアップロード (SSE-S3で暗号化)
S3_DEVICE_PATH="${S3_PREFIX}/${DEVICE_ID}"
echo "Uploading files to s3://${S3_BUCKET}/${S3_DEVICE_PATH}/ ..."
aws s3 cp "${CERT_FILE}" "s3://${S3_BUCKET}/${S3_DEVICE_PATH}/${CERT_FILE}" --sse AES256
aws s3 cp "${PUB_KEY_FILE}" "s3://${S3_BUCKET}/${S3_DEVICE_PATH}/${PUB_KEY_FILE}" --sse AES256
aws s3 cp "${PRV_KEY_FILE}" "s3://${S3_BUCKET}/${S3_DEVICE_PATH}/${PRV_KEY_FILE}" --sse AES256
if [ $? -ne 0 ]; then
echo "ERROR: Failed to upload files to S3 for ${DEVICE_ID}. Check permissions and bucket settings."
# エラー処理
continue
fi
echo "Successfully processed ${DEVICE_ID}."
# レート制限回避のための待機 (必要に応じて調整)
sleep 0.5
done
# 一時作業ディレクトリ削除
echo "Cleaning up working directory: ${WORK_DIR}"
rm -rf "${WORK_DIR}"
echo "----------------------------------------"
echo "Certificate provisioning finished."
主要な変数設定
スクリプトの冒頭で定義している設定項目は、環境に応じて柔軟に変更できるように構成しています。
変数名 | デフォルト値 | 説明 |
---|---|---|
NUM_CERTIFICATES |
10 | 発行する証明書の数 |
S3_BUCKET |
your-iot-certificates-bucket | 証明書保管用S3バケット名 |
S3_PREFIX |
certificates | S3内のフォルダプレフィックス |
THING_NAME_PREFIX |
my-device- | デバイス名のプレフィックス |
IOT_POLICY_NAME |
SimpleIoTDevicePolicy | 使用するポリシー名 |
REGION |
ap-northeast-1 | AWSリージョン |
これらの変数を変更することで、異なる環境や要件に対応できるようにしています。例えば、NUM_CERTIFICATES
を100に変更すれば一度に100個の証明書を発行できますし、THING_NAME_PREFIX
を変更することで異なるプロダクトラインの命名規則にも対応させることはできます。ただ連番が使用できる前提としています。ここは命名規則の要件に応じて検討しましょう。
ただ直接編集するような作りなので、実行に応じて可動したいなどあれば引数で、設定したい値を渡して実行するような作りにしてもいいかと思います。
頻度や柔軟性をどこまで求めるかで作りは検討できるポイントかなと思います。
あくまで今回は簡易的なスクリプトで実行する際のイメージとなります!
各デバイスでの処理(ループ内)
AWS IoT CoreのAPIを使用して、モノの作成や証明書、秘密鍵を作成します。
APIベースで作成できるのは便利ですね。
- モノの作成
- キーペアと証明書を作成 & 有効化
- AWS IoT Core APIで証明書・秘密鍵・公開鍵を生成
- 証明書を自動的にアクティブ状態に設定
- 証明書をモノにアタッチ
- S3にアップロード
- 証明書・公開鍵・秘密鍵を保存
- デバイス別フォルダに整理して格納
アウトプット・結果イメージ
S3バケットの構造
スクリプト実行後、S3バケットには以下のような構造でファイルが保存されます。
s3://your-iot-certificates-bucket/
└── certificates/
├── my-device-001/
│ ├── my-device-001_[証明書ID]_cert.pem
│ ├── my-device-001_[証明書ID]_pub.key
│ └── my-device-001_[証明書ID]_prv.key
└── my-device-002/
└── ...
デバイスごとにフォルダが作成され、証明書・公開鍵・秘密鍵がセットで保管される構成になっています。ファイル名には証明書IDが含まれるため、後からの管理や追跡も容易です。
実行時の画面出力
スクリプト実行中は、以下のような出力で1つずつのデバイスについて進捗状況を確認できます。
Processing device: my-device-XXX
Creating keys and certificate for my-device-XXX...
Certificate ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Certificate ARN: arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Files created locally: my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_cert.pem, my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_pub.key, my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_prv.key
Attaching certificate arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx to thing my-device-XXX...
Attaching IoT policy 'SimpleIoTDevicePolicy' to certificate arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:cert/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...
IoT policy 'SimpleIoTDevicePolicy' successfully attached to certificate.
Uploading files to s3://your-iot-certificates-bucket/certificates/my-device-XXX/ ...
upload: ./my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_cert.pem to s3://your-iot-certificates-bucket/certificates/my-device-XXX/my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_cert.pem
upload: ./my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_pub.key to s3://your-iot-certificates-bucket/certificates/my-device-XXX/my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_pub.key
upload: ./my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_prv.key to s3://your-iot-certificates-bucket/certificates/my-device-XXX/my-device-XXX_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_prv.key
Successfully processed my-device-XXX.
スクリプト実行
スクリプト作成
下記コマンドでエディターを開き、コードをコピーしてスクリプトを作成します。
nano iot_certificate_provisioning.sh
作成したら、実行権限を付与します。
chmod +x iot_certificate_provisioning.sh
もしスクリプト内の設定項目を環境に合わせて修正したい箇所があれば修正します。
NUM_CERTIFICATES=10
S3_BUCKET="your-iot-certificates-bucket"
THING_NAME_PREFIX="MyDevice"
REGION="ap-northeast-1"
準備が整ったらスクリプトの実行を行います。
./iot_certificate_provisioning.sh
実行すると最後にCertificate provisioning finished.
と出ていれば完了です!
実行結果の確認
スクリプト実行後、以下の方法で結果を確認できます。
まずはS3に秘密鍵など保存されているか確認してみます。
aws s3 ls s3://sample-yjinno-your-iot-certificates-bucket/certificates/ --recursive
しっかりと保存されていますね!
実行結果として、S3バケットには以下のようなパスでファイルが保存されます。
s3://your-iot-certificates-bucket/
└── certificates/
├── MyDevice-001/
│ ├── MyDevice-001_[証明書ID]_cert.pem
│ ├── MyDevice-001_[証明書ID]_pub.key
│ └── MyDevice-001_[証明書ID]_prv.key
├── MyDevice-002/
│ ├── MyDevice-002_[証明書ID]_cert.pem
│ ├── MyDevice-002_[証明書ID]_pub.key
│ └── MyDevice-002_[証明書ID]_prv.key
└── ...
下記スクリーンショットのように格納されています。
またモノも作成できているか下記コマンドで確認可能です。
aws iot list-things
コンソール上でも作成されているか確認してみます。
おおお、10個作られていますね。
また、モノと証明書が紐づいているか、my-device-001
を確認してみます。
モノ
証明書
ポリシー
全て問題なく作成および紐付けされていますね!!
やはりCloudShellを使ってクラウド上でシンプルに作成できるのは魅力ですね。
デバイスでの検証
最後に作成された証明書・秘密鍵をS3からダウンロードして、ローカル上をデバイスと見立てて、AWS IoT Coreに通信してみましょう。
S3からファイルダウンロード
my-device-001_xxx_cert.pem
と書いてあるデバイス証明書と、my-device-001_xxx_prv.key
と書いてある秘密鍵をダウンロードして、後ほど使用するPythonスクリプトと同階層に配置します。
今回は検証で1デバイスで手動でダウンロードして配置していますが、大量のデバイスに配置するとなると工夫すべきポイントはあるかと思います。また、デバイスにどういった経路で安全に証明書や秘密鍵を配置するかも考える必要があると感じました。
AWS IoT Coreエンドポイントの取得
デバイス側で接続するために、AWS IoT Coreのエンドポイントを取得しておく必要があります。
CloudShellで以下のコマンドを実行します。
# AWS IoT Coreエンドポイントを取得
aws iot describe-endpoint --endpoint-type iot:Data-ATS
実行結果
{
"endpointAddress": "xxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com"
}
このendpointAddress
の値をメモしておいてください。デバイス側のスクリプトで使用します。
AWS のルートCA証明書をダウンロード
AWSのルートCA証明書をダウンロードします。接続時にはこのルートCA証明書を使用します。
これも後ほど使用するPythonスクリプトと同階層に配置します。
curl -o ./AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem
Pythonスクリプト
証明書や秘密鍵の名前は取得してきたものに変更しましょう。
今回は一個だから手作業していますが、証明書・秘密鍵・クライアントIDの名前なども動的に変更できる必要がありますね。動的に対応できるよう環境変数を証明書をダウンロードするタイミングで合わせて設定することで管理するのも手かと思います。
大量のデバイスに配布するとなった際には検討するポイントの1つですね。
インストールするライブラリ
事前に使用するライブラリをインストールします。
pip install awsiotsdk
処理は下記となります。長いので折りたたんでいますが、
テスト用にsample/test
トピックに3通メッセージを送ります。
処理全体
import time
import json
import uuid
from awscrt import mqtt
from awsiot import mqtt_connection_builder
class DeviceTestClient:
def __init__(self, endpoint, cert_path, key_path, ca_path='.pem'):
self.endpoint = endpoint
self.cert_path = cert_path
self.key_path = key_path
self.ca_path = ca_path
self.client_id = "my-device-001"
self.mqtt_connection = None
def connect(self):
"""デバイスをAWS IoT Coreに接続"""
print(f"デバイス証明書で接続テスト開始...")
print(f"クライアントID: {self.client_id}")
# MQTT接続を作成
self.mqtt_connection = mqtt_connection_builder.mtls_from_path(
endpoint=self.endpoint,
port=8883,
cert_filepath=self.cert_path,
pri_key_filepath=self.key_path,
ca_filepath=self.ca_path,
client_id=self.client_id,
clean_session=False,
keep_alive_secs=30
)
print(f"エンドポイント {self.endpoint} に接続中...")
connected_future = self.mqtt_connection.connect()
connected_future.result()
print("✅ 接続成功!デバイス証明書で正常に認証されました!")
def publish_test_message(self, topic="test/topic", message_count=3):
"""テストメッセージを送信"""
print(f"\n📤 トピック '{topic}' にテストメッセージを送信します...")
for i in range(message_count):
# テストメッセージの作成
test_message = {
"timestamp": int(time.time()),
"device_id": self.client_id,
"message_number": i + 1,
"status": "my-device-001 Test",
"data": {
"temperature": 25.5 + i,
"humidity": 60.0 + i * 2,
"battery": 85 - i
}
}
# JSON形式でメッセージを送信
message_json = json.dumps(test_message, indent=2)
print(f"\n送信メッセージ {i + 1}:")
print(message_json)
# メッセージをパブリッシュ
publish_future, packet_id = self.mqtt_connection.publish(
topic=topic,
payload=message_json,
qos=mqtt.QoS.AT_LEAST_ONCE
)
# パブリッシュ完了を待機
publish_future.result()
print(f"✅ メッセージ {i + 1} 送信完了 (Packet ID: {packet_id})")
# 次のメッセージまで少し待機
if i < message_count - 1:
time.sleep(2)
def disconnect(self):
"""接続を切断"""
if self.mqtt_connection:
print("\n🔌 接続を切断中...")
disconnect_future = self.mqtt_connection.disconnect()
disconnect_future.result()
print("✅ 切断完了")
def main():
# エンドポイントを指定
endpoint = "xxx-ats.iot.ap-northeast-1.amazonaws.com" # 実際の値に変更してください
print("=== AWS IoT Core デバイス接続テスト ===")
# 作成したデバイス証明書を使用
test_client = DeviceTestClient(
endpoint=endpoint,
cert_path='my-device-001_xxx_cert.pem',
key_path='my-device-001_xxx_prv.key',
ca_path='AmazonRootCA1.pem' # ダウンロードしたルートCA
)
try:
# 接続テスト
test_client.connect()
# テストメッセージ送信
test_client.publish_test_message(
topic="sample/test",
message_count=3
)
except Exception as e:
print(f"❌ エラーが発生しました: {e}")
finally:
test_client.disconnect()
if __name__ == "__main__":
main()
変更する箇所
実行する前に下記を変更します。
client_id
- 使用するモノの名前へ
cert_path
- 使用する証明書のパス
key_path
- 使用する秘密鍵のパス
endpoint
- 取得したエンドポイント
sample/test
トピックをテストクライアントでサブスクライブして、メッセージが届くか確認してみます。
実行コマンド
python test_device_connection.py
実行結果
適切に受信できていますね!
おわりに
今回は、以前の記事で触れたAWS CloudShellを使った具体的な実装方法を実施してみました!
CloudShellを使った方法は、独自CAを構築する場合に比べてシンプルかつ低コストで実装できることが大きなメリットかなと思います。まずは小さく始めたいというケースには非常に適していると思います。
一方で、スクリプトの作り込みであったり、秘密鍵の管理やセキュリティ対策(保管しているS3バケットの管理など)、また秘密鍵をデバイスへ安全に転送する、大量のデバイスへどう配布するかなどは要件に応じて考えるポイントかと思います。
また今回紹介したスクリプトはあくまでサンプルなので、実際にどういった作りにするのか要件に応じて検討が必要です。
CloudShellを使った証明書発行案のメリット・デメリット比較検討は以前記載した記事をご参照ください。
また、デバイス数が大幅に増加する場合や、より高度な自動化が必要になった場合は、フリートプロビジョニングのような手法への移行も検討すべきかもしれません。(フリートプロビジョニングの詳細については、別途記事を書いておりますので、そちらもご参照ください)
少しでも本記事が参考になったら幸いです!最後までご覧いただきありがとうございましたー!!