はじめに
AWS IoTをはじめとし、多くのIoTシステムでは、各デバイスそれぞれに固有のクライアント証明書を発行し、これを利用してAuthentication/Authorizationを実現しているものと思います。
しかしながら、ほとんどのAWSサービスはクライアント証明書に対応しておらず、例えば特定のクライアント証明書を持つデバイスのみ、特定のS3 Bucketにアクセスさせたい、といったことはそのままでは実現できません。
しかしAuthorizing Direct Callsという仕組みを利用することで、これを実現することができるようになります。
Authorizing Direct Calls to AWS Services - AWS IoT
https://docs.aws.amazon.com/iot/latest/developerguide/authorizing-direct-aws.html
TL;DR
- Authorizing Direct Callsとは、個別のクライアント証明書をTemporary Security Credentialsに変換する仕組み
- Temporary Security Credentialsがあれば、(権限があれば)AWS APIを何でも実行できる
- IoT Thingから見た場合、Certificate→IoT Policy→Role Alias→IAM Roleというかたちで権限情報が紐づく
検証した環境
- AWSアカウントID: 123456789099
- S3 Bucket:
- S3 Bucket名: investigate-aws-iot
- テスト対象ファイル名: s3://investigate-aws-iot/file_needs_retrieving.txt
- IAM Role:
- Role名: InvestigateAuthorizingDirectCallsRole
- Role ARN: arn:aws:iam::123456789099:role/InvestigateAuthorizingDirectCallsRole
- AWS IoT:
- IoT Endpoint: abcdefgh123456.credentials.iot.ap-northeast-1.amazonaws.com
- Role Alias:
- Role Alias名: InvestigateIotRoleAlias
- Role Alias ARN: arn:aws:iot:ap-northeast-1:123456789099:rolealias/InvestigateIotRoleAlias
- Thing/Certificate/Policy
- Thing: InvestigateAuthorizingDirectCallsThing
- Certificate: c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e
- Policy: InvestigateAuthorizingDirectCallsPolicy
作業概要
- S3 Bucketの準備
- IoT ThingからAssumeRoleするIAM Roleの作成
- AWS IoT Role Aliasの作成
- AWS IoT Thing/Certificate/Policyの準備
- Thingの作成
- IoT Policyの作成
- Certificateの作成
- Certificateのactivate
- CertificateへのPolicyのattach
- CertificateをThingにattach
- Authorizing Direct Callsの確認
- AWS IoTのEndpoint確認
- curlを実行し、Temporary Security Credentialsを取得する
- S3 Bucketからファイルを取得できることを確認する
各ステップの詳細
1. S3 Bucketの準備
手順詳細は省略しますが、下記のように、必要なS3 Bucketを作成し、取得対象のファイルを格納しておきます。
$ aws s3 ls
2019-08-21 15:14:05 investigate-aws-iot
$ aws s3 ls s3://investigate-aws-iot/
2019-08-21 15:16:09 11 file_needs_retrieving.txt
上記ファイルの内容は何でもよいです。今回は下記の内容でつくっています。
retrieved
2. IoT ThingからAssumeRoleするIAM Roleの作成
本稿で確認するAuthorizing Direct Callsの仕組みは、最終的にThingはここで定義するIAM Roleの権限を取得することとなります。したがって、このRoleに必要なPermissionsを定義する必要があります。
Role名は上述の通り、「InvestigateAuthorizingDirectCallsRole」とします。
AWS IoTのサービスからAssumeRoleされる形になるため、Trust Relationshipで、「credentials.iot.amazonaws.com」からAssumeRoleできるようにしておきます。
またPermissionsとして、S3 Bucket一覧の取得と、対象S3 Bucketのオブジェクト取得に必要な権限を付与します。
それぞれ具体的には下記のようになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "credentials.iot.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:GetRole",
"iam:PassRole"
],
"Resource": [
"arn:aws:iam::123456789099:role/InvestigateAuthorizingDirectCallsRole"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::investigate-aws-iot/*",
"arn:aws:s3:::investigate-aws-iot"
]
},
{
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "arn:aws:s3:::*"
}
]
}
3. AWS IoT Role Aliasの作成
AWS IoTのRole Aliasを作成します。これはAWS Management Consoleからは実行できないようなので、AWS CLIから実行します。
Role Alias名は上述の通り「InvestigateIotRoleAlias」とします。
引数として、先ほど作成したIAM RoleのARNを渡します。
$ aws iot create-role-alias --role-alias InvestigateIotRoleAlias --role-arn arn:aws:iam::123456789099:role/InvestigateAuthorizingDirectCallsRole
{
"roleAlias": "InvestigateIotRoleAlias",
"roleAliasArn": "arn:aws:iot:ap-northeast-1:123456789099:rolealias/InvestigateIotRoleAlias"
}
4. AWS IoT Thing/Certificate/Policyの準備
動作確認対象のデバイス(Thing)を作成し、個別のクライアント証明書を発行し、Policyを紐づけます。
このあたりの作業は、通常AWS IoTを利用する上では当たり前のように実施していると思いますので、できる限り省略します。
4-1. Thingの作成
普通にThingを作成します。何も特別なことはありません。
$ aws iot create-thing --thing-name InvestigateAuthorizingDirectCallsThing
{
"thingArn": "arn:aws:iot:ap-northeast-1:123456789099:thing/InvestigateAuthorizingDirectCallsThing",
"thingName": "InvestigateAuthorizingDirectCallsThing"
}
4-2. IoT Policyの作成
AWS IoTにはIoT Policyという概念があります。これは各クライアント証明書に紐づけるもので、当該クライアント証明書が紐づけられたThingに付与する権限を指定します。
本稿で必要な権限は、先ほど作成したRole Aliasに対する「iot:AssumeRoleWithCertificate」となります。
まずは下記JSONファイルを作成します。ここでは「iot_policy.json」とします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:AssumeRoleWithCertificate",
"Resource": "arn:aws:iot:ap-northeast-1:123456789099:rolealias/InvestigateIotRoleAlias"
}
]
}
上記ファイルを指定して、IoT Policyを作成します。
$ aws iot create-policy --policy-name InvestigateAuthorizingDirectCallsPolicy --policy-document file://iot_policy_document.json
{
"policyName": "InvestigateAuthorizingDirectCallsPolicy",
"policyArn": "arn:aws:iot:ap-northeast-1:123456789099:policy/InvestigateAuthorizingDirectCallsPolicy",
"policyDocument": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"iot:AssumeRoleWithCertificate\",\n \"Resource\": \"arn:aws:iot:ap-northeast-1:123456789099:rolealias/InvestigateIotRoleAlias\"\n }\n ]\n}\n",
"policyVersionId": "1"
}
4-3. Certificateの作成
クライアント証明書と鍵ペアを作成し、それぞれ個別のファイルに保存します。
まずはiot create-keys-and-certificateを実行し、結果をファイルに出力します。
$ aws iot create-keys-and-certificate > keys-and-cert.txt
$ cat keys-and-cert.txt
{
"certificateArn": "......",
"certificatePem": "-----BEGIN CERTIFICATE-----\n......\n-----END CERTIFICATE-----\n",
"keyPair": {
"PublicKey": "-----BEGIN PUBLIC KEY-----\n......\n-----END PUBLIC KEY-----\n",
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\n......\n-----END RSA PRIVATE KEY-----\n"
},
"certificateId": "c231554173......"
}
certificateIdの先頭10文字を取得します。これはファイル名などに用います。
$ cat keys-and-cert.txt | jq -r '.certificateId[0:10]'
c231554173
証明書と秘密鍵を個別のファイルに保存します。公開鍵は単体では特に必要ありません。
$ cat keys-and-cert.txt | jq -r '.certificatePem' > c231554173-certificate.pem.crt
$ cat keys-and-cert.txt | jq -r '.keyPair.PrivateKey' > c231554173-private.pem.key
4-4. Certificateのactivate
Certificateは作成直後は「INACTIVE」の状態になっていますので、これを「ACTIVE」に変更します。
$ aws iot update-certificate --certificate-id c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e --new-status "ACTIVE"
iot describe-crtificateコマンドで、クライアント証明書のステータスを確認することができます。
$ aws iot describe-certificate --certificate-id c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e
{
"certificateDescription": {
"certificateArn": "arn:aws:iot:ap-northeast-1:123456789099:cert/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e",
"status": "ACTIVE",
"certificateId": "c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e",
"lastModifiedDate": 1566750890.409,
"certificatePem": "-----BEGIN CERTIFICATE-----\n......\n-----END CERTIFICATE-----\n",
"transferData": {},
"ownedBy": "123456789099",
"creationDate": 1566750089.245
}
}
4-5. CertificateへのPolicyのattach
クライアント証明書にIoT Policyを紐づけます。
$ aws iot attach-principal-policy --policy-name InvestigateAuthorizingDirectCallsPolicy --principal arn:aws:iot:ap-northeast-1:123456789099:cert/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e
IoT Policyに紐づけられたクライアント証明書は、aws iot list-policy-principalsコマンドを実行することで確認できます。
$ aws iot list-policy-principals --policy-name InvestigateAuthorizingDirectCallsPolicy
{
"principals": [
"arn:aws:iot:ap-northeast-1:123456789099:cert/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e"
]
}
4-6. CertificateをThingにattach
クライアント証明書をThingに紐づけます。
$ aws iot attach-thing-principal --thing-name InvestigateAuthorizingDirectCallsThing --principal arn:aws:iot:ap-northeast-1:123456789099:cert/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e
たクライアント証明書に紐づけられたThingは、aws iot list-principal-thingsコマンドを実行することで確認できます。
$ aws iot list-principal-things --principal arn:aws:iot:ap-northeast-1:123456789099:cert/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e
{
"things": [
"InvestigateAuthorizingDirectCallsThing"
]
}
5. Authorizing Direct Callsの確認
5-1. AWS IoTのEndpoint確認
AWS IoTはそのサービス提供用に、各AWSアカウント・リージョンごとに専用のEndpointを用意しています。aws iot describe-endpointコマンドを実行して、当該Endpointのドメイン名を取得できます。
$ aws --profile playground iot describe-endpoint --endpoint-type iot:CredentialProvider
{
"endpointAddress": "abcdefgh123456.credentials.iot.ap-northeast-1.amazonaws.com"
}
5-2. curlを実行し、Temporary Security Credentialsを取得する
上記で取得したEndpointに対して、Thingのクライアント証明書・秘密鍵を用いてHTTPSリクエストを発行することで、Temporary Security Credentialsを取得することができます。
まず、AWSのRootCA局証明書が必要になりますので、これをダウンロードしておきます。
$ curl -O https://www.amazontrust.com/repository/AmazonRootCA1.pem
下記のように、curlの引数として以下の情報を渡すことで、Temporary Security Credentialsを取得することができます。
- クライアント証明書とその秘密鍵
- RootCA局証明書
- Thing名を「x-amzn-iot-thingname」ヘッダに指定
$ curl -v --cert ./c231554173-certificate.pem.crt --key ./c231554173-private.pem.key \
--cacert AmazonRootCA1.pem \
-H "x-amzn-iot-thingname: InvestigateAuthorizingDirectCallsThing" \
https://abcdefgh123456.credentials.iot.ap-northeast-1.amazonaws.com/role-aliases/InvestigateIotRoleAlias/credentials \
| jq
上記が成功すると、下記のようなJSONの出力を得ることができます。この情報を用いて、S3やDynamoDBその他AWSリソースにアクセスすることが可能です。権限は、最初の方で作成したIAM Role「InvestigateAuthorizingDirectCallsRole」のPermissionsが付与されます。
{
"credentials": {
"accessKeyId": "ASIA................",
"secretAccessKey": "................................",
"sessionToken": "................................................",
"expiration": "2019-08-25T18:26:25Z"
}
}
5-3. S3 Bucketからファイルを取得できることを確認する
上記で取得したTemporary Security Credentialsを用いて、実際にS3 Bucketにアクセスできることを確認します。
まず、先ほど取得したAccessKeyId/SecretAccessKey/SessionTokenを、環境変数にセットします。
$ export AWS_ACCESS_KEY_ID=ASIA................
$ export AWS_SECRET_ACCESS_KEY=................................
$ export AWS_SESSION_TOKEN=................................................
なお、AWS CLIからTemporary Security Credentialsを利用する方法について、詳細は下記ドキュメントに記載があります。
Using Temporary Security Credentials to Request Access to AWS Resources - AWS Identity and Access Management
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html#using-temp-creds-sdk-cli
次に、STSのGetCallerIdentity APIを実行し、APIの発行主体がIAM Role「InvestigateAuthorizingDirectCallsRole」になっていることを確認します。
$ aws sts get-caller-identity
{
"Account": "123456789099",
"UserId": "AROA............:c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e",
"Arn": "arn:aws:sts::123456789099:assumed-role/InvestigateAuthorizingDirectCallsRole/c231554173c80cdb46da694a21ec92b060b07d27883214edf7ac1573843f2a3e"
}
最後に、S3 Bucketからファイルを取得できることを確認します。
$ aws s3 ls s3://investigate-aws-iot
2019-08-21 15:16:09 11 file_needs_retrieving.txt
$ aws s3 cp s3://investigate-aws-iot/file_needs_retrieving.txt ./
download: s3://investigate-aws-iot/file_needs_retrieving.txt to ./file_needs_retrieving.txt
おわりに
これで、Thing個別のクライアント証明書を用いて、AWSリソースにアクセスできるようになりました。
AWSの基本的な考え方として、各AWSリソースにアクセスする際には、AWS Credentials(AccessKeyId/SecretAccessKey)か、Temporary Security Credentials(AccessKeyId/SecretAccessKey/SessionToken)を用いて、Signature V4で署名してAWS APIを発行する、という方針があると思います。
Cognito Federated Identitiesもそうですが、何らか操作する主体を特定できる情報があった場合、最終的にTemporary Security Credentialsに変換する、という考え方が根底にあるかと思います。
そのため、たとえクライアント証明書があったところで、これを用いて直接AWS APIを発行できるようになる可能性はゼロと考えた方がよいでしょう。