概要
IoT Core はカスタムオーソライザーを使用して認証し、IoT Core に接続することができます。
カスタムオーソライザーは署名の検証を行うことができ、また実装方法によっては
ユーザー名とパスワードを使用して認証することができます。
本記事は、この実装に関する備忘録です。
前提条件
- Mac 環境であること
- AWS のリソース作成は CDK を利用
- Iot Core への接続認証は MQTTX のソフトを利用
手順
CDK のプロジェクトを作成
適当なディレクトリを作成します。
mkdir cdk-iot-custom-auth && cd cdk-iot-custom-auth
CDK プロジェクトを作成します。
cdk init app --language typescript
オーソライザー用の Lambda の作成
Lambda のコード格納先のディレクトリを作成します。
mkdir -p lambda/custom-auth && touch lambda/custom-auth/index.ts
AWS のドキュメントをもとに認証様の関数を作成する。参照
この Lambda は IoT Core のポリシーを返却する必要があります。
password=test の場合、publish/subscribe などの操作を許可する IoT ポリシーを返却します。
exports.handler = function (event, context, callback) {
var uname = event.protocolData.mqtt.username;
var pwd = event.protocolData.mqtt.password;
var buff = Buffer.from(pwd, "base64");
var passwd = buff.toString("ascii");
switch (passwd) {
case "test":
callback(null, generateAuthResponse(passwd, "Allow"));
break;
default:
callback(null, generateAuthResponse(passwd, "Deny"));
}
};
interface AuthResponse {
isAuthenticated: boolean;
principalId: string;
disconnectAfterInSeconds: number;
refreshAfterInSeconds: number;
policyDocuments: [
{
Version: string;
Statement: Statement[];
}
];
}
interface Statement {
Action: string;
Effect: "Allow" | "Deny";
Resource: string;
}
// Helper function to generate the authorization response.
const generateAuthResponse = function (token, effect) {
const authResponse: AuthResponse = {
isAuthenticated: effect === "Allow" ? true : false,
principalId: "TEST123",
policyDocuments: [
{
Version: "2012-10-17",
Statement: [],
},
],
disconnectAfterInSeconds: 3600,
refreshAfterInSeconds: 300,
};
const publishStatement: Statement = {
Action: "iot:Connect",
Effect: effect,
Resource: "*",
};
const connectStatement: Statement = {
Action: "iot:Publish",
Effect: effect,
Resource: "*",
};
const subscribeStatement: Statement = {
Action: "iot:Subscribe",
Effect: effect,
Resource: "*",
};
const receiveStatement: Statement = {
Action: "iot:Receive",
Effect: effect,
Resource: "*",
};
authResponse.policyDocuments[0].Statement[0] = connectStatement;
authResponse.policyDocuments[0].Statement[1] = publishStatement;
authResponse.policyDocuments[0].Statement[2] = subscribeStatement;
authResponse.policyDocuments[0].Statement[3] = receiveStatement;
return authResponse;
};
password=test の場合、publish/subscribe などを許可する、iot ポリシーを返却するようにします。
上記のコードで返却される、Iot ポリシーは以下になります。
{
"isAuthenticated": true,
"principalId": "TEST123",
"policyDocuments": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "iot:Publish",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "iot:Connect",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "iot:Subscribe",
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "iot:Receive",
"Effect": "Allow",
"Resource": "*"
}
]
},
"disconnectAfterInSeconds": 3600,
"refreshAfterInSeconds": 300
}
CDK にて Lambda を定義します。
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
export class CdkIotCustomAuthStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// オーソライザー用の関数を作成
const iotAuthFunc = new nodejs.NodejsFunction(this, "IotAuthFunc", {
functionName: "iot-custom-auth",
runtime: lambda.Runtime.NODEJS_22_X,
entry: "lambda/custom-auth/index.ts",
});
}
}
オーソライザー用の公開鍵と秘密鍵の作成
カスタムオーソライザーでは、認証に公開鍵と秘密鍵が必要なので、それぞれ作成していきます。
-
鍵を格納するためのディレクトリを作成します
mkdir secret
-
秘密鍵を作成します。
openssl genrsa -out secret/private-key.pem 4096
-
秘密鍵に問題がないか検証します。
openssl rsa -check -in secret/private-key.pem -noout
-
公開鍵を作成します。
openssl rsa -in secret/private-key.pem -pubout -out secret/public-key.pem
-
公開鍵に問題がないか確認します。
openssl pkey -inform PEM -pubin -in secret/public-key.pem -noout
特にエラーが表示されなければ問題ないです。
カスタムオーソライザーの作成
CDK にて、カスタムオーソライザーを定義します。
この際、署名の検証を有効にします。
import * as iot from "aws-cdk-lib/aws-iot";
//<一部省略>
// カスタムオーソライザーの作成
const iotCustomAuthorizer = new iot.CfnAuthorizer(this, "IotCustomAuthorizer", {
authorizerName: "iot-custom-auth",
status: "ACTIVE",
authorizerFunctionArn: iotAuthFunc.functionArn,
signingDisabled: false,
tokenKeyName: "authorizer-token",
tokenSigningPublicKeys: {
Key1: process.env.PUBLIC_KEY || "",
},
});
また、PUBLIC_KEY の環境変数を設定するよう、以下のコマンドも実行しておきます。
export PUBLIC_KEY="$(cat secret/public-key.pem)"
Iot Core に Lambda 関数の呼び出しを許可する。
カスタムオーソライザーに Lambda 関数を実行するための許可を付与します
// IotにLambda関数の実行許可を付与
iotAuthFunc.addPermission("iotAuthFuncPermission", {
principal: new iam.ServicePrincipal("iot.amazonaws.com"),
sourceArn: iotCustomAuthorizer.attrArn,
action: "lambda:InvokeFunction",
});
AWS のリソースを生成
CDK の準備が完了しましたので、cdk deploy
を実行し、リソースを生成していきます。
この際、PUBLIC_KEY
に環境変数を設定しているか、再確認してください。
MQTTX を使用して接続確認をする
MQTTX をインストールし、MQTTX のアプリを開きます。
インストールは以下の記事がわかりやすかいと思います。
接続に以下の情報が必要になるので、各情報を取得します。
- AWS の Iot エンドポイント
- トークンの署名
AWS の Iot エンドポイントの確認
CLI の以下のコマンドを実行し、エンドポイントの値を取得します。
aws iot describe-endpoint --endpoint-type iot:Data-ATS
トークンの署名
次のコマンドを実行してトークンに署名をしていきます。
echo -n TOKEN_VALUE | openssl dgst -sha256 -sign PEM encoded RSA private key | openssl base64
本環境で実際に実行したコマンドはこちらになります。
また、トークンの署名の値は変数に格納しておきます。
TOKEN_SIGNATURE=$(echo -n "Key1" | openssl dgst -sha256 -sign secret/private-key.pem | openssl base64 -A)
署名したトークンの値を使用して MQTT 接続するには、署名したトークンの値を URL エンコードする必要があるので、
以下のコマンドを実行し、エンコードしていきます。
echo -n $TOKEN_SIGNATURE | jq -sRr @uri
出力結果をメモしておきます。
MQTTX から Iot Core への接続
接続に必要な情報が集まったので、MQTTX から Iot Core に接続していきます。
-
MQTTX にて、「新しい接続」を押下する
-
各項目に以下の値を設定し、「接続」を押下する。
- 名前:<任意>
- ホスト:wss://
- Path:/mqtt
- ユーザ名:<ユーザ名>?x-amz-customauthorizer-name=<オーソライザー名>&x-amz-customauthorizer-signature=<エンコード後の TOKEN_SIGNATURE>&<トークンのキー名>=<トークンの値>
- 例:mqttuser?x-amz-customauthorizer-name=iot-custom-auth&x-amz-customauthorizer-signature=<エンコード後の TOKEN_SIGNATURE>&authorizer-token=Key1
- パスワード:<パスワード>
- 例:test
- SSL/TLS:有効
- SSL 証明書:無効
無事に接続できれば成功です!