AWS IoT SiteWise のアクセスコントロールを理解する

IoT SiteWise への書込み方法に応じたポリシー作りがポイントです!
2022.04.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

これまでの SiteWise の記事では「とりあえず SiteWise へデータ書込みできれば OK」という前提で SiteWise を使ってきましたが、今回は SiteWise の IAM による権限管理のうち、BatchPutAssetPropertyValue による書込みについて下記ページを元に確認した内容をご紹介します。

また、利用する上でハマったポイントについても説明してますので、参考にしていただければ幸いです。

なお、IoT Core のルールアクションで SiteWise に書き込む際も BatchPutAssetPropertyValue が利用されるので「直接 SiteWise に書き込む場合」と「IoT Core で書き込む場合」のいずれの場合でも同じポリシーでアクセス管理を行います。

SiteWise で利用できる IAM の機能

最初に SiteWise サポートする IAM の機能についてです。
上記ドキュメントにも記載されていますが、SiteWise では基本的に ID ベースのポリシー にてアクセス制限を行うことになります。リソースベースポリシーは本記事の執筆時点ではサポートしていません。

SiteWise でサポートしている IAM 機能の一覧は下記のとおりです。

IAM 機能 SiteWiseのサポート
リソースレベルの権限を持つIDベースのポリシー はい
リソースベースのポリシー いいえ
アクセス制御リスト(ACL) いいえ
タグベースの承認 はい
一時的な資格情報 はい
サービスにリンクされた役割 はい
サービスの役割 はい

SiteWise への書込み方法を理解する

次に、IAM によるアクセス権限を知る前に、SiteWise への書込み方法を把握しておきましょう。
SiteWise へ書き込む場合、書き込み対象を指定する必要があります。この指定には次の 2 通りの方法があります。

  1. アセット ID とプロパティ ID を指定して書き込み
  2. プロパティエイリアスを指定して書込み

以下にそれぞれの書込み方法について説明します。

1. アセット ID とプロパティ ID を指定して書き込む場合

SiteWise でアセットを作る場合、事前にそのテンプレートとなるアセットモデルを作る必要があります。アセットモデルでは、デバイスや製造機器から取得したいデータ項目や単位など、構成要素となるプロパティを「アセットプロパティ」として定義します。

そして、作成したモデルから個々のデータ取得単位(例えばデバイス単位)でアセットを作成します。
これらを図にしたものが下記になります。

01-asset-model-ids

この図の中で注目するべきポイントは下記の 2 つです。

  • プロパティ ID
  • アセット ID

「プロパティ ID」 はアセットモデルのプロパティが持つ ID と 個々のアセットのプロパティ ID が同一となります。上記の図でいうと、プロパティ IDは「華氏温度」や「発電量」というデータ項目やデータの種類自体の ID であり、個々のアセットの温度や発電量に対する ID ではない、ということが分かります。

一方で、アセット ID は一意なので「アセット ID」と「プロパティ ID」の組み合わせでデータの書込み先を特定することができます。
例えば、先程の図で「turbin 2」の発電量を書き込みたい場合は、次の ID の組み合わせで特定できます。

  • turbin 2 の発電量
    • 「アセット ID:def」
    • 「プロパティ ID:YYY」

下記は IoT Core 経由で書き込む想定のサンプルコードですが、書き込むデータとして「アセットID」と「プロパティID」を指定しています。
(サンプルコードは、デバイスの温度と湿度を IoT Core に送る想定のものです)

import boto3
import json
import logging
import random
import sys
import time
import uuid
import math

logger = logging.getLogger()
logger.setLevel(logging.INFO)
streamHandler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

client = boto3.client('iot-data', endpoint_url='https://xxx-ats.iot.ap-northeast-1.amazonaws.com')
topic = 'sitewise/ingest'

def http_pub_asset():
    start = time.time()

    while True:
        timestamp_float, timestamp_int = math.modf(time.time())
        MeasureValueTemp = random.uniform(20, 50)
        MeasureValueHumi = random.uniform(40, 80)
        logger.info("temperature: {}".format(MeasureValueTemp))
        logger.info("humidity: {}".format(MeasureValueHumi))

        try:
            entries=[
                {
                    'entryId': '{}'.format(uuid.uuid4()),
                    'assetId': '3e68feef-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
                    'propertyId': 'e58a7248-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
                    'value': MeasureValueTemp,
                    'timeInSeconds': int(timestamp_int),
                    'offsetInNanos': int(round((timestamp_float * 1000000000), 0)),
                    'quality': 'GOOD'
                },
                {
                    'entryId': '{}'.format(uuid.uuid4()),
                    'assetId': '3e68feef-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
                    'propertyId': '9e3b2f95-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
                    'value': MeasureValueHumi,
                    'timeInSeconds': int(timestamp_int),
                    'offsetInNanos': int(round((timestamp_float * 1000000000), 0)),
                    'quality': 'GOOD'
                }
            ]

            for e in entries:
                response = client.publish(
                    topic=topic,
                    qos=0,
                    payload=json.dumps(e)
                )
                logger.info("response: {}\n".format(json.dumps(response, indent=2)))
        except Exception as e:
            logger.error("{}".format(e))
            logger.error("temperture: {} humidity: {}".format(MeasureValueTemp, MeasureValueHumi))


        time.sleep(5)

try:
    http_pub_asset()
except KeyboardInterrupt:
    print('\nstopped putting value.')

コードを見ていただくと分かりますが、コードの中で「アセット ID」と「プロパティ ID」を指定しているので事前にこれらの ID をデバイス側が知っておく必要があります。

工夫すれば、クラウド側で定義されている ID を事前に取得、変数にセットしてデータ送信ということもできるかと思いますが、あまり現実的ではありません。

2. プロパティエイリアス を指定して書き込む場合

アセットを作成した際、各アセットプロパティに対してエイリアスを設定することができます。
このエイリアスは「/turbin/2/temperature」のように各アセットプロパティのエイリアスとして一意に設定できるので、事前にデバイス側でアセットID や プロパティ ID を知っておく必要がなく、デバイス側の実装も楽になります。

これを図にすると下記の様になります。アセット ID と プロパティ ID の組み合わせではなく、エイリアスだけで書き込むアセットプロパティが特定できます。

02-asset-model-property-alias

先程のサンプルコードでエイリアスを指定するときは、書き込むデータを下記のように指定するだけです。
アセットID と プロパティ ID の記載がなくなり、プロパティエイリアスの指定だけになっています。

entries=[
    {
        'entryId': '{}'.format(uuid.uuid4()),
        'propertyAlias': '/MyDevice/1/Temperature',
        'value': MeasureValueTemp,
        'timeInSeconds': int(timestamp_int),
        'offsetInNanos': int(round((timestamp_float * 1000000000), 0)),
        'quality': 'GOOD'
    },
    {
        'entryId': '{}'.format(uuid.uuid4()),
        'propertyAlias': '/MyDevice/1/Humidity',
        'value': MeasureValueHumi,
        'timeInSeconds': int(timestamp_int),
        'offsetInNanos': int(round((timestamp_float * 1000000000), 0)),
        'quality': 'GOOD'
    }
]

以下のように、送信元で指定されたプロパティエイリアスを SiteWise 側で「各アセットの各プロパティ」に関連付けることでデータの書込み先が確定します。
(下記では、温度と湿度のそれぞれの測定値プロパティにプロパティエイリアスを設定しています)

03-set-property-alias

SiteWise のデータストリームとは?

SiteWise に対してデータを送る時、SiteWise は「データストリーム」というリソースを自動作成してデバイスや産業機器からのデータを受信します。
また、これらのデータストリームにはそれぞれ一意のエイリアスを付与することができますが、この「データストリームエイリアス」には「プロパティエイリアス」がセットされます。

そのため、プロパティエイリアスを指定してデータを送る場合は、そのエイリアス名がデータストリームエイリアスにセットされます。一方で「アセット ID」と「プロパティ ID」を指定してデータを送る場合は、アセットプロパティにプロパティエイリアスを設定することでデータストリームエイリアスにセットされます。

先程のサンプルコードの例のように「プロパティエイリアス」を使ってデータを書き込むと…(下に続きます)

'propertyAlias': '/MyDevice/1/Temperature',

下記のようにデータストリームが作成されて、プロパティエイリアスと同名の「ストリームエイリアス」を確認することができます。

04-datastream

このデータストリームの画面で、ストリームエイリアスと個々のアセットプロパティを関連付ける事ができます。

07-manage-data-stream

一方で、アセット ID とプロパティ ID を使ってデータを書き込む場合は...(下に続きます)

'assetId': '0bec26b3-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'propertyId': 'e58a7248-xxxx-xxxx-xxxx-xxxxxxxxxxxx',

下記のように対象のアセットの画面でプロパティエイリアスを設定することで…(下に続きます)

05-set-property-alias

同名のストリームエイリアスが付与されてデータストリームを確認することができます。

06-datastream-2

プロパティエイリアスを指定して送ることが可能なら、都度対象のアセットプロパティにエイリアスを設定しなくても、データストリームの管理画面で簡単にストリームエイリアスとアセットプロパティを関連付けることができるので便利です。

また、プロパティエイリアスを使ってデータ送信すると、SiteWise 側で同名のストリームエイリアスとして認識できるので、事前にプロパティエイリアスを把握しておく必要がなくなります。

ここまでのまとめ

以上で次のことが分かりました。

  • SiteWise へデータ送信するとき「データストリーム」が作成される
  • データストリームにはエイリアスを設定することができる
  • プロパティエイリアスを指定してデータ送信すると、対象のデータストリームに同名のエイリアスがセットされる
    • ストリームエイリアスを個々のアセットプロパティに関連付けることで、ストリームエイリアス名がプロパティエイリアスにセットされる
      • これにより書込み先のアセットプロパティが確定して SiteWise にデータの書込みが可能になる
  • アセット ID とプロパティ ID を指定してデータ送信する場合、そのアセットプロパティにエイリアスを設定することで書込み先が確定して SiteWise にデータ書込み可能になる
    • アセットプロパティにエイリアスを設定することで対象のデータストリームに同名のエイリアスがセットされる

アセット ID とプロパティ ID を指定して書き込む場合の IAM ポリシー

さて、長々と前提となるキーワードや仕組みについて説明しましたが、これらを理解することで適切な IAM ポリシーを作ることができます。

冒頭で SiteWise への書込み方法は 2種類あると説明しましたが、それぞれの方法で IAM ポリシーによる制御方法が変わります。ここでは「アセット ID とプロパティ ID を使ってデータ書込み」する場合について説明します。

assetHierarchyPath という条件キーではアセットの階層パスを/」区切りで指定します。
下記の場合は「3e68feef〜」という ID のアセットを階層の最上位アセットとして、下位アセット全てに対するアクセスを許可しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iotsitewise:BatchPutAssetPropertyValue",
            "Resource": "arn:aws:iotsitewise:*:*:asset/*",
            "Condition": {
                "StringLike": {
                    "iotsitewise:assetHierarchyPath": [
                        "/3e68feef-xxxx-xxxx-xxxx-xxxxxxxxxxxx/*",
                        "/3e68feef-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                    ]
                }
            }
        }
    ]
}

冒頭で紹介した記事でも書かれていますが、次のような活用例が考えられます。

  • 管理者:対象階層の全てのアセットを操作できる
  • オペレータ:特定アセットのみ操作できる

AWS IoT Core のルールアクションを使う場合の IAM ポリシーの注意点

IoT Core 経由で SiteWise にデータ送信するとき、IoT Core のルールアクションを作成することになります。その際、次のような画面で ルールアクションが SiteWise にデータ書込みを行うための IAM Role を指定します。

08-sw-rule-iam-role

この画像のように、「ルートアセットに test-asset-1」を指定して「ロールを作成」をクリックすると、次のような IAM ポリシーを持つ Role が作成されます。
assetHierarchyPath 条件キーにセットされるのは、ルートアセットとして指定したアセットの ID です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iotsitewise:BatchPutAssetPropertyValue",
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "iotsitewise:assetHierarchyPath": [
                        "/bec8ebcb-xxxx-xxxx-xxxx-xxxxxxxxxxxx/*",
                        "/bec8ebcb-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
                    ]
                }
            }
        }
    ]
}

コンソールで自動生成されたものなので、このポリシーをそのまま使えば良さそうですが注意すべきポイントがあります。ルールアクションを作る際、次の画面のようにデータ書込みの方法を選択しますが、どちらを選択しても作成されるポリシーは同じであるという点です。

09-select-method

つまり、プロパティエイリアスを使ってデータ書込みを行う場合、ルールアクションの画面で自動生成されたポリシーを使うと権限不足のエラーで書込みができません。

アセット ID とプロパティ ID を指定して書き込む場合は問題ありませんが、プロパティエイリアスを使う場合は、自動生成されたポリシーを編集するか別のポリシーを作成して使うようにして下さい。そのポリシー内容については次に説明します。

プロパティエイリアス を指定して書き込む場合 の IAM ポリシー

次に、プロパティエイリアスを指定して書き込む場合についてです。
この場合は下記のように propertyAlias 条件キーで書き込むプロパティエイリアスを制限できます。
下記の例では /MyDevice/1/* となっているので デバイス番号「1」のデータを想定したアセットプロパティ郡として /MyDevice/1/temperature/MyDevice/1/humidity 等のデータを書き込むことができます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iotsitewise:BatchPutAssetPropertyValue",
            "Resource": [
                "arn:aws:iotsitewise:*:*:time-series/*"
            ],
            "Condition": {
                "StringLike": {
                    "iotsitewise:propertyAlias": "/MyDevice/1/*"
                }
            }
        }
    ]
}

例えば、とある工場(工場 A)の全デバイスだけ書き込めるようにするには、/factorry/A/〜 といったプロパティエイリアスを使ってデータを書込み、IAM ポリシーでは /factorry/A/* として制限すればよさそうです。

また、データストリーム毎にポリシーを変えたい場合は、Resource 句で対象のデータストリームを記載して制限します。

"arn:aws:iotsitewise:*:*:time-series/[DATA_STREAM_ID]"

データストリーム ID を AWS CLI で確認する場合は describe-time-series コマンドを使います。
レスポンスに含まれる timeSeriesId の値が対象のデータストリーム ID です。

$ aws iotsitewise describe-time-series --alias /MyDevice/1/Temperature

{
    "assetId": "3e68feef-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "propertyId": "e58a7248-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "alias": "/MyDevice/1/Temperature",
    "timeSeriesId": "ceceda59-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "dataType": "DOUBLE",
    "timeSeriesCreationDate": "2022-03-31T17:26:28+09:00",
    "timeSeriesLastUpdateDate": "2022-04-06T03:33:30+09:00"
}

利用可能な条件キー一覧

本記事で紹介したもの以外に下記ドキュメントにて、利用可能な条件キーが掲載されていますので参考にして下さい。
なお、本記事で紹介している propertyAlias は一覧に掲載されておりませんが問題なく利用できます。 IAM からも呼び出せるので、ドキュメントの記載漏れなだけだと思われます。

最後に

SiteWise へのデータ書込みの仕組みが分かれば、各条件キーをうまく使い分けて適切な IAM ポリシーを作れることが分かりました。
デバイス証明書を使って IoT Core 経由で書き込む場合は、証明書の IoT ポリシーも併用して適切な権限管理を心がけるようにしましょう。

以上です。