IoTデバイスにクレデンシャル情報を配置せずS3からファイルダウンロード

記事タイトルとURLをコピーする

こんにちは!サーバーワークスの松井です。

今回は、IoTデバイスにクレデンシャル情報を配置せずS3からファイルダウンロードする方法を紹介したいと思います。

概要

IoTデバイスでS3の特定のディレクトリにファイルがあるかを確認し、ファイルがあればダウンロードします。 ダウンロードしたファイルは、別のディレクトリに移動させます。

前提条件

以下は設定されているものとします。

  • IoT Coreでのモノの作成

  • デバイスへの証明書の配置

  • python3.9が実行できる環境

手順

1. S3バケットを作成

ダウンロードするファイルを配置するS3バケットを作成します。

デバイス毎にプレフィックスを作成することでプログラム実行デバイスのみがプレフィックスにアクセスできるようにする。

ポリシー変数${credentials-iot:ThingName}を利用する。

例: ${credentials-iot:ThingName}/output

2. IAMロール作成

IAMロールにアタッチするIAMポリシー、信頼ポリシーを作成します。

IAMポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Condition": {
                "StringLike": {
                    "s3:prefix": "${credentials-iot:ThingName}/*"
                }
            },
            "Action": [
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::swx-test-downloads-file"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:Get*",
                "s3:Delete*",
                "s3:Put*"
            ],
            "Resource": [
                "arn:aws:s3:::swx-test-downloads-file/${credentials-iot:ThingName}/*"
            ],
            "Effect": "Allow"
        }
    ]
}

信頼ポリシー

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "credentials.iot.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

3. ロールエイリアス作成

IoTデバイス証明書にIAMロールの権限を付与するには、ロールエイリアスを作成し、ポリシーに権限を記載する必要があります。

4. 証明書にアタッチしているポリシーの権限追加

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:AssumeRoleWithCertificate",
      "Resource": "arn:aws:iot:ap-northeast-1:{AccountID}:rolealias/swx-test-downloads-file-role-alias"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "*"
    }
  ]
}

5. デバイス内設定

今回は、IoTデバイス内でpythonのSDKであるboto3を利用するためのクレデンシャル情報を直に置くのではなくプログラムが呼び出されるたびにプログラム内から認証情報を取得するという方法をとります。

デバイス内ではAWS CLIを使えない前提なのでCloudShellを使い、以下コマンドを実行して、一時クレデンシャルを取得するためのエンドポイント情報を取得しておきます。

$ aws iot describe-endpoint --endpoint-type iot:CredentialProvider
{
    "endpointAddress": "xxxx.iot.ap-northeast-1.amazonaws.com"
}

5. プログラム作成

以下のディレクトリ階層にてプログラムを作成していきます。

・
├── conf
│   └── setting.conf
├── main.py
├── settings.py

main.py

import requests
import json
import boto3
import settings
from boto3.session import Session 
 
cert_filepath = settings.CERT_PATH
pri_key_filepath = settings.PRIVATE_KEY_PATH
endpoint = settings.IOT_CREDENTIAL_ENDPOINT
role_alias = settings.IOT_ROLE_ALIAS
bucket_name = settings.BUCKET_NAME
prefix = settings.PREFIX
another_prefix = settings.ANOTHER_PREFIX
file_name = settings.FLIE_NAME 
device_id = settings.DEVICE_ID




def main():
    # 一時クレデンシャルを発行し、ファイルの存在判定、ダウンロード、移動
    result = requests.get(
        f'{endpoint}/role-aliases/{role_alias}/credentials',                                                                                                                                                        
        cert=(cert_filepath, pri_key_filepath),
        headers={"x-amzn-iot-thingname": f'{device_id}'}
    ) 

    if(result.status_code != 200):
        print('error occurred')
        exit() 
    
    body = json.loads(result.text)
    access_key = body["credentials"]["accessKeyId"]
    secret_key = body["credentials"]["secretAccessKey"]
    token = body["credentials"]["sessionToken"] 

    session = Session(aws_access_key_id=access_key,
                    aws_secret_access_key=secret_key,
                    aws_session_token=token) 
                    
    _s3_client = session.client('s3')
    _s3_resource = session.resource('s3') 

    if check_data(_s3_client):
        downloads_file(_s3_resource)
        mv_file(_s3_client) 
        
    else:
        exit() 
 

def check_data(_s3_client):
    # Prefix直下にファイルがあるか判定
    response = _s3_client.list_objects(
        Bucket = bucket_name,
        Prefix = prefix
    ) 

    assumed_keys = [f'{prefix}/{file_name}']
    try:
        keys = [content['Key'] for content in response['Contents']]
        status = set(assumed_keys).issubset(keys)
        print("file exists")
    except KeyError:
        status = False
        print("file not exists") 

    return status 
 

def downloads_file(_s3_resource):
    # Prefix直下にあるファイルをダウンロード
    bucket = _s3_resource.Bucket(bucket_name)
    bucket.download_file(f'{prefix}/{file_name}', file_name) 
 

def mv_file(_s3_client): 

    # Prefix直下にあるファイルを別Prefixへ退避
    _s3_client.copy_object(
        Bucket = bucket_name, Key = f'{another_prefix}/{file_name}',
        CopySource = {'Bucket': bucket_name, 'Key': f'{prefix}/{file_name}'}
        ) 

    # Prefix直下にあるファイルを削除
    _s3_client.delete_object(Bucket = bucket_name, Key = f'{prefix}/{file_name}') 
 

if __name__ == "__main__":
    main()

settings.py

import os
import configparser
from logging import config
 
# 環境変数設定
conf = configparser.ConfigParser()
conf.read(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf/setting.conf'), encoding='UTF-8')

# DEVICE
DEVICE_ID =  conf['DEVICE']['DEVICE_ID']
CERT_PATH = conf['DEVICE']['CERT_PATH']
PRIVATE_KEY_PATH = conf['DEVICE']['PRIVATE_KEY_PATH']

# AWS
IOT_CREDENTIAL_ENDPOINT = conf['AWS']['IOT_CREDENTIAL_ENDPOINT']
IOT_ROLE_ALIAS = conf['AWS']['IOT_ROLE_ALIAS']
BUCKET_NAME = conf['AWS']['BUCKET_NAME']
PREFIX = conf['AWS']['PREFIX']
ANOTHER_PREFIX = conf['AWS']['ANOTHER_PREFIX']
FLIE_NAME = conf['AWS']['FLIE_NAME']

setting.conf

[DEVICE]
DEVICE_ID = xxx # デバイス名
CERT_PATH = xxx  # 証明書配置パス
PRIVATE_KEY_PATH = xxx  # 秘密鍵配置パス

[AWS]
IOT_CREDENTIAL_ENDPOINT = https://xxx.credentials.iot.ap-northeast-1.amazonaws.com
IOT_ROLE_ALIAS = swx-test-downloads-file-role-alias  # ロールエイリアス名
BUCKET_NAME = swx-test-downloads-file  # S3バケット名
PREFIX = IoT Things名/swx-test-a  # ダウンロード元S3ディレクトリ名
ANOTHER_PREFIX = IoT Things名/swx-test-b  # 移動先S3ディレクトリ名
FLIE_NAME = test.txt  # 配置ファイル名

プログラム実行

事前に以下のモジュールを環境にインストールしておいてください。

pip install requests

pip install boto3

実行します。

python main.py

main.pyと同じ階層にtest.txtがダウンロードされており、S3内のディレクトリ「swx-test-a」内のtest.txtが、「swx-test-b」に移動していたら成功です。

まとめ

IoTデバイス内にクレデンシャルを持たせたくないが、AWSのリソースにアクセスしたいという方は多いかと思いますので、今回はS3を例に取りましたが、他のリソースにも同様にアクセスできるので是非参考にしてみてください。

参考

AWS IoT Core認証情報プロバイダーを使用して、AWSサービスの直接呼出しを認証 - AWS IoT Core