亚马逊AWS官方博客

如何把设备安全的接入AWS IoT(一)

1. 简介

AWS IoT 解决方案是一个全托管的云平台,使互联设备可以轻松安全地与云应用程序及其他设备交互。AWS IoT 可支持数十亿台设备和数万亿条消息,并且可以对这些消息进行处理并将其安全可靠地路由至 AWS 终端节点和其他设备。AWS IoT 平台支持您将设备连接到 AWS 服务和其他设备,保证数据和交互的安全,处理设备数据并对其执行操作,以及支持应用程序与即便处于离线状态的设备进行交互。由于 AWS IoT 与 AI 服务集成,您也可以使设备更智能。AWS IoT 还提供最全面的安全功能,以便您能够创建预防性安全策略,并及时对潜在安全问题做出响应。

使用 AWS IoT 的第一步是将设备连接到 AWS IoT Core 服务。 AWS IoT 支持多种接入协议,身份认证方法和授权策略。 这些不同的协议,认证和授权方式有多种有效的组合方式。您可以使用任意一种有效的组合方式将您的设备接入到 AWS IoT。

本系列文章首先介绍了这些协议,认证和授权方式的原理和细节,以及其分别所适应的场景,然后介绍了 AWS IoT 支持的所有有效的连接组合方式。每种支持的组合方式都提供了模拟设备的参考代码,和运行模拟设备连接到 IoT 的步骤。

注:模拟设备连接到 AWS China 的北京 Region。

2. AWS IoT 支持的协议

设备要接入 AWS IoT,首先要使用 AWS IoT 支持的协议来跟 IoT 平台交互。

2.1 HTTP协议

Http 协议是互联网中最为常见的协议。Http 协议下支持后面提到所有的认证和授权的方式。但是在物联网的场景中,它也有着协议开销比较大等缺点,另外 Http 只有请求响应的模式,不支持物联网场景中非常重要的订阅模式,不能支持下行命令的下发。

注:AWS IoT 只支持 Https 加密传输。

2.2 MQTT 协议

MQTT 协议是物联网场景中使用最为广泛的协议,具有协议开销小,支持发布订阅等所有模式的优点。

2.3 MQTT OVER WEBSOCKET

MQTT over Websocket 是基于 Websocket 上的 MQTT 协议,也具备 MQTT 协议的优点,另外它使用了 443 的端口,在网络环境可达性上比MQTT 更有优势,但是也相对更为复杂一些。

3. AWS IoT 支持的认证和授权方式

设备接入 AWS IoT 的时候,必须要进行认证,确认设备的合法身份。通过认证后,还需要对设备的请求进行鉴权,只有经过授权的请求才会被 AWS IoT 接受。不同的设备认证方式,其授权方式也可能会有所不同。

AWS IoT 支持的认证方式有四种,分别是 IAM 身份,Amazon Cognito 身份,X.509 证书,和自定义身份验证。

AWS IoT 支持的授权策略有两种,分别是 IAM Policy 和 IoT Policy。

认证方式 授权策略 支持的协议 适用的场景
IAM身份 IAM Policy Http & MQTT over Websocket 非正式场景
Amazon Cognito身份 IAM Policy & IoT Policy Http & MQTT over Websocket 移动端
X.509证书 IoT Policy Http & MQTT 默认选择
自定义身份验证 IoT Policy Http & MQTT over Websocket 特殊场景

4. 准备工作

4.1 创建操作环境

登陆 AWS China 管理控制台,然后在 AWS 服务框输入和选择 EC2 服务。

启动一个 EC2 虚机,选择 Amazon Linux 2 AMI(HVM), SSD Volume Type。

在实例类型步骤,直接选择下一步。

在配置实例详细信息步骤,确保自动分配共有 IP 选择了启用,然后点击创建新的 IAM 角色。

在弹出的窗口中选择创建角色,然后依次选择 AWS 服务,EC2,点击下一步。

在 Attach 权限策略步骤,勾选 AdministratorAccess 策略,点击点一步。

添加标签步骤直接选择下一步。

角色名称处输入 AWSIoTDeviceAccessWorkshop,点击创建角色。

返回启动虚机的窗口,确保 IAM 角色选择刚才创建的 AWSIoTDeviceAccessWorkshop。

依次点击下一步:添加存储,下一步:添加标签,下一步:配置安全组。

在配置安全组步骤,确保打开了 22 端口。

依次点击审核和启动,启动。

依次选择创建新密钥对,输入新密钥对的名称 AWSIoTDeviceAccessWorkshop,点击下载密钥对并保存密钥文件。最后点击启动实例。

在启动状态窗口点击实例 ID。

选中实例,并点击连接。根据弹出的窗口的指导,SSH连接到操作环境中。

4.2 配置操作环境

配置 AWS CLI 权限。

aws configure

AWS Access Key ID 和 AWS Secret Access Key 留空,Default region name 配置为 cn-north-1,Default output format 设置为 json。

准备操作目录。

mkdir -p ~/awsIoTAccessDemo
cd ~/awsIoTAccessDemo/

下载 AWS IoT 的 Root CA 文件。

设备连接应该优先选择ATS端点,使用ATS的CA文件。但是由于自定义身份验证暂时不支持ATS端点,所以也需要下载VeriSign端点的CA证书。

wget https://www.amazontrust.com/repository/AmazonRootCA1.pem
wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem

安装倚赖的软件。

sudo yum install python-pip jq -y
pip install boto3 --user
pip install AWSIoTPythonSDK --user
pip install flask --user
pip install paho-mqtt --user

获取 Account Id。

account_id=`aws sts get-caller-identity | jq .Account|sed 's/"//g'`

获取 Account 的 IoT Endpoint 前缀。

endpoint_prefix=`aws iot describe-endpoint \
| jq .endpointAddress | sed 's/"//g'| awk -F . '{print $1}'`

把 Account Id 和 IoT 的 Endpoint 前缀配置到环境变量中。

echo "export account_id=$account_id" >> ~/.bashrc
echo "export endpoint_prefix=$endpoint_prefix" >> ~/.bashrc

4.3 配置 IoT 消息接收监控页面

进入 AWS IoT 服务的控制台。

在 AWS IoT 控制台中,依次点击测试,订阅主题,在订阅主题框里输入  “IoTDemo/#”,然后点击订阅主题。

后续所有的 AWS IoT Core 收到的消息都会在下方的空白处显示出来。这可以作为设备发送消息成功的验证。

5. 设备使用 IAM 身份认证接入

您可以使用 IAM 提供的身份来认证设备。设备需要预置或者通过其他方式获取 Security Credential,再使用 SigV4 的签名算法对请求进行签名。AWS IoT 服务则通过签名来认证设备的身份。通过身份认证后,AWS IoT 再根据身份拥有的 IAM Policy来对请求进行鉴权。IAM 身份认证方式简单易用,但是安全性较差,适用于测试等非正式场景。

IAM 身份认证方式示意图如下图:

5.1 创建 IAM 身份和权限

首先,创建一个 IAM 用户,IoTDeviceUser。

aws iam create-user --user-name IoTDeviceUser

为 IoTDeviceUser 用户创建 Access Key

aws iam create-access-key \
    --user-name IoTDeviceUser > /tmp/IoT_demo_access_key

记录下 AccessKeyId 和 SecretAccessKey

AccessKeyId=`cat /tmp/IoT_demo_access_key| jq .AccessKey.AccessKeyId| sed 's/"//g'`
SecretAccessKey=`cat /tmp/IoT_demo_access_key| jq .AccessKey.SecretAccessKey| sed 's/"//g'`

5.2 设备使用 HTTP 协议接入

为设备创建 IAM Policy

device_IAM_http_policy_arn=`aws iam create-policy \
--policy-name IoTDeviceIAMHttpPolicy \
--policy-document "{
    \"Version\": \"2012-10-17\",
    \"Statement\": [
        {
            \"Sid\": \"VisualEditor0\",
            \"Effect\": \"Allow\",
            \"Action\": \"iot:Publish\",
            \"Resource\": [
                \"arn:aws-cn:iot:cn-north-1:${account_id}:topic/IoTDemo/device_IAM_http\"
            ]
        }
    ]
}" | jq .Policy.Arn | sed 's/"//g'`

把 IAM Policy 绑定 IAM 用户。

aws iam attach-user-policy --user-name IoTDeviceUser \
--policy-arn ${device_IAM_http_policy_arn}

生成设备模拟程序。

cat <<EOF > ~/awsIoTAccessDemo/device_IAM_http.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import boto3
import argparse
import json
#获取参数

parser = argparse.ArgumentParser(description='Send data to IoT Core')
parser.add_argument('--data', default="data from device_IAM_http",
            help='data to IoT core topic')
parser.add_argument('--AccessKeyId', required=True,
            help='AccessKeyId')
parser.add_argument('--SecretAccessKey', required=True,
            help='SecretAccessKey')

args = parser.parse_args()
data = args.data
access_key_id = args.AccessKeyId
secret_access_key = args.SecretAccessKey

device_name = 'device_IAM_http'
region = 'cn-north-1'
topic = "IoTDemo/"+device_name

iot_data_client = boto3.client('iot-data',region_name=region,aws_access_key_id=access_key_id,aws_secret_access_key=secret_access_key)

response = iot_data_client.publish(
    topic=topic,
    qos=0,
    payload=json.dumps({"source":device_name, "data":data})
)
EOF

运行设备模拟程序。

python device_IAM_http.py --data "data from device IAM http." \
--AccessKeyId ${AccessKeyId} --SecretAccessKey ${SecretAccessKey}

然后在第 4.3 章节打开的控制台中可以看到 AWS IoT 收到的消息。后面的场景类似,不再赘述。

5.3 设备使用 MQTT OVER WEBSOCKET 接入

为设备创建 IAM Policy。

device_IAM_websocket_policy_arn=`aws iam create-policy \
--policy-name IoTDeviceIAMWebsocketPolicy \
--policy-document "{
    \"Version\": \"2012-10-17\",
    \"Statement\": [
        {
            \"Sid\": \"VisualEditor0\",
            \"Effect\": \"Allow\",
            \"Action\": [
                \"iot:Publish\",
                \"iot:Receive\"
            ],
            \"Resource\": \"arn:aws-cn:iot:cn-north-1:${account_id}:topic/IoTDemo/device_IAM_websocket\"
        },
        {
            \"Sid\": \"VisualEditor1\",
            \"Effect\": \"Allow\",
            \"Action\": \"iot:Connect\",
            \"Resource\": \"arn:aws-cn:iot:cn-north-1:${account_id}:client/device_IAM_websocket\"
        },
        {
            \"Sid\": \"VisualEditor2\",
            \"Effect\": \"Allow\",
            \"Action\": \"iot:Subscribe\",
            \"Resource\": \"arn:aws-cn:iot:cn-north-1:${account_id}:topicfilter/IoTDemo/device_IAM_websocket\"
        }
    ]
}" | jq .Policy.Arn | sed 's/"//g'`

把 IAM Policy 绑定 IAM 用户。

aws iam attach-user-policy --user-name IoTDeviceUser \
--policy-arn ${device_IAM_websocket_policy_arn}

生成设备模拟程序。

cat <<EOF > ~/awsIoTAccessDemo/device_IAM_websocket.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import json
import time
import sys
import argparse

parser = argparse.ArgumentParser(description='Send data to IoT Core')
parser.add_argument('--endpoint_prefix', required=True,
            help='endpoint prefix')
parser.add_argument('--AccessKeyId', required=True,
            help='AccessKeyId')
parser.add_argument('--SecretAccessKey', required=True,
            help='SecretAccessKey')

args = parser.parse_args()
endpoint_prefix = args.endpoint_prefix
access_key_id = args.AccessKeyId
secret_access_key = args.SecretAccessKey

device_name = 'device_IAM_websocket'
region = 'cn-north-1'
private_topic = "IoTDemo/"+device_name

base_path = "."
server_root_ca_file = base_path + "/AmazonRootCA1.pem"

endpoint = "%s---ats---iot---cn-north-1.amazonaws.com.rproxy.goskope.com.cn" % endpoint_prefix
port = 443

# Custom MQTT message callback
def customCallback(client, userdata, message):
    print("Received a new message: ")
    print(message.payload)
    print("from topic: ")
    print(message.topic)
    print("--------------\n\n")

def pub_msg():
    try:
        pri_loopCount = 0
        while True:
            print 'please input:',
            msg = raw_input()
            private_data = msg
            message = {}
            message['message'] = json.dumps({"source":device_name, "data":private_data})
            message['sequence'] = pri_loopCount
            messageJson = json.dumps(message)
            myAWSIoTMQTTClient.publish(private_topic, messageJson, 1)
            pri_loopCount += 1
            time.sleep(2)
    except:
        sys.exit()

if __name__ == '__main__':
    # Init AWSIoTMQTTClient
    myAWSIoTMQTTClient = None
    myAWSIoTMQTTClient = AWSIoTMQTTClient(device_name, useWebsocket=True)
    myAWSIoTMQTTClient.configureEndpoint(endpoint, port)
    myAWSIoTMQTTClient.configureCredentials(server_root_ca_file)
    myAWSIoTMQTTClient.configureIAMCredentials(access_key_id, secret_access_key)

    # AWSIoTMQTTClient connection configuration
    myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
    myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
    myAWSIoTMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
    myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
    myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

    # Connect and subscribe to AWS IoT
    myAWSIoTMQTTClient.connect()
    myAWSIoTMQTTClient.subscribe(private_topic, 1, customCallback)
    time.sleep(2)

    pub_msg()
EOF

运行设备模拟程序。

python device_IAM_websocket.py --endpoint_prefix ${endpoint_prefix} \
--AccessKeyId ${AccessKeyId} --SecretAccessKey ${SecretAccessKey}

此设备模拟程序会一直运行,接受输入的数据,发送到 AWS IoT Core,同时也订阅自己发送消息的 topic。

输入要发送到 AWS IoT 的消息,如 “data from device IAM websocket.”,设备会接收到自己发送的这个消息。同时,在4.3章节中打开的控制台中也可以看到此消息。

执行 Ctrl+C 停止程序。

7. 资源清理(可选)

aws iam detach-user-policy --user-name IoTDeviceUser --policy-arn arn:aws-cn:iam::${account_id}:policy/IoTDeviceIAMHttpPolicy
aws iam detach-user-policy --user-name IoTDeviceUser --policy-arn arn:aws-cn:iam::${account_id}:policy/IoTDeviceIAMWebsocketPolicy
aws iam delete-policy --policy-arn arn:aws-cn:iam::${account_id}:policy/IoTDeviceIAMHttpPolicy
aws iam delete-policy --policy-arn arn:aws-cn:iam::${account_id}:policy/IoTDeviceIAMWebsocketPolicy
AccessKeyId=`cat /tmp/IoT_demo_access_key| jq .AccessKey.AccessKeyId| sed 's/"//g'`
aws iam delete-access-key --access-key-id $AccessKeyId --user-name IoTDeviceUser
aws iam delete-user --user-name IoTDeviceUser
rm -f /tmp/IoT_demo_access_key

本篇作者

陈雷

AWS 高级解决方案架构师,负责基于 AWS 的云计算方案的咨询与架构设计,同时致力于物联网方面的研究和推广。在加入 AWS 之前,有超过 10 年的架构设计和软件开发的经验,在微服务,devops,物联网和大数据领域有丰富的经验。