亚马逊AWS官方博客

物联网设备在 OTA 场景下利用证书方式进行 AWS 平台资源的授权访问

为了保证物联网设备能够保持在功能上随时更新,并且在出现问题的时候及时得到修复。小到智能手环,空气净化器, 大到家用汽车,设备厂商无不是通过提供OTA(Over-The-Air)功能来提高用户满意度。在做设备的OTA升级之前,设备厂商通常会提前把需要设备加载的固件保存在可以被设备访问的存储空间里(如果是AWS,通常会保存在S3对象存储里),然后通知用户有新的固件可供升级。接下来用户通过Web或移动端去控制设备下载固件并完成升级过程。

在设备与AWS IoT Core之间实现消息收发之前,IoT Core的设备网关会通过X.509证书的方式对设备进行相应的认证和授权,从而保证平台和数据的安全性。但是在OTA场景下,设备的固件下载是通过连接存储服务(AWS S3)来完成的,那么在这个过程中,我们需要赋予设备访问存储服务(AWS S3)的权限。然而,包括S3在内的许多服务并不提供通过证书方式验证和授权的功能。在此之前,我们更多用到的方法是另起一套认证系统,用户维护一个设备唯一标识的数据库,使用AWS API Gateway, Lambda, DynamoDB, STS等服务完成设备认证并赋予物联网设备一个Token后,通过Request Signing的方式获得S3的临时访问权限。使用这种认证在方式的好处是用户可以高度定制化。不过,由于设备证书通常是与设备唯一绑定的,已经可以唯一标识该设备,另起一套认证流程如果从成本和开发效率的角度上看就不是最佳方案了。好消息是随着AWS物联网平台功能的不断更新和迭代,AWS IoT Core现在已经原生提供了直接使用证书的方式来获得Token,本文将介绍这个功能 (IoT Credential Provider)的原理和使用方法,从而能够帮助用户节约成本,更快速的完成OTA功能的开发。

如图所示,该功能的流程如下:

  1. AWS IoT 设备发送一个包含509设备证书的HTTPS请求到 IoT Core 的 Credential Provider 模块对应的Endpoint。
  2. IoT Credential Provider将请求转发到IoT的认证授权模块来验证证书的有效性,并验证其是否有申请最终Token的权限。
  3. 验证成功后,IoT的认证授权模块会返回成功的消息到IoT Credential Provider。
  4. IoT Credential Provider接下来调用AWS Security Token Service (STS)得到拥有相应权限的STS Token。
  5. IoT Credential Provider将Token返回给AWS IoT设备端。
  6. AWS IoT设备端拿到Token后使用AWS Signature v4签名方式来访问AWS 资源

 

准备工作

本文中的 AWS IoT 设备会使用一台 Amazon Linux EC2实例模拟,Amazon Linux EC2 实例上默认安装了 AWS 命令行工具 AWSCLI,如读者使用运行其他操作系统的实例或者自己的电脑,请参考此链接来安装 AWSCLI,并且请确保你已经赋予这台EC2具有足够权限的角色来执行后面的AWSCLI命令。在测试环境中,为简单起见,您可以赋予IAM和IoT服务的Full Access 权限。在真实环境中,建议您按照最小权限原则,根据步骤中出现的操作,只赋予IAM CreateRole, IAM CreatePolicy, IoT CreateThing等等一些细粒度的权限。接下来的所有操作都是以 AWS 北京区为示例,您可以按照自己的需求通过 aws configure 命令配置不同的区域。

 

第一步:创建 IoT Thing 并下载证书到设备端

登陆到准备工作中创建的Amazon Linux EC2实例上,这里我先通过AWS CLI创建一个名为my-iot-device的thing

 

$ aws iot create-thing --thing-name my-iot-device

{

    "thingArn": "arn:aws-cn:iot:cn-north-1:<your_aws_account_id>:thing/my-iot-device",

    "thingName": "my-iot-device",

    "thingId": "162633c7-6ef4-492a-a8df-853b9315fb3b"

}

为方便起见,我们在AWS IoT Console上为名为my-iot-device的thing创建设备证书和密钥,将设备证书,公钥和私钥以及RootCA (https://www.amazontrust.com/repository/AmazonRootCA1.pem) 下载到本地后上传到EC2实例上

完成后,在设备端EC2上应该有如下四个文件:

$ ls

6ea896581d-certificate.pem.crt  6ea896581d-public.pem.key

6ea896581d-private.pem.key      AmazonRootCA1.pem

证书创建完成后,默认是没有被激活的,请记得到AWS IOT console的Secure选项里把创建出来的证书激活。

 

第二步:创建 IAM Role

在这个方案里,IoT Credential Provider是作为一个中介代理存在的,它的作用就是代理设备端的请求,完成证书验证和获取Token的任务。因此,我们需要创建一个IAM Role, 这个Role会包含两部分内容。

第一部分是和IoT Credential Provider建立一个Trust Relationship的关系,允许IoT Credential Provider代表设备去调用AWS Security Token Service (STS)服务的API AssumeRole。

第二部分我们会赋予这个Role访问S3桶的权限,这个权限会最终传递给设备端。

首先我们在设备端EC2上创建一个文件trust_relationship.json,并保存如下内容到文件中:

{

    "Version": "2012-10-17",

    "Statement": {

        "Effect": "Allow",

        "Principal": {"Service": "credentials.iot.amazonaws.com"},

        "Action": "sts:AssumeRole"

    }

}

创建 IAM Role并和IoT Credential Provider建立Trust Relationship的关系

$ aws iam create-role --role-name s3_access_role --assume-role-policy-document file://trust_relationship.json

{

    "Role": {

        "AssumeRolePolicyDocument": {

            "Version": "2012-10-17",

            "Statement": {

                "Action": "sts:AssumeRole",

                "Effect": "Allow",

                "Principal": {

                    "Service": "credentials.iot.amazonaws.com"

                }

            }

        },

        "RoleId": "AROAV6C6662I4OLER263N",

        "CreateDate": "2019-08-03T04:07:01Z",

        "RoleName": "s3_access_role",

        "Path": "/",

        "Arn": "arn:aws-cn:iam::<your_aws_account_id>:role/s3_access_role"

    }

}

接着我们还需要授予这个Role读取S3桶中内容的权限,从而在设备端得到Token后可以下载固件。

创建文件s3_access_policy.json,保存如下内容到文件中,其中your_bucket替换成要访问的bucket名字:

{

  "Version": "2012-10-17",

  "Statement": {

    "Effect": "Allow",

    "Action": "S3:GetObject",

    "Resource": [

      "arn:aws-cn:s3:::your_bucket/",

      "arn:aws-cn:s3:::your_bucket/*"

    ]  

  }

}

创建IAM Policy:

$ aws iam create-policy --policy-name s3_access_policy --policy-document file://s3_access_policy.json

{

    "Policy": {

        "PolicyName": "s3_access_policy",

        "PermissionsBoundaryUsageCount": 0,

        "CreateDate": "2019-08-03T04:10:59Z",

        "AttachmentCount": 0,

        "IsAttachable": true,

        "PolicyId": "ANPAV6C6662I3YCACLOO6",

        "DefaultVersionId": "v1",

        "Path": "/",

        "Arn": "arn:aws-cn:iam::<your_aws_account_id>:policy/s3_access_policy",

        "UpdateDate": "2019-08-03T04:10:59Z"

    }

}

 

将s3_access_policy权限赋予s3_access_role, 这里的–policy-arn 就是上一步命令输出中的Policy Arn:

$ aws iam attach-role-policy --role-name s3_access_role --policy-arn arn:aws-cn:iam::<your_aws_account_id>:policy/s3_access_policy

 

第三步:创建 AWS IOT Role 别名

当设备连接AWS IOT的时候,设备需要知道它应该获取的是哪一个Role,比如您可以把s3_access_role的ARN写在您的代码里。不过,这并不是一个最佳实践。如果对应的Role ARN发生了变化,您还需要在设备上修改代码才能保证连接性。因此,我们会建议创建一个Role的别名,设备利用别名去获得对应的Role,而无论真实的Role ARN如何变化,在设备端您也不需要做任何更改。

创建AWS IOT Role别名:

$ aws iot create-role-alias --role-alias s3_access_role_alias --role-arn arn:aws-cn:iam::<your_aws_account_id>:role/s3_access_role --credential-duration-seconds 3600

{

    "roleAlias": "s3_access_role_alias",

    "roleAliasArn": "arn:aws-cn:iot:cn-north-1:<your_aws_account_id>:rolealias/s3_access_role_alias"

}

其中您可以通过 –credential-duration-seconds 参数去设定Token的有效时间,最短900秒,最长3600秒。如果不设定,系统默认采用3600秒的最长时间。

这里需要注意的是,您在执行这个命令的时候,对应的IAM user应该有iam:GetRole和iam:PassRole的权限。增加这个权限的目的是能够将前面创建的IAM Role s3_access_role传递给AWS IoT服务。

 

第四步:为设备证书添加 AWS IoT Policy

在第一步中我们为设备创建了设备证书,为了让拥有该证书的设备能够通过AWS IoT平台获得Token,我们还需要给设备证书添加利用证书来获得Role的权限iot:AssumeRoleWithCertificate。

在AWS IoT Console中选择Secure -> Policies, 并点击Create按钮:

分别填入Policy Name, Action, Resource ARN, Effect后点击Create, 其中Resource ARN是第三步中创建的Role的别名ARN:

回到AWS IoT Console中的Manage -> Things页面,选择my-iot-device,在Security中点击之前创建的证书:

在Policies中选择Actions -> Attach Policy

选择aws_iot_policy后点击Attach按钮:

 

第五步:设备获得 Token

接下来我们使用Linux的curl命令像AWS IOT Credential Provider发送一个http请求,从而获得Token.

您账户下的AWS IOT Credential Provider endpoint地址可以通过如下命令获得:

$ aws iot describe-endpoint --endpoint-type iot:CredentialProvider

{

    "endpointAddress": “abcdwfbjnlid3q.credentials.iot.cn-north-1.amazonaws.com.cn"

}

在endpoint的路径后加上 /role-aliases/s3_access_role_alias/credentials,然后发送http请求:

$ curl --cert ./6ea896581d-certificate.pem.crt --key 6ea896581d-private.pem.key  https://abcdwfbjnlid3q.credentials.iot.cn-north-1.amazonaws.com.cn/role-aliases/s3_access_role_alias/credentials

{"credentials":{"accessKeyId":"ASIAV6C6662IZFJNHJPB","secretAccessKey":"SfCRyD+4aterYJrjaVmShLJEr+KEgGV/LVa4B9BU","sessionToken":"FQoDYXdzEJ///////////wEaDG1oQHU14JeLxtYc/SLmArSqFV8iD4/Hipz6aIu5z6azBBRIQQLm/mQCGE7hmqmtHBo5yyO97HpGfUMxMbxrULQCJ1kzlchDmOoU1ZgK7uvK4MCS+a3ALpmzo7YyEVvlHdIa/AX3B+/jznnu6qizZNGtpwyVcwn9beZyLSk5/CTMyFRZM6A5vGd43++lGtEM8Fj+ug1/V+MpUqrl69pfvWEMqJiWSSBHUjGUv+QOTQeYiNq82xBL7GoKN255d8nZctB/uZdUZWtw6/nD6TtmcIQIiFesVhgw3zGPthPei0AM1O6xynDLCGCl1uOX4oNmYoTxAxMIMnnREciEPA97Ocf8dmlSGUgIN3uhYqI5dkmp9grMAxVbccOU6Ubtq4BnmbwZrApFktd+IKa1AhhV/w7zpMQoxBNkLIqLpVaMbMwbcdpza/dYyKC57x1t2fgZLNQPWOSKKns74VG1SyrrDoDkCLr1RPYvmiTt4MXtLzC5dCtghl4o4tuU6gU=","expiration":"2019-08-03T07:46:58Z"}}

从命令行的输出中得到了我们需要的accessKeyId, secretAccessKey 以及 sessionToken.

在获得了STS Token之后,设备上的程序就可以通过Pre-signed URL的方式去下载存储在S3桶中的固件了,具体方式请参见:

https://docs.thinkwithwp.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

 

总结

利用上述方案,设备可以直接利用已集成的设备证书获得从AWS S3下载固件的权限。让客户能够以更低的成本,更加快捷的方式完成OTA功能的开发。并且,利用同样的手段分配不同的AWS资源权限,也可以实现对AWS平台上其他各类资源的访问能力,从而为IoT设备创造出更加丰富的功能。

 

本篇作者

郭松

AWS 解决方案架构师,负责企业级客户的架构咨询及设计优化,同时致力于 AWS IoT 和存储服务在国内和全球企业客户的应用和推广。加入 AWS 之前在EMC研发中心担任系统工程师,对企业级存储应用的高可用架构,方案及性能调优有深入研究。