Aserto Topaz を触ってみる

## この記事の目的

前回、前々回の記事では Open Policy Agent (OPA) と認可アーキテクチャの概要を話してきました。

https://qiita.com/caunu-s/items/a11f081811918d176515

https://qiita.com/caunu-s/items/e5d29088bd391c47824f

本記事では、OPA を組み込んだ OSS である **Aserto Topaz** を動かしながら、アプリケーションレイヤでの認可の実装例を確認していきます。

:::note info
11 月の OpenStandia メールマガジンでもご紹介させていただきました。
メルマガもどうぞよろしくお願いいたします。。。
https://openstandia.jp/mm/mm20241113.html
:::

## Aserto Topaz とは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/30fe12ad-30d8-60ec-071a-94dda8c6d401.png)

[Aserto Topaz](https://www.topaz.sh/) は「Open Policy Agent によるポリシー判定アーキテクチャ」および「[Google Zanzibar](https://storage.googleapis.com/gweb-research2023-media/pubtools/5068.pdf)[^1] におけるデータモデル」の利点をかけ合わせ、さらに管理コンソールやディレクトリを付属させ、[Open Container Initiative (OCI)](https://opencontainers.org/) 仕様のポリシーライフサイクル管理機能・ログ集約機能なども備えた、アプリケーションレイヤでの認可実装をより容易にする機能を豊富に持つ認可 OSS です。

[^1]: 「誰が何に対してどのような権限(関係)を持つのか」「あれとこれはどのような関係をもつのか」を保持するデータモデルを元に柔軟な判定を実現する Google の認可判定基盤です。
認可の道を行く者たちには ReBAC 認可モデルの実装例として知られています。
グローバルに一貫性を保持しつつ、処理を高速化する仕組みを実現しているところがスゴイところです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/4e841680-1db7-f5b0-9149-5614b6f30505.png)

Topaz はマイクロサービス、あるいはサイドカーとしてホストすることが想定されています[^2]。

[^2]: [バイナリでの配布](https://www.topaz.sh/docs/deployment/binary)も行われてはいますが、主流な使い方ではなさそうです。

OPA との最大の違いは**ディレクトリとして [BoltDB](https://dbdb.io/db/boltdb) が組み込まれて**おり、よりリッチなデータ情報に基づく認可判定 ([**ReBAC**](https://www.osohq.com/academy/relationship-based-access-control-rebac)) を実現できることです。
また、もちろん OPA と同様にポリシーを用いた判定も行うことができます。

アプリは Topaz と連携することで「ユーザが最低限の情報しか提供しなくても、アプリはユーザのデータを基に画面表示を適切に制御する」などの**今まで実装が難しかった挙動の実現**が期待できます。
連携できるアプリ向け言語も豊富で、公式から提供している [SDK](https://www.topaz.sh/docs/software-development-kits/overview) を用いて連携実装を行うようになっています。

ちなみに [Aserto](https://www.aserto.com/) は Topaz を管理する企業であり、同社が提供する認可プラットフォームの名称でもあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/6dfc265f-e497-bac8-0140-0a555e68b8ac.png)

Aserto のサービスラインナップとして見ると、認可プラットフォーム Aserto が利用する判定機能部分を Topaz として OSS 公開している形となります。
[AuthZEN Working Group](https://openid.net/wg/authzen/) にも中核として参加しており、いわば認可のリーディングカンパニーと言える立場にある企業です。

## さっそく起動してみる

公式 ドキュメントの Getting Started を見ながら動かしていきます。
Getting Started にて提供されているサンプルアプリを使って認可の動きを確かめます。

https://www.topaz.sh/docs/getting-started

:::note warn
Getting Started 動作環境として Docker 環境が求められています。
**その上 Rootless な Docker 環境であることが前提となっています。**
※ 筆者はココで引っかかりました。
本動作確認に先立って Arch Linux にて Rootless Docker 環境を作成したうえで実施しました。
:::

まずは topaz CLI のインストールから行います。
topaz CLI は Topaz インスタンスの管理を単純化するために提供されています。
今回は GitHub のリリースから最新版 (v0.32.38) を取得して利用しました。

```shell
# 取得
wget https://github.com/aserto-dev/topaz/releases/download/v0.32.38/topaz_linux_x86_64.zip
# 展開
7z x topaz_linux_x86_64.zip
# パスが通っている場所に移動する
sudo mv topaz /usr/local/bin/
sudo mv topazd /usr/local/bin/
# 確認
topaz version
> topaz 0.32.38 gdf63b9a linux-amd64 [2024-11-26T22:05:27Z]
```

次に Topaz で利用する証明書をストアに登録できるようにします。
※ 証明書を正しく読み込めるかどうかが Getting Started を完遂できるかの肝です。

```shell
topaz certs trust
> certs directory: /home/caunu-s/.local/share/topaz/certs
> Adding trust to dev cert was requested. 
> Trusting the certificate on Linux distributions automatically is not supported. 
> For instructions on how to manually trust the dev cert on your Linux distribution, 
> go to: https://www.topaz.sh/docs/command-line-interface/topaz-cli/certs
```

では Topaz の Docker イメージを取得します。

```shell
topaz install
> >>> installing ghcr.io/aserto-dev/topaz:0.32.38 (linux/amd64)...
```

続いて Topaz 立ち上げです。
今回 Topaz はローカルで独立したコンテナとして立ち上げます。
ポリシーなどのサンプルテンプレートが準備されているのでそのまま使います。

```shell
topaz templates install todo
> Installing this template will completely reset your topaz configuration.
> Do you want to continue? (y/N) y
> >>> stopping topaz...
> >>> topaz is not running
> >>> configure policy
> certs directory: /home/caunu-s/.local/share/topaz/certs
>   FILE            ACTION     
>   gateway-ca.crt  generated  
>   gateway.crt     generated  
>   gateway.key     generated  
>   grpc-ca.crt     generated  
>   grpc.crt        generated  
>   grpc.key        generated  
> policy name: todo
> Using configuration "todo"
> >>> starting topaz "todo"...
> 3be74303533f51ac9d0230a3f6d4e2149e6cdb72044f6a764e40f263ef684b69
> 
> WARNING: delete manifest resets all directory state, including relation and object data
> >>> delete manifest
> >>> set manifest to /home/caunu-s/.local/share/topaz/tmpl/todo/model/manifest.yaml
> >>> importing data from /home/caunu-s/.local/share/topaz/tmpl/todo/data
> objects   : 20 (set:20 delete:0 error:0)
> relations : 25 (set:25 delete:0 error:0)
> dial tcp: lookup localhost on 127.0.0.1:53: no such host

# 確認
docker ps -a
> CONTAINER ID   IMAGE                              COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                                                                                                                                                                                             NAMES
> 3be74303533f   ghcr.io/aserto-dev/topaz:0.32.38   "./topazd run --conf…"   10 seconds ago   Up 10 seconds   0.0.0.0:8080-8081->8080-8081/tcp, :::8080-8081->8080-8081/tcp, 0.0.0.0:8282->8282/tcp, :::8282->8282/tcp, 0.0.0.0:8383->8383/tcp, :::8383->8383/tcp, 0.0.0.0:9292->9292/tcp, :::9292->9292/tcp, 0.0.0.0:9393->9393/tcp, :::9393->9393/tcp, 0.0.0.0:9494->9494/tcp, :::9494->9494/tcp, 0.0.0.0:9696->9696/tcp, :::9696->9696/tcp   topaz-todo
```

起動中に様々なものが入ったので確認していきます。

```shell
tree $HOME/.local/share/topaz
> /home/caunu-s/.local/share/topaz
> ├── certs
> │   ├── gateway-ca.crt
> │   ├── gateway.crt
> │   ├── gateway.key
> │   ├── grpc-ca.crt
> │   ├── grpc.crt
> │   └── grpc.key
> ├── db
> │   └── todo.db
> └── tmpl
>     └── todo
>         ├── data
>         │   ├── citadel_objects.json
>         │   ├── citadel_relations.json
>         │   ├── todo_objects.json
>         │   └── todo_relations.json
>         └── model
>             └── manifest.yaml
```

証明書がたくさん入っていますが、これらは設定ファイル ```todo.yaml``` の中で各サービスで利用するように設定されています。
今回 BoltDB の中身はローカルに保存するように設定したみたいですね。
```/tmpl/todo``` 配下からはおいしい香りがするので動作確認するときに取っておきましょう。

```json: ~/.config/topaz/topaz.json 
{
    "version": 1,
    "active": {
        "config": "todo",
        "config_file": "/home/caunu-s/.config/topaz/cfg/todo.yaml"
    },
    "running": {
        "config": "todo",
        "config_file": "/home/caunu-s/.config/topaz/cfg/todo.yaml",
        "container_name": "topaz-todo"
    },
    "defaults": {
        "no_check": false,
        "no_color": false,
        "container_registry": "",
        "container_image": "",
        "container_tag": "",
        "container_platform": ""
    }
}
```

設定ファイルに ```todo.yaml``` を使うことは ```topaz.json``` へ記載することがわかります。
このファイルが設定ファイル群の親玉ですね。

```yaml: ~/.config/topaz/cfg/todo.yaml 
# yaml-language-server: $schema=https://topaz.sh/schema/config.json
---
# config schema version
version: 2

# logger settings.
logging:
  prod: true
  log_level: info
  grpc_log_level: info

# edge directory configuration.
directory:
  db_path: '${TOPAZ_DB_DIR}/todo.db'
  request_timeout: 5s # set as default, 5 secs.

# remote directory is used to resolve the identity for the authorizer.
remote_directory:
  address: "0.0.0.0:9292" # set as default, it should be the same as the reader as we resolve the identity from the local directory service.
  tenant_id: ""
...

# default jwt validation configuration
jwt:
  acceptable_time_skew_seconds: 5 # set as default, 5 secs

# authentication configuration
auth:
  keys:
    # - "<API key>"
    # - "<Password>"
  options:
    default:
      enable_api_key: false
      enable_anonymous: true
    overrides:
      paths:
        - /aserto.authorizer.v2.Authorizer/Info
        - /grpc.reflection.v1.ServerReflection/ServerReflectionInfo
        - /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo
      override:
        enable_api_key: false
        enable_anonymous: true

api:
  health:
    listen_address: "0.0.0.0:9494"

  metrics:
    listen_address: "0.0.0.0:9696"
    zpages: true

  services:
    console:
      grpc:
        listen_address: "0.0.0.0:8081"
        fqdn: ""
        certs:
          tls_key_path: '${TOPAZ_CERTS_DIR}/grpc.key'
          tls_cert_path: '${TOPAZ_CERTS_DIR}/grpc.crt'
          tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/grpc-ca.crt'
      gateway:
        listen_address: "0.0.0.0:8080"
...
        certs:
          tls_key_path: '${TOPAZ_CERTS_DIR}/gateway.key'
          tls_cert_path: '${TOPAZ_CERTS_DIR}/gateway.crt'
          tls_ca_cert_path: '${TOPAZ_CERTS_DIR}/gateway-ca.crt'
...
    model:
      grpc:
        listen_address: "0.0.0.0:9292"
...
      gateway:
        listen_address: "0.0.0.0:9393"
...
    reader:
      needs:
        - model
      grpc:
        listen_address: "0.0.0.0:9292"
...
      gateway:
        listen_address: "0.0.0.0:9393"
...
    writer:
      needs:
        - model
      grpc:
        listen_address: "0.0.0.0:9292"
...
      gateway:
        listen_address: "0.0.0.0:9393"
...
    exporter:
      grpc:
        listen_address: "0.0.0.0:9292"
...
    importer:
      needs:
        - model
      grpc:
        listen_address: "0.0.0.0:9292"
...
    authorizer:
      needs:
        - reader
      grpc:
        connection_timeout_seconds: 2
        listen_address: "0.0.0.0:8282"
...
      gateway:
        listen_address: "0.0.0.0:8383"
...
opa:
  instance_id: "-"
  graceful_shutdown_period_seconds: 2
  # max_plugin_wait_time_seconds: 30 set as default
  local_bundles:
    paths: []
    skip_verification: true
  config:
    services:
      ghcr:
        url: https://ghcr.io
        type: "oci"
        response_header_timeout_seconds: 5
    bundles:
      todo:
        service: ghcr
        resource: "ghcr.io/aserto-policies/policy-todo:3.0.0"
        persist: false
        config:
          polling:
            min_delay_seconds: 60
            max_delay_seconds: 120
```

```todo.yaml``` には証明書や使用するポートの指定、各サービスの依存関係などが記載されています。
また、gRPC による実装も行っていることもわかります。
gRPC UI からリクエストをバッチリ送れちゃいますね。

```shell
grpcui --insecure localhost:8282
> gRPC Web UI available at http://127.0.0.1:44435/
> Opening in existing browser session.

grpcui --insecure localhost:9292
> gRPC Web UI available at http://127.0.0.1:43375/
> Opening in existing browser session.
```
![picture-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b5d8cde1-a26c-24c2-2a57-a23c6cd5bf27.png)

![picture-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/8c0c1970-6973-3934-14fa-8896754b15ec.png)

OPA の設定も ```todo.yaml``` にありました。特に注目したいのが ```bundles``` 配下の ```resource``` です。

Topaz では [policy CLI](https://www.topaz.sh/docs/command-line-interface/policy-cli/download) を利用することでポリシーを Docker イメージのように取り扱い管理します。
実際に今回つかうポリシーは Github Container Registry で管理されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/b880ea77-153e-a283-9443-448bf531ee70.png)

ちなみに policy CLI ワークフローを提供しているのが [opcr](https://github.com/opcr-io/policy) です。
Topaz と似たかわいいアイコンがつけられています。

![opcr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/11a2a10c-f3ee-118b-c30e-23e60f16184d.png)

ここまでで Topaz の立ち上げをしてきたので、バックエンド (Go サーバ) とフロントエンド (React アプリ) のサンプルアプリも立ち上げていきます。

```shell
# バックエンド
git clone https://github.com/aserto-demo/todo-go-v2
cp .env.example .env
vim .env
> ASERTO_GRPC_CA_CERT_PATH='/$HOME/.local/share/topaz/certs/grpc-ca.crt'
go mod tidy
go run .
> 5:15PM INF options loaded authorizer=localhost:8282 directory=localhost:9292
> 5:15PM INF starting server listen_address=0.0.0.0:3001

# フロントエンド
git clone https://github.com/aserto-demo/todo-application.git
cd todo-application
yarn
yarn start
> Compiled successfully!
> You can now view client in the browser.
>   Local:            http://localhost:3000
> ...
```

```localhost:3000``` でアプリが立ち上がりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/17b72bf5-3fb3-3c30-6a6d-d2ab168150c1.png)

## 中身を見ていく

まずはチュートリアル通りに動かします。

- ```rick@the-citadel.com```
- V@erySecre#t123!

でログインします。

![picture-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/72e55c6a-c01f-7254-89e2-07f71987a0e2.png)

todo リストを更新します。

![picture-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/7c33e203-6abf-6d5b-d4f9-3a7ae9b845a2.png)

問題なく行えますね。

次に

- ```morty@the-citadel.com```
- V@erySecre#t123!

でログインします。

![picture-6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/5842c9b1-69cb-589a-6059-13431e8e5666.png)

```morty@the-citadel.com``` さんは自分の todo を追加するのも完了状態にするのも問題なく行えます。

![picture-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/dc2bbf24-9a63-9784-9ae5-5651306fbb2c.png)

では ```morty@the-citadel.com``` さんが ```rick@the-citadel.com``` さんの todo を削除してみます。

![picture-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/825c0b03-6145-e2ad-121e-617ecd1e198f.png)

できませんでした。
この仕組みを見ていこうと思います。

何はともあれ、Topaz の判定ロジックが気になります。
API を叩くことでどんな判定が行われるかわかります。

```shell
curl -k -X POST 'https://localhost:8383/api/v2/authz/is' \
-H 'Content-Type: application/json' \
-d '{
     "identity_context": {
          "type": "IDENTITY_TYPE_SUB",
          "identity": "rick@the-citadel.com"
     },
     "policy_context": {
          "path": "todoApp.GET.todos",
          "decisions": ["allowed"]
     }
}'

> {
>   "decisions":  [
>     {
>       "decision":  "allowed",
>       "is":  true
>     }
>   ]
> }
```

上記で ```rick@the-citadel.com``` さんは todoApp に GET できることがわかりますが、そもそもインタフェースがわからないと API へどのようにリクエストを送ったらいいかすらわかりません。
```v2/policies``` API を叩いてもいいですが、大量に出てくるであろうドキュメントを捌くのは少し億劫です。 

そこで Topaz Console (管理GUI) を見てみます。

![picture-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/5f54dbad-7465-49d4-4564-d16c2229c8ac.png)

ポリシーはこの画面から管理できますね。
ちなみに管理 GUI では判定の確認や API 情報、データモデル情報なんかも参照できます。

![picture-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/f25a5ea0-035e-dd7f-7b24-b63d01f44454.png)

![picture-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/7dc9ea2a-efce-4b17-ab00-b85b258595a3.png)

ここでは削除に関するポリシーだけ見てみます。[^3]

```rego: todoApp.common
package todoApp.common

is_member_of(user, group) := x {
	x := ds.check({
		"object_id": group,
		"object_type": "group",
		"relation": "member",
		"subject_id": user.id,
		"subject_type": "user",
	})
}

check(user, permission, todo) := x {
	x := ds.check({
		"object_id": todo,
		"object_type": "resource",
		"relation": permission,
		"subject_id": user.id,
		"subject_type": "user",
	})
}
```

```rego: todoApp.PUT.todos.__id
package todoApp.PUT.todos.__id

# This policy determines whether the user can complete a specific todo identified by input.resource.object_id

import data.todoApp.common.check
import data.todoApp.common.is_member_of
import future.keywords.in
import input.resource
import input.user

default allowed = false

# check if the user has the can_write permission on the resource
# (example of evaluating a permission on a specific resource)
allowed {
	check(user, "can_write", resource.object_id)
}

# check if the user is a member of the allowed groups
# (example of group-based RBAC)
allowed {
	allowedGroups := {"admin", "evil_genius"}
	some group in allowedGroups
	is_member_of(user, group)
}
```

```rego: todoApp.DELETE.todos.__id
package todoApp.DELETE.todos.__id

# This policy determines whether the user can delete the todo identified by input.resource.object_id

import data.todoApp.common.check
import data.todoApp.common.is_member_of
import input.resource
import input.user

default allowed = false

# check if the user has the can_delete permission on the resource
# (example of evaluating a permission on a specific resource)
allowed {
	check(user, "can_delete", resource.object_id)
}

# check if the user is a member of the admin group
# (example of group-based RBAC)
allowed {
	is_member_of(user, "admin")
}
```

[^3]: ポリシー全量を見たい場合は [GitHub](https://github.com/aserto-templates/policy-todo/tree/main/content/src/policies) からご参照ください。

まずパッケージの分け方ですが、共通関数をくくりだして一つのファイルとしています。
[前回の記事](https://qiita.com/caunu-s/items/e5d29088bd391c47824f#opa-%E3%81%AE%E5%88%A4%E5%AE%9A%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF%E3%81%A8%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3)で「テナントわけにも使えそう」とは述べたものの、(やはり) モジュールレベルでの管理として利用されています。

どうやら共通関数を用いて

- 操作するユーザが権限を持っているか
- 操作を許されたグループに所属しているか

を確認しているようです。

... これだけでは先ほどの動作 (他者のものは編集できない) は説明つきませんね。

ということで取っておいた ```/tmpl/todo``` 配下を確認してみます。

```yaml: manifest.yaml
# yaml-language-server: $schema=https://www.topaz.sh/schema/manifest.json
---

# model
model:
  version: 3

# object type definitions
types:
  # user represents a user that can be granted role(s)
  user:
    relations:
      manager: user

    permissions:
      ### display_name: user#in_management_chain ###
      in_management_chain: manager | manager->in_management_chain


  # group represents a collection of users and/or (nested) groups
  group:
    relations:
      member: user | group#member


  # identity represents a collection of identities for users
  identity:
    relations:
      identifier: user


  # resource creator represents a user type that can create new resources
  resource-creator:
    relations:
      member: user | group#member

    permissions:
      can_create_resource: member


  # resource represents a protected resource
  resource:
    relations:
      owner: user
      writer: user | group#member
      reader: user | group#member

    permissions:
      can_read: reader | writer | owner
      can_write: writer | owner
      can_delete: owner

```

```yaml: citadel_relation.json
{
  "relations": [
...
    {
      "object_type": "user",
      "object_id": "morty@the-citadel.com",
      "relation": "manager",
      "subject_type": "user",
      "subject_id": "rick@the-citadel.com"
    },
...
    {
      "object_type": "identity",
      "object_id": "CiRmZDA2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs",
      "relation": "identifier",
      "subject_type": "user",
      "subject_id": "rick@the-citadel.com"
    },
    {
      "object_type": "identity",
      "object_id": "CiRmZDE2MTRkMy1jMzlhLTQ3ODEtYjdiZC04Yjk2ZjVhNTEwMGQSBWxvY2Fs",
      "relation": "identifier",
      "subject_type": "user",
      "subject_id": "morty@the-citadel.com"
    },
...
    {
      "object_type": "identity",
      "object_id": "morty@the-citadel.com",
      "relation": "identifier",
      "subject_type": "user",
      "subject_id": "morty@the-citadel.com"
    },
    {
      "object_type": "identity",
      "object_id": "rick@the-citadel.com",
      "relation": "identifier",
      "subject_type": "user",
      "subject_id": "rick@the-citadel.com"
    },
...
    {
      "object_type": "group",
      "object_id": "admin",
      "relation": "member",
      "subject_type": "user",
      "subject_id": "rick@the-citadel.com"
    },
    {
      "object_type": "group",
      "object_id": "editor",
      "relation": "member",
      "subject_type": "user",
      "subject_id": "morty@the-citadel.com"
    },
...
    {
      "object_type": "group",
      "object_id": "editor",
      "relation": "member",
      "subject_type": "group",
      "subject_id": "admin",
      "subject_relation": "member"
    },
    {
      "object_type": "group",
      "object_id": "evil_genius",
      "relation": "member",
      "subject_type": "user",
      "subject_id": "rick@the-citadel.com"
    },
...
    {
      "object_type": "group",
      "object_id": "viewer",
      "relation": "member",
      "subject_type": "group",
      "subject_id": "editor",
      "subject_relation": "member"
    }
  ]
}
```

```yaml: citadel_objects.json
{
  "objects": [
...
    {
      "type": "group",
      "id": "evil_genius",
      "display_name": "evil_genius-group",
      "properties": {}
    },
...
    {
      "type": "group",
      "id": "admin",
      "display_name": "admin-group",
      "properties": {}
    },
...
    {
      "type": "group",
      "id": "viewer",
      "display_name": "viewer-group",
      "properties": {}
    },
...
    {
      "type": "user",
      "id": "rick@the-citadel.com",
      "display_name": "Rick Sanchez",
      "properties": {
        "email": "rick@the-citadel.com",
        "picture": "https://www.topaz.sh/assets/templates/citadel/img/Rick%20Sanchez.jpg",
        "roles": [
          "admin",
          "evil_genius"
        ],
        "status": "USER_STATUS_ACTIVE"
      }
    },
    {
      "type": "user",
      "id": "morty@the-citadel.com",
      "display_name": "Morty Smith",
      "properties": {
        "email": "morty@the-citadel.com",
        "picture": "https://www.topaz.sh/assets/templates/citadel/img/Morty%20Smith.jpg",
        "roles": [
          "editor"
        ],
        "status": "USER_STATUS_ACTIVE"
      }
    },
...
    {
      "type": "group",
      "id": "editor",
      "display_name": "editor-group",
      "properties": {}
    },
...
  ]
}
```

- グループはサブグループとメンバーで構成される。
- reader, writer, owner は can_read 権限をもつ。
- writer, owner は can_write 権限をもつ。
- owner は can_delete 権限をもつ。
- ```rick@the-citadel.com``` は ```morty@the-citadel.com``` のマネージャーである。
- ```rick@the-citadel.com``` は admin, evil_genius メンバーに属する。
- ```morty@the-citadel.com``` は editor メンバーに属する。
- admin メンバーは editor メンバーにも属する。

等など、様々な関係データが掘り出せました。
ポリシーと照らし合わせると、

- 誰でも誰のリソース (todo) を見れる。 
- 自分のものは変更できるし消せる。
- 自分のものであるか admin 権限を持っていないと消せない。(完了は evil_genius 権限でよい)

ということが見えてきました。

冒頭に書いた

```「Open Policy Agent によるポリシー判定アーキテクチャ」および「Google Zanzibar におけるデータモデル」の利点をかけ合わせ```

が見えてきたような気がします。

## まとめ

Aserto Topaz は OPA を拡張した認可サービスです。
二つ前の記事から読み進めていただいた方は、なんとなくでも理解が進められたのではないかなと思います。

認可アーキテクチャはまだまだ発展途上中で、いつ「本格的に導入せよ！」となるかわからないのが実情です。

こちらの記事を参考に、少しでも「最新の認可ってこうなってるんだ！」と感じていただけていたらうれしいです。