AWS IoT Core와 통신을 하기 위해서 인증하는 방식은 몇 가지 존재한다.
보통 X.509 인증서 방식을 많이 사용하는데, 이 인증서를 이용한 인증 방식을 사용하지 못 할 경우 난감하다..
그래서 사용되는 게 보통 Cognito나 Custom Authentication인데 이번엔 이 사용자 인증을 통한 클라이언트 인증 및 권한을 부여하는 방법에 대해서 설명하겠다.
워크플로
1. 프로토콜(MQTT 또는 HTTP)을 사용하여 AWS IoT Core 엔드포인트에 연결한다. HTTP의 경우 헤더 필드 또는 쿼리 매개변수가 필요하고, MQTT의 경우 사용자 이름 및 비밀번호로 자격증명을 전달한다.
2. AWS IoT Core는 연결된 권한 부여자(Custom authorizers)를 확인해서, Custom Authorizers에 연결된 Lambda 함수를 트리거 한다.
3. Lambda 함수는 전달 받은 사용자 이름과 비밀번호를 확인하고 인증 결정을 내린다.
4. Lambda 함수는 AWS IoT Core 정책을 반환한다.
5. IoT Core는 Lambda 함수의 인증 결과(즉, 반환된 정책)에 따라 디바이스를 연결 또는 거부 한다.
간단히 하면,..
엔드포인트로 전달한 인증 정보(사용자 이름 및 비밀번호)가 Custom Authorizers에 연결된 Lambda로 전달되면 Lambda가 인증 정보가 맞는지 확인하고, 맞으면 Allow가 담긴 정책을 반환 / 틀리면 Deny를 반환한다.
IoT Core는 반환된 결과에 따라 디바이스를 연결 또는 거부한다.
구축 방법
Lambda 함수 생성
1. 런타임 NodeJs 22.x로 Lambda 함수를 생성한다.
2. 다음 코드를 복사 붙여넣기 하고 Deploy 한다.
// Named export 사용
export const handler = async (event, context) => {
// 이벤트 및 MQTT 데이터 디버깅
console.log("Event received:", JSON.stringify(event, null, 2));
console.log("Context received:", JSON.stringify(context, null, 2));
const username = event.protocolData.mqtt.username;
const password = event.protocolData.mqtt.password;
const clientid = event.protocolData.mqtt.clientId;
// Username과 Password 출력
console.log("Username:", username);
console.log("Encoded Password:", password);
console.log("Client ID :", clientid);
// Client ID를 principal ID로 사용하기 위함
const sanitizedId = clientid.replace(/[^a-zA-Z0-9]/g, '');
console.log("Sanitized ID :", sanitizedId);
const buff = Buffer.from(password, 'base64');
const passwd = buff.toString('ascii');
// 디코딩된 Password 출력
console.log("Decoded Password:", passwd);
if (passwd === 'password1234') {
const response = generateAuthResponse(passwd, 'Allow', sanitizedId);
console.log("Generated Allow Response:", JSON.stringify(response, null, 2));
return response;
} else {
const response = generateAuthResponse(passwd, 'Deny', sanitizedId);
console.log("Generated Deny Response:", JSON.stringify(response, null, 2));
return response;
}
};
// Lambda 응답
const generateAuthResponse = (token, effect, sanitizedId) => {
const authResponse = {
isAuthenticated: true,
principalId: sanitizedId,
policyDocuments: [{
Version: '2012-10-17',
Statement: [
{
Action: ["iot:Connect"],
Effect: effect,
Resource: ["arn:aws:iot:ap-northeast-2:ACCOUNT:client/${iot:ClientId}"]
},
{
Action: ["iot:Publish", "iot:Receive"],
Effect: effect,
Resource: [
"arn:aws:iot:ap-northeast-2:ACCOUNT:topic/*"
]
},
{
Action: ["iot:Subscribe"],
Effect: effect,
Resource: [
"arn:aws:iot:ap-northeast-2:ACCOUNT:topicfilter/*"
]
}
]
}],
disconnectAfterInSeconds: 3600,
refreshAfterInSeconds: 300
};
// Authorization Response 출력
console.log("Auth Response:", JSON.stringify(authResponse, null, 2));
return authResponse;
};
코드에 대해서 간단히 이야기하면
- 여기에선 username까지 인증을 거치지 않고 password 여부만 판단한다.
- Lambda에 전달된 인코딩된 password를 디코딩 했을 때 미리 지정된 password와 일치하면 Effect에 Allow를 넣어서 반환
- 일치하지 않으면 Effect에 Deny를 넣어서 반환한다.
3. AWS IoT Core 콘솔 > 보안 > 권한 부여자(Authorizers) 메뉴로 이동한다. Create authorizer 버튼을 선택하여 권한 부여자를 생성한다.
4. 이름 입력, 활성화, 위에서 생성한 lambda 함수를 선택한 뒤 마친다. 생성한 Authorizer ARN을 복사해둔다.
5. 다시 Lambda 함수 콘솔로 돌아간다. Configuration > Permissions 메뉴로 접근한다.
6. Resource-based policy statements 블럭에서 Add permissions를 선택한다.
7. AWS Service를 선택하고 사진과 같이 입력한다.
- Service : AWS IoT
- Statement ID : my-custom-id-001 (아무거나 입력해도 된다)
- Principal : iot.amazonaws.com
- Source ARN : 복사한 Authorizer의 ARN을 입력한다.
- Action : lambda:InvokeFunction
이렇게 하면 설정은 완료된다. 이제 테스트를 해보자
테스트
1. 로컬이든, ec2든 다음 코드를 입력하여 붙여넣는다.
- 엔드포인트는 AWS IoT Core 엔드포인트로 변경한다.
from __future__ import print_function
import sys
import ssl
import time
import datetime
import logging, traceback
import paho.mqtt.client as mqtt
### Set endpoint, Url, Username, password, clientid, CA File, topic, and port
# mosquitto_endpoint = "mosquitto.thesuavedeveloper.win"
# url = "https://{}".format(mosquitto_endpoint)
username = "username"
password = "password1234"
clientId = "clientid-1234"
topic="sdk/python/test/1234"
### Parameters added/altered for AWS IoT Enhanced Auth
aws_iot_endpoint = "ENDPOINT-ats.iot.ap-northeast-2.amazonaws.com" # 엔드포인트
url = "https://{}".format(aws_iot_endpoint)
alpn_protocol_name_cust_auth = "mqtt"
custom_auth_name = "iot-core-authorizers" # 권한 부여자 이름
useridParams= "{}?x-amz-customauthorizer-name={}".format(username,custom_auth_name)
cafile="root-CA.crt" # Root CA 파일 이름
port=443
## Set up Logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(log_format)
logger.addHandler(handler)
## Setup SSL ALPN Parameters
def ssl_alpn():
try:
#debug print opnessl version
logger.info("open ssl version:{}".format(ssl.OPENSSL_VERSION))
ssl_context = ssl.create_default_context()
ssl_context.set_alpn_protocols([alpn_protocol_name_cust_auth])
ssl_context.load_verify_locations(cafile=cafile)
return ssl_context
except Exception as e:
print("exception ssl_alpn()")
raise e
## Establish Connection and Publish messages
if __name__ == '__main__':
try:
# mqttc = mqtt.Client(clientId)
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1,clientId)
mqttc.username_pw_set(username=useridParams,password=password)
ssl_context= ssl_alpn()
mqttc.tls_set_context(context=ssl_context)
logger.info("start connect")
mqttc.connect(aws_iot_endpoint, port=port)
logger.info("connect success")
mqttc.loop_start()
while True:
now = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
logger.info("try to publish:{}".format(now))
mqttc.publish(topic, now, qos=1)
time.sleep(2)
except Exception as e:
logger.error("exception main()")
logger.error("e obj:{}".format(vars(e)))
# logger.error("message:{}".format(e.message))
traceback.print_exc(file=sys.stdout)
2. 다음 명령을 실행한다.
pip3 install paho-mqtt
3. 코드를 실행한다.
4. AWS IoT Core MQTT client에서 확인할 수 있다면 성공!
로그
1. cloudwatch log group에서 Lambda의 결과를 확인하면 다음과 같은 정보가 lambda에게 전달됨을 알 수 있다.
- severName : IoT 엔드포인트
- username은 사용자이름?x-amz-customauthorizer-name=권한부여자이름
- password : 비밀번호
2. Lambda 함수는 다음 형태의 결과를 반환한다. 이 결과는 AWS IoT Core에게 전달되고, 반환된 정책에 따라서 해당 client ID를 허용하거나 거부한다.
만약 올바르지 않은 password가 전달되었다면 정책의 Effect가 Deny로 나타난다.
{
"isAuthenticated":true, //A Boolean that determines whether client can connect.
"principalId": "xxxxxxxx", //A string that identifies the connection in logs.
"disconnectAfterInSeconds": 86400,
"refreshAfterInSeconds": 300,
"policyDocuments": [{
Version: '2012-10-17',
Statement: [
{
Action: ["iot:Connect"],
Effect: effect,
Resource: ["arn:aws:iot:ap-northeast-2:ACCOUNT:client/${iot:ClientId}"]
},
{
Action: ["iot:Publish", "iot:Receive"],
Effect: effect,
Resource: [
"arn:aws:iot:ap-northeast-2:ACCOUNT:topic/*"
]
},
{
Action: ["iot:Subscribe"],
Effect: effect,
Resource: [
"arn:aws:iot:ap-northeast-2:ACCOUNT:topicfilter/*"
]
}
]
}]
}
3. AWSIotLogsV2 로그에서 볼 수 있듯이 연결이 허용된 것을 확인할 수 있다.
만일 거부된다면 다음 오류가 나타난다
고려사항
- 사용자 정의 인증은 MQTT 연결 기간이 최대 1 ~ 2주이다.
- password는 AWS IoT Core에서 base64로 인코딩 되기 때문에 Lambda에서 디코딩이 필요하다.
관련 자료
https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html
Creating and managing custom authorizers (CLI) - AWS IoT Core
If you leave signing enabled, you can prevent excessive triggering of your Lambda by unrecognized clients. Consider this before you disable signing in your authorizer.
docs.aws.amazon.com
끗
'AWS > PROJECT' 카테고리의 다른 글
DynamoDB 테이블에 디바이스 데이터 저장하기(Lambda, IoT Core) (0) | 2025.01.09 |
---|---|
[API GW] Private API Gateway 만들기와 접근하는 방법(VPC Endpoint, Lambda) (0) | 2024.07.18 |
[EC2] Windows 서버에 Apache 설치하는 방법 (0) | 2024.07.17 |
[Lambda] Opensearch security_exception 오류 해결법 (0) | 2024.07.15 |
Private Server에서 VPC Endpoint를 통해 IoT Core와 통신하기 (0) | 2024.05.20 |