ASP.NET Coreでセキュリティーを考慮したQuickSightの匿名埋め込みダッシュボード機能を利用する（前編）

# はじめに

QuickSightにはユーザーごとに参照可能なデータを行レベルで制御するRowLevelSecurityという機能があります。

少し前のQuickSightの更新で追加された匿名ダッシュボードの埋め込み機能でもこの機能が利用できるのですが、AWS CLIを利用する必要があったり、AWSのドキュメントでもあまり触れられていなかったりするので、検証した記録を残しておきます。

少し長くなったので、AWS CLIを使ってデータセットにセキュリティー設定をする前編と、ASP.NET Coreで埋め込みダッシュボードを作成する[後編](https://qiita.com/karuakun/private/bc27dec079696ed68d01)の２つに分割して投稿します。

# QuickSightの埋め込みダッシュボード機能

QuickSightで埋め込みダッシュボード機能を利用するコマンドには次の３つがあります。どの機能も、QuickSightのエンタープライズ契約が必要です。
１つ目の`get-dashboard-embed-url`は古いコマンドです。これから利用する場合は、２つ目と３つ目のコマンドを利用しましょう。また、今回対象とする`generate-embed-url-for-anonymous-user`はQuickSightのセッションキャパシティープランでのみ利用可能なコマンドになっています。利用できない場合はQuickSightの利用プランを確認してみてください。

- [get-dashboard-embed-url](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/get-dashboard-embed-url.html)
- [generate-embed-url-for-anonymous-user](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/generate-embed-url-for-anonymous-user.html)
- [generate-embed-url-for-registered-user](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/generate-embed-url-for-registered-user.html)

詳細はAWSのドキュメントを参照してください。

- [Working with Embedded Analytics](https://docs.aws.amazon.com/quicksight/latest/user/embedded-analytics.html) 
- [Amazon QuickSight の料金](https://aws.amazon.com/jp/quicksight/pricing/)

# 今回サンプルに利用するデータセット

今回はデータベースに定義された次のテーブルを対象にデータセットを作成します。
このデータセットでは、アプリにログインしたユーザーのロールによって表示できるデータを制御します。
つまり、ダッシュボードを表示するユーザーのロールが`user`だった場合は#8, #9,#10のデータは表示されないように制限しますが、ロールが`admin`だった場合はすべてのデータを表示できるように制御します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/c4b7bfdd-dcff-4b74-c9da-7f4d1e99d5a4.png)
データソースとなるMySQLのテーブル定義はこうなっています。

``` sql:bi_sample_dashboard
create table bi_sample_dashboard
(
  id             int primary key auto_increment,
  role           varchar(10),
  string_column1 varchar(100),
  string_column2 varchar(100),
  string_column3 varchar(100),
  int_column1    int,
  int_column2    int
);
```



# データセットに対するRowLevelSecurityの設定

RLS(RowLevelSecurity)を設定する場合、現在のところマネジメントコンソールにUIが用意されていないため、次の手順でAWS CLIでコマンドを実行していく必要があります。

1. データセット定義の出力
1. データセットに対するRLSの設定
1. コンソールユーザー用のパーミッション設定

QuickSightはなにをやるにもAWSのアカウントIDが必要になります。
この頃はSTSから簡単にAWSのアカウントIDが取得できるので変数にとっておきましょう。

``` sh:AWSAccountIdの確認
❯ $awsAccountId = aws sts get-caller-identity | jq -r '.Account'
```

## データセット定義の出力

AWS CLIを使って定義情報を`dataset.json`ファイルに書き出します。

``` sh:データセット定義の出力
❯ $dataSetId = aws quicksight list-data-sets --aws-account-id $awsAccountId `
    | jq -r '.DataSetSummaries[] | select(.Name==\"bi_sample_dashboard\")'
❯ aws quicksight describe-data-set --aws-account-id $awsAccountId --data-set-id $dataSetId `
    | jq -r '.DataSet' > C:\temp\dataset.json
```

<details><summary>dataset.json</summary><div>

``` json:dataset.json
{
  "Arn": "arn:aws:quicksight:ap-northeast-1:xxxxxxxxxxx:dataset/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "DataSetId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "Name": "bi_sample_dashboard",
  "CreatedTime": "2021-08-30T08:36:52.447000+09:00",
  "LastUpdatedTime": "2021-08-30T08:41:25.692000+09:00",
  "PhysicalTableMap": {
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
      "RelationalTable": {
        "DataSourceArn": "arn:aws:quicksight:ap-northeast-1:xxxxxxxxxxx:datasource/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "Name": "bi_sample_dashboard",
        "InputColumns": [
          {
            "Name": "id",
            "Type": "INTEGER"
          },
          {
            "Name": "role",
            "Type": "STRING"
          },
          {
            "Name": "string_column1",
            "Type": "STRING"
          },
          {
            "Name": "string_column2",
            "Type": "STRING"
          },
          {
            "Name": "string_column3",
            "Type": "STRING"
          },
          {
            "Name": "int_column1",
            "Type": "INTEGER"
          },
          {
            "Name": "int_column2",
            "Type": "INTEGER"
          }
        ]
      }
    }
  },
  "LogicalTableMap": {
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
      "Alias": "bi_sample_dashboard",
      "DataTransforms": [
        {
          "ProjectOperation": {
            "ProjectedColumns": [
              "id",
              "role",
              "string_column1",
              "string_column2",
              "string_column3",
              "int_column1",
              "int_column2"
            ]
          }
        }
      ],
      "Source": {
        "PhysicalTableId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
    }
  },
  "OutputColumns": [
    {
      "Name": "id",
      "Type": "INTEGER"
    },
    {
      "Name": "role",
      "Type": "STRING"
    },
    {
      "Name": "string_column1",
      "Type": "STRING"
    },
    {
      "Name": "string_column2",
      "Type": "STRING"
    },
    {
      "Name": "string_column3",
      "Type": "STRING"
    },
    {
      "Name": "int_column1",
      "Type": "INTEGER"
    },
    {
      "Name": "int_column2",
      "Type": "INTEGER"
    }
  ],
  "ImportMode": "SPICE",
  "ConsumedSpiceCapacityInBytes": 1513,
  "ColumnGroups": []
}
```
</div></details>

## データセットに対するRLSの設定

今回は`role`カラムを使ってRLSを設定したいので次のようなタグ定義追加します。
複数項目の区切り文字に`,`（カンマ）を、全項目を出力したい場合は`*`（アスタリスク）を指定するようにしました。

``` json
  "RowLevelPermissionTagConfiguration": {
    "Status": "ENABLED",
    "TagRules": [
      {
        "TagKey": "tag_role",
        "ColumnName": "role",
        "TagMultiValueDelimiter": ",",
        "MatchAllValue": "*"
      }
    ]
  }
```

前項で出力した`dataset.json`を次のように編集します。

```diff_json

{
+  "AwsAccountId": "xxxxxxxxxxxx",
-  "Arn": "arn:aws:quicksight:ap-northeast-1:xxxxxxxxxxx:dataset/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "DataSetId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "Name": "bi_sample_dashboard",
-  "CreatedTime": "2021-08-30T08:36:52.447000+09:00",
-  "LastUpdatedTime": "2021-08-30T08:41:25.692000+09:00",
  "PhysicalTableMap": {
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
      "RelationalTable": {
        "DataSourceArn": "arn:aws:quicksight:ap-northeast-1:xxxxxxxxxxx:datasource/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "Name": "bi_sample_dashboard",
        "InputColumns": [
          {
            "Name": "id",
            "Type": "INTEGER"
          },
          {
            "Name": "role",
            "Type": "STRING"
          },
          {
            "Name": "string_column1",
            "Type": "STRING"
          },
          {
            "Name": "string_column2",
            "Type": "STRING"
          },
          {
            "Name": "string_column3",
            "Type": "STRING"
          },
          {
            "Name": "int_column1",
            "Type": "INTEGER"
          },
          {
            "Name": "int_column2",
            "Type": "INTEGER"
          }
        ]
      }
    }
  },
-  "LogicalTableMap": {
-    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
-      "Alias": "bi_sample_dashboard",
-      "DataTransforms": [
-        {
-          "ProjectOperation": {
-            "ProjectedColumns": [
-              "id",
-              "role",
-              "string_column1",
-              "string_column2",
-              "string_column3",
-              "int_column1",
-              "int_column2"
-            ]
-          }
-        }
-      ],
-      "Source": {
-        "PhysicalTableId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
-      }
-    }
-  },
-  "OutputColumns": [
-    {
-      "Name": "id",
-      "Type": "INTEGER"
-    },
-    {
-      "Name": "role",
-      "Type": "STRING"
-    },
-    {
-      "Name": "string_column1",
-      "Type": "STRING"
-    },
-    {
-      "Name": "string_column2",
-      "Type": "STRING"
-    },
-    {
-      "Name": "string_column3",
-      "Type": "STRING"
-    },
-    {
-      "Name": "int_column1",
-      "Type": "INTEGER"
-    },
-    {
-      "Name": "int_column2",
-      "Type": "INTEGER"
-    }
-  ],
  "ImportMode": "SPICE",
-  "ConsumedSpiceCapacityInBytes": 1513,
-  "ColumnGroups": []
+  "RowLevelPermissionTagConfiguration": {
+    "Status": "ENABLED",
+    "TagRules": [
+      {
+        "TagKey": "tag_role",
+        "ColumnName": "role",
+        "TagMultiValueDelimiter": ",",
+        "MatchAllValue": "*"
+      }
+    ]
+  }
}
```

JSONファイルを更新したら、CLIで適用します。

``` sh
❯ aws quicksight update-data-set --cli-input-json file://C:/temp/dataset.json
```

RLSが適用されたので、QuickSightの分析ではグラフが表示できなくなっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/2980ec32-fc88-3439-2df8-c1afd1e2cf21.png)

## コンソールユーザー用のパーミッション設定

このままだと不便なので、現在のユーザーにパーミッションを設定しましょう。
QuickSightの`admin`ユーザーに対し、`user`と`admin`ロールの行を参照可能となるように設定しています。

```csv:bi_sample_dashboard_permission.csv
UserName,role
admin,"user,admin"
```

新しいデータセットの作成から、`bi_sample_dashboard_permission.csv`をもとにパーミッション用のデータセットを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/748a9b55-032d-409c-1d7f-ec0bc262a085.png)

パーミッションを適用したいデータセットの定義を開き、`行レベルセキュリティ`ボタンから先ほど作成したパーミッション用のデータセットを割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/3bc71606-75ab-da59-ed78-d28565753adc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/a51d8cd0-2f79-0fc2-1655-490d0e2864b2.png)

表示できるようになりましたね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/acf715f5-eaa5-33b1-c66c-bc5f2cffaa99.png)

## 前編のまとめ

- QuickSightの匿名ダッシュボードでRLSを利用すると、参照可能なデータを行ごとに制御することができる
- まだマネジメントコンソールからは設定できないのでAWS CLIで操作する必要がある
- 匿名ダッシュボード機能はQuickSightのセッションキャパシティープランでのみ利用できる


[ASP.NET Coreでセキュリティーを考慮したQuickSightの匿名埋め込みダッシュボード機能を利用する（後編）](https://qiita.com/karuakun/private/bc27dec079696ed68d01)