AWS IoT Core Simplified – Part 2: URL Presigned
این قسمت 2 از مجموعه مقالات در مورد IoT Core است:
قطعات در راه است:
قسمت 3: با استفاده از یک مجوز سفارشی متصل شوید
بخش 4: قوانین موضوع
با استفاده از یک آدرس اینترنتی تعیین شده متصل شوید
مشابه S3، امکان ایجاد یک url تعیین شده برای IoT Core وجود دارد. تا زمانی که شناسه کلاینت در هر کلاینت متفاوت باشد، حتی میتوانید این url تعیینشده را در حافظه پنهان ذخیره کنید و دوباره از آن برای چندین مشتری استفاده کنید. اگر کلاینت را با شناسه کلاینت که از قبل در حال استفاده است وصل کنید، کلاینت دیگر قطع خواهد شد. این می تواند یک روش عالی برای ارائه محتوا به مشتریان باشد، اما همچنین به آنها اجازه می دهد تا در صورت نیاز مورد استفاده شما، مطالب را خودشان منتشر کنند. در حالی که این یک روش فوق العاده آسان است، اما منعطف ترین راه برای استفاده از آن از نظر مجوزها نیست. لطفاً توجه داشته باشید که این فقط برای آدرسهای وب سوکت کار میکند، نه برای اتصالات معمولی MQTT.
مجوزها
مهم است که بدانیم نشانی اینترنتی تعیین شده دارای مجوزهای مشابه نقشی است که برای امضای آن استفاده شده است. یعنی همین همه مشتریان دقیقاً مجوزهای مشابهی خواهند داشت مگر اینکه به طور خاص نقش متفاوتی را در هر مورد استفاده ایجاد کرده و از آن استفاده کنید. به عنوان مثال، اگر Lambda شما دارای نقشی باشد که اجازه می دهد با هر شناسه مشتری در هر موضوعی ارتباط برقرار کنید، هر کلاینت می تواند به هر موضوعی متصل شود. در برخی موارد این کاملاً خوب است، اما مطمئن شوید که برای مورد استفاده شما مناسب است. بدیهی است که شما می توانید (و باید) لامبدا خود را محدود کنید تا فقط به آنچه می خواهید مشتریان خود اجازه انجام دهند.
برای مثال، اگر وبسایتی دارید که میخواهید بهروزرسانیهای اخبار زنده را به کاربر نهایی ارسال کنید، میتوانید خطمشی با این موارد در آن داشته باشید:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Subscribe",
"iot:Receive"
],
"Resource": [
"arn:aws:iot:{region}:{account-id}:client/user-*",
"arn:aws:iot:{region}:{account-id}:topicfilter/news",
"arn:aws:iot:{region}:{account-id}:topic/news"
]
}
]
}
این به مشتری اجازه می دهد تا زمانی که با هر شناسه مشتری شروع می شود، ارتباط برقرار کند کاربر- (شما می توانید یک uuid ایجاد کنید تا آن را تصادفی کنید)، و اجازه می دهد در موضوع “اخبار” مشترک شوید و پیام هایی را از طریق آن دریافت کنید. این در حال حاضر اساساً یک اتصال فقط خواندنی است زیرا مشتری مجاز به انتشار هیچ پیامی با این خط مشی نیست.
میتوانید نقش جداگانهای برای ناشران خود داشته باشید که به شما امکان میدهد بهروزرسانیهایی برای موضوع بنویسید:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect",
"iot:Publish"
],
"Resource": [
"arn:aws:iot:{region}:{account-id}:client/publisher-*",
"arn:aws:iot:{region}:{account-id}:topic/news"
]
}
]
}
از طرف دیگر، می توانید به جای انجام این کار با کلاینت متصل به MQTT، پیامی را با استفاده از AWS SDK منتشر کنید. این iot: انتشار بدیهی است که مجوز موضوع مربوطه هنوز مورد نیاز است.
کد
به نظر می رسد که این در مستندات کاملاً پنهان است، اما من نمونه کدی را پیدا کردم. من این را به چیزی تبدیل کردم که از ابزارهای فعلی AWS استفاده می کند که آن را بسیار فشرده تر و خواناتر می کند. نمونههای کد در تایپ اسکریپت نوشته شدهاند، توصیه میکنم از چارچوب بدون سرور با esbuild برای استقرار کد استفاده کنید تا بتوانید آن را با زمان اجرا گره در لامبدا اجرا کنید.
import * as crypto from 'node:crypto';
import { type BinaryLike } from 'node:crypto';
import { Sha256 } from '@aws-crypto/sha256-js';
import { type AwsCredentialIdentity } from '@aws-sdk/types';
import { SignatureV4 } from '@smithy/signature-v4';
const sha256 = (data: BinaryLike): string =>
crypto.createHash('sha256').update(data).digest().toString('hex');
const toQueryString = (queryStrings: Record<string, string>): string => Object.entries(queryStrings)
.map(([key, value]) => `${key}=${value}`)
.join('&');
export const getSignedUrl = async (
host: string,
region: string,
credentials: AwsCredentialIdentity,
expiresIn = 900,
): Promise<string> => {
const service = 'iotdevicegateway';
const algorithm = 'AWS4-HMAC-SHA256';
const sigV4 = new SignatureV4({
sha256: Sha256,
service,
region,
credentials,
});
const date = new Date().toISOString().replaceAll(/[:-]|\.\d{3}/gu, '');
const credentialScope = `${date.slice(0, 8)}/${region}/${service}/aws4_request`;
const parameters: Record<string, string> = {
'X-Amz-Algorithm': algorithm,
'X-Amz-Credential': `${encodeURIComponent(`${credentials.accessKeyId}/${credentialScope}`)}`,
'X-Amz-Date': date,
'X-Amz-Expires': expiresIn.toString(),
'X-Amz-SignedHeaders': 'host',
};
const path = '/mqtt';
const headers = `host:${host}\n`;
const canonicalRequest = `GET\n${path}\n${toQueryString(parameters)}\n${headers}\nhost\n${sha256('')}`;
const stringToSign = `${algorithm}\n${date}\n${credentialScope}\n${sha256(canonicalRequest)}`;
parameters['X-Amz-Signature'] = await sigV4.sign(stringToSign);
if (credentials.sessionToken) {
parameters['X-Amz-Security-Token'] = encodeURIComponent(credentials.sessionToken);
}
return `wss://${host}${path}?${toQueryString(parameters)}`;
};
اکنون می توانم یک Lambda با کد زیر ایجاد کنم:
import { type APIGatewayProxyResultV2 } from 'aws-lambda';
import { getSignedUrl } from '../../Service/IoTCore.js';
export const handle = async (): Promise<APIGatewayProxyResultV2> => ({
statusCode: 200,
body: JSON.stringify({
url: await getSignedUrl(process.env.AWS_IOT_HOST ?? '', process.env.AWS_DEFAULT_REGION ?? '', {
accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '',
sessionToken: process.env.AWS_SESSION_TOKEN ?? undefined,
}),
}),
});
و هنگام تماس با آن، یک URL دریافت میکنم که میتوانم از آن برای ارتباط با چیزی مانند Paho MQTT Client استفاده کنم. برای این مثال، من یک متغیر محیطی AWS_IOT_HOST را با نقطه پایانی IoT Core تنظیم کردهام (xxxxxxx-ats.iot.eu-west-1.amazonaws.com). نقطه پایانی را می توان با رفتن به سرویس IoT Core در داشبورد و سپس رفتن به تنظیمات پیدا کرد:
البته می توانید از IoT Core SDK نیز برای بازیابی نقطه پایانی استفاده کنید.
اکنون که راهی برای دریافت url تعیین شده دارید، می خواهید از آن برای اتصال به IoT Core استفاده کنید. برای انجام این کار از یک مرورگر، می توانید از کتابخانه Paho MQTT و کد زیر استفاده کنید:
const client = new Paho.MQTT.Client(url, clientId);
client.connect({
useSSL: true,
timeout: 3,
mqttVersion: 4,
onSuccess: function () {
console.log("connected");
},
onFailure: function () {
console.log("failed to connect");
},
});
client.onMessageArrived = function (message) {
console.log(message);
};
client.onConnectionLost = function (e) {
console.log("lost connection", e);
};
آدرس اینترنتی پاسخی است که از لامبدا شما دریافت می کنید. مطمئن شوید که clientId توسط خطمشی شما مجاز است زیرا در غیر این صورت از اتصال خودداری میکند.
می توانید در موضوعی مانند این مشترک شوید:
client.subscribe('updates/"');
و پیامی مانند این منتشر کنید:
const message = new Paho.MQTT.Message(payload);
message.destinationName = 'updates/messages';
client.send(message);
لطفاً توجه داشته باشید، یک بار دیگر، انجام هر کاری که توسط خطمشی شما مجاز نیست، منجر به قطع ارتباط میشود.
آدرسهای اینترنتی تعیینشده از لامبدا به دلیل نحوه کارکرد IAM فقط برای مدت محدودی کار میکنند، اما میتوان آن را در CDN مانند CloudFront برای چند دقیقه کش کرد. به این ترتیب شما نیازی به ایجاد یک URL جدید برای هر بازدید کننده ندارید. با پیکربندی من، URL های تعیین شده حداکثر تا 5 دقیقه معتبر هستند. در عمل، من آنها را به مدت 1 دقیقه کش می کنم تا در سمت بسیار امن باشند.
شما اکنون یک راه اساسی برای کار با IoT Core دارید که امکان برقراری ارتباط دو طرفه قدرتمند را فراهم می کند. خوش بگذره!
در بخش 3 توضیح خواهم داد که چگونه می توان از یک مجوز سفارشی برای انجام مجوزهای دقیق تر به ازای هر مشتری استفاده کرد.