亚马逊AWS官方博客
基于 AWS IoT 证书授权最终用户访问云上资源
前言
企业在 AWS 上构建自己的应用系统时,常常需要授权应用系统的最终用户直接访问 AWS 资源(例如:允许最终用户访问非公开权限的 Amazon S3 存储桶)。AWS 通过 Identity and Access Management (IAM)服务授权用户访问 AWS 资源。在面对最终用户时,企业可以选择 Amazon Cognito 结合 IAM从应用程序端控制 AWS 资源访问。这需要企业将 Amazon Cognito 与管理最终用户的身份提供商(IDP, Identity Provider)进行集成,以实现 AWS 资源访问控制[1]。
本文将会介绍一种基于 x.509 证书的认证方式,并结合 AWS Identity and Access Management (IAM) 服务完成对相关资源的授权。从实现角度上看,该方式具有如下特点:
- 无需与第三方 IDP 即成,实现简单;
- 无需预付费用,适合对成本敏感的企业;
- 基于 509 的认证方式,安全可靠
关于 AWS IoT
在 AWS 上,用户可以通过 AWS Certificate Manager 或 AWS IoT 服务获取 x.509 证书。本文所介绍的方案涉及到基于证书的认证、授权,将使用 AWS IoT 服务对证书进行管理。
在介绍方案之前,先来简要介绍一下 AWS IoT[2] 服务。AWS IoT 通过“物品 (Thing)”、“证书 (Certificate)”、“策略 (Policy)”来管理物联设备。物品可以属于一个或多个物品组 (Thing Group),策略可以关联给物品、物品组、或物品组中包含的其它物品组。此外,物品还可以属于某一种物品类型 (Thing Type),属于物品类型的物品可以拥有最多 50 个属性 (Attribute) 用于描述物品,而不属于任何物品类型的物品最多只能拥有 3 个属性。下图所表示的是它们之间的对应关系:
图1:AWS IoT 基本概念
用户管理体系设计
借助 AWS IoT 证书实现对最终用户的认证、授权管理,本质上就是将最终用户在逻辑上视作一个物联设备,借助 AWS IoT 中的“物品”及物品属性的概念管理用户信息,利用 AWS IoT 证书的认证、授权过程实现针对最终用户的认证、授权。接下来,我将进一步阐述这一对应关系。
概念对应关系
- 用户信息 vs 物品属性:
通常,在应用系统中进行用户管理时往往需要一个数据库来记录用户信息,例如记录用户的 ID、姓名、电话、邮箱等。AWS IoT 中的物品可以通过属性的方式来记录物品的元数据,这些元数据可以用来表示最终用户的基本信息。
- 用户组 vs 物品组:
用户组通常用来简化对一批同类用户进行授权的过程。在 AWS IoT 服务中,物品组在关联策略之后可以向组内所有物品进行统一授权,这与用户组的概念非常接近。本文中的方法也是用 AWS IoT 物品组代表用户组。
- 公司/组织 vs 物品类型:
每一个物品仅能属于一个物品类型,正如一个用户往往只属于一个公司或组织。在本文中将使用 AWS IoT 物品类型代表现实中的公司。这对于那些多租户的 SaaS 应用非常必要。
图2:概念对应
权限设计
借助关联给物品的证书,AWS IoT 服务与 AWS IAM、AWS STS 等服务集成,可以授权物品直接访问 AWS 资源和服务。这一过程如下图所示:
图3:借助 AWS IoT 服务直接访问 AWS 资源
上图所示过程可以在官方文档上找到详细说明[3],本文不再赘述。概括而言,这一过程可以细分为两次验证、两次授权:
- AWS IoT 服务验证证书的有效性,完成认证过程;
- AWS IoT 对用户进行授权,允许通过 AWS STS 服务继承特定的 IAM 角色,获得临时用户凭证(AKSK);
- 用户使用临时用户凭证对 API 调用请求进行签名,AWS 验证签名的有效性;
- 验证请求有效后,根据对应的 IAM 角色授予相关 AWS 资源的访问权限。
验证有效性的工作由 AWS 服务自动完成,用户只需对上述 2、4 两个环节中涉及到的权限进行设置:
- AWS IoT 权限:授权所使用的证书可以使用 sts:AssumeRole。这是必须授予的权限,是后续操作正常完成的前提。
- AWS IAM Role 权限:授予用户访问具体 AWS 服务的权限。该权限根据不同应用、不同用户进行对应的设置。
在实际应用过程中往往通过“用户组”和/或 传递变量的方式来简化授权过程。如前文中所提到的,我们可以使用 AWS IoT Thing Group 作为“用户组”实现批量授权。在变量方面,AWS IAM 可以接受的 AWS IoT 变量类型如下:
- credentials-iot:ThingName
- credentials-iot:ThingTypeName
- credentials-iot:AwsCertificateId
我们可以利用这些变量实现只使用一个 AWS IAM Role 就可以针对不同用户授予不同权限的目的(例如:每一个用户仅可以访问 Amazon S3 存储桶中自己的目录)。
下面我将介绍上述设计的原型实现,以帮助大家更好的理解这一设计思路。
原型实现
此部分中如未做特殊说明,均使用 Linux 环境,并安装 awscli 工具。关于 awscli 的安装,请参考官方文档[4]。对于所使用到的 aws api 说明,请参考在线帮助[5]。该原型使用宁夏区域(cn-northwest-1)资源。
需要说明的是,该方法同样适用于海外的其它区域,您无需修改代码,只需要注意替换相关资源的 ARN 为海外区域的 ARN 即可。
该原型将模拟一名最终用户,以 IoT 证书作为自己的用户凭证,完成验证后可以访问 Amazon S3 存储桶中以自己名字命名的目录(读写权限),但无法对其它目录拥有写权限。
创建用户载体
根据本文“概念对应关系”章节中的内容,首先需要在 AWS IoT 服务中创建出“公司”、“最终用户”、“用户授权组”的对应的载体,即:ThingType、Thing 和 ThingGroup,以及用户用来作为自身凭据的 x.509 证书。
- 创建 ThingType(公司)
aws iot create-thing-type \
--thing-type-name "AWS" \
--thing-type-properties "thingTypeDescription=testing, searchableAttributes=login,emailAddress,assumeRole"
- 创建 Thing(最终用户)
aws iot create-thing --cli-input-json file://user-liuwp001-s3.json
其中,文件 user-liuwp001-s3.json 用于描述 Thing 的相关信息,示例如下:
{
“thingName”: “liuwp001-s3”,
“thingTypeName”: “AWS”,
"attributes": {
"JobRole": "PSA",
"Company": "AWS",
"FamilyName": "Liu",
"GivenName": "WP",
"emailAddress": "xxxxxxxx",
"assumeRole": "S3User"
}
}
其中的 thingName 就是用户在应用系统中的用户名,也是后续 S3 存储桶中用户目录的目录名。而属性信息其实就是用户自身的注册信息,它们将会以 Thing 属性的形式被保留在 AWS IoT 服务中。
- 创建 ThingGroup(授权组)
aws iot create-thing-group --cli-input-json file://s3-access.json
其中,文件 s3-access.json 用于描述该 ThingGroup 的相关信息,示例如下:
{
"thingGroupName": "s3-access",
"thingGroupProperties": {
"thingGroupDescription": "This group is for s3 access based on different user.",
"attributePayload": {
"attributes": {
"securityLevel": "High"
},
"merge": true
}
}
}
- 为 Thing 创建证书
执行以下命令时,THING_NAME 、证书保存的目录都可以根据自己的需要进行设定。
THING_NAME=user-liuwp001-s3
aws iot create-keys-and-certificate --set-as-active \
--public-key-outfile ~/iot-cert/$THING_NAME/$THING_NAME.public.key \
--private-key-outfile ~/iot-cert/$THING_NAME/$THING_NAME.private.key \
--certificate-pem-outfile ~/iot-cert/$THING_NAME/$THING_NAME.certificate.pem
上述命令成功执行后会返回所生成证书的 ARN,执行以下命令将证书关联到之前创建的 Thing 上:
aws iot attach-thing-principal --thing-name $THING_NAME --principal <YOUR_CERT_ARN>
生成的证书文件需要交付到最终用户手中。
授权设置
根据前文“权限设计”中的介绍,本方案中有两处权限需要设置:AWS IAM Role 和 AWS IoT。
- 在 AWS IAM 中进行权限设置:
关于 AWS IAM Role 的详细概念说明可以参考官方文档[6],本文不再赘述。下面着重介绍如何创建出符合本文设定场景的 IAM Role。
此处创建的 IAM Role 需要被 AWS IoT 的 credentials(见本文图3)服务所调用。因此,这个 Role 的信任实体应当设置为 credentials.iot.amazonaws.com。执行以下命令创建 IAM Role,请注意将 <YOUR_ROLE_NAME> 根据需要进行替换。
aws iam create-role --role-name <YOUR_ROLE_NAME> --assume-role-policy-document file://trustpolicy.json
其中 trustpolicy.json 的内容示例如下:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "credentials.iot.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
Role 创建出来后需要和 IAM 策略相关联,以便授予对应权限。本文中最终用户通过 IoT 证书所获得的权限是访问专门的 S3 存储桶(只读),仅对以自己名字命名的目录拥有读写权限。通过以下方式创建用于描述 IAM 策略的 json 文件,在 IAM 策略中接受 credentials-iot:ThingName 变量。
使用过程中注意将 <YOUR_BUCKET> 和 <BUCKET_PREFIX> 根据需要进行替换。
cat >s3-access-policy.json <<eof
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws-cn:s3:::<YOUR_BUCKET>",
"Condition": {
"StringLike": {
"s3:prefix": [
"<BUCKET_PREFIX>/${credentials-iot:ThingName}/*",
"<BUCKET_PREFIX>/${credentials-iot:ThingName}/"
]
}
}
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws-cn:s3::: <YOUR_BUCKET>/*"
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws-cn:s3::: <YOUR_BUCKET>/<BUCKET_PREFIX>/${credentials-iot:ThingName}",
"arn:aws-cn:s3::: <YOUR_BUCKET>/<BUCKET_PREFIX>/${credentials-iot:ThingName}/*"
]
}
]
}
eof
基于该内容创建 IAM 策略,并关联到之前创建的 IAM Role 上:
# 创建 IAM Policy,并记录结果中的 ARN
aws iam create-policy --policy-name sobey-cctv-reporter --policy-document file://s3-access-policy.json
aws iam attach-role-policy --role-name <ROLE_NAME> --policy-arn <POLICY_ARN>
- 在 AWS IoT 中进行权限设置:
AWS IoT 不能直接使用 IAM Role,需要先在 AWS IoT 服务中为这个 IAM Role 创建别名:
aws iot create-role-alias --role-alias <ROLE_ALIAS> --role-arn <IAM_ROLE_ARN> --credential-duration-seconds 3600
<ROLE_ALIAS>可根据需要自行定义,<IAM_ROLE_ARN> 为之前所创建的 IAM Role 的 ARN。
接下来,需要为 IoT 的证书关联一个 IoT 策略,以允许该证书通过刚刚创建的别名使用 IAM Role。创建策略命令如下:
aws iot create-policy --policy-name <Policy_NAME> --policy-document file:// <Policy_NAME>.json
其中 <Policy_NAME>.json 内容如下:
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "iot:AssumeRoleWithCertificate",
"Resource": "<ALIAS_ARN>",
"Condition": {
"StringEquals": {
"iot:Connection.Thing.Attributes[assumeRole]": "S3User",
"iot:Connection.Thing.ThingTypeName": "AWS"
},
"Bool": {
"iot:Connection.Thing.IsAttached": "true"
}
}
}
}
策略中增加了 Condition 字段,以减少发生用户元数据错填、加错 ThingGroup 时错误的获取了相关权限的情况。
将创建好的 IoT 策略关联给之前创建的 ThingGroup,这样加入到 ThingGroup 中的所有 Thing 都会获得调用 IAM Role 的权限(被调用的 Role 通过 IoT 的角色别名进行了限制):
aws iot attach-policy --policy-name <POLICY_NAME> --target <THINGGROUP_ARN>
接下来只要将之前创建的 Thing 加入到这个 ThingGroup 中即可:
aws iot add-thing-to-thing-group --thing-name <YOUR_THING_NAME> --thing-group-name <GROUP_NAME>
验证
最终用户在获得 IoT 证书后,可以通过该证书访问 AWS IoT Credentials Provider,通过 Credentials Provider 访问 AWS STS 服务以换取临时 credentials。
首先需要获得自己的 AWS 账号内 IoT Credentials Provider 的 endpoint:
aws iot describe-endpoint --endpoint-type iot:CredentialProvider
返回结果的格式应为:xxxxxxxxx.credentials.iot.cn.<REGION>.amazonaws.com.rproxy.goskope.com.cn
通过标准的 curl 命令即可访问上述 endpoint,命令中需包含 Thing 证书、Thing 私钥、用于验证 AWS IoT Core 服务器身份的根 CA 证书。根 CA 证书的获取可参考官方文档[7]。
请求临时 credentials 命令如下:
curl –cert <THING_CERT> --key <THING_PRIVATE_KEY> -H "x-amzn-iot-thingname: <THING_NAME>" –cacert <AWS_ROOT_CA> https://<CREDENTIALS_ENDPOINT>/role-aliases/<ALIAS_NAME>/credentials > temptoken
在 temptoken 文件中可以找到所需的 access key id, secret access key。如安装了 jq 工具,也可通过如下方式设置环境变量:
export AWS_ACCESS_KEY_ID=$(jq -r ".credentials.accessKeyId" temptoken)
export AWS_SECRET_ACCESS_KEY=$(jq -r ".credentials.secretAccessKey" temptoken)
export AWS_SESSION_TOKEN=$(jq -r ".credentials.sessionToken" temptoken)
设置完成后,即可通过 awscli 或 SDK 工具操作 s3 存储桶中的对应目录。
检索
实际应用过程中可能存在大量用户(Thing)的情况,为了快速查找相关用户,可以利用 AWS IoT 提供的检索功能。
激活 Fleet Indexing 功能:
aws iot update-indexing-configuration \
--thing-indexing-configuration thingIndexingMode=REGISTRY_AND_SHADOW
根据需要检索,例如查找 Company 为 AWS、assumeRole 为 S3User 的用户信息(即 Thing 信息):
aws iot search-index \
--query-string "attributes.Company:AWS AND attributes.assumeRole:S3User"
总结
本文介绍了如何借助 AWS IoT 服务实现用户的授权、认证管理,并以访问 S3 存储桶中的资源为例介绍了如何实现原型系统的搭建。该方案具有如下“三高一低”的特点:
- 低成本:方案中无需部署实体服务器,没有资源预部署的需求;
- 高可靠:验证、授权过程均由 AWS 托管服务完成,其可靠性与 AWS 平台的整体可靠性相同;
- 高安全:用户访问云上资源所需的凭证均为动态凭证,证书本身则没有访问 AWS 云上资源的权限;
- 高灵活:通过 ThingGroup+变量 的方式实现灵活的授权管理,而无需为每个最终用户创建单独的权限策略。
当然,实际应用环境中的需求远比本文介绍的原型更负责,您需要根据自己的实际需求进行调整。必要时可以联系与您接口的 AWS 人员做进一步的讨论。
参考文档
[1] Amazon Cognito Identity Pools (Federated Identities)
[2] What is AWS IoT
[3] Authorizing direct calls to AWS services
[4] Installing, updating, und uninstalling the AWS CLI
[6] IAM roles
[7] CA certificates for server authentication