Web APIのアクセスコントローラーとしてのAmazon Verified Permissionsの可能性を探る

Web APIのアクセスコントローラーとしてのAmazon Verified Permissionsの可能性を探る

Amazon Verified Permissions(以下AVP)は、2023年にGA(一般提供開始)されてから2年程経過していますが、日本語での技術記事や実践例などあまり見かけません。 本記事では、AVPがWeb APIのアクセス制御を実装するサービスとしてどのような可能性を持つのか、実際に触れてみた経験をもとに、技術的な観点や所感を交えて紹介してみたいと思います!
Clock Icon2025.06.13

Amazon Verified Permissions(以下AVP)は、2023年にGA(一般提供開始)されてから2年程経過していますが、日本語での技術記事や実践例などあまり見かけません。

私自身も最近までその存在を知らず、偶然見つけて興味を持ち、実際に検証してみました。

本記事では、AVPがWeb APIのアクセス制御を実装するサービスとしてどのような可能性を持つのか、実際に触れてみた経験をもとに、技術的な観点や所感を交えて紹介してみたいと思います!

AVPとは?

AVP(Amazon Verified Permissions)は、Amazonが提供するクラウド型のアクセスコントロールサービスです。
アプリケーションの認可処理のアクセス制御部分を柔軟かつ強力に実現するための主要な機能を備えています。

  • ポリシー言語(Cedar)による柔軟なルール記述
  • ポリシーストア(ポリシーの一元管理・バージョン管理)
  • ポリシーエンジン(高速な評価・判定処理)

AVPの最大の特徴は、**Cedar**によるポリシーベース(PBAC: Policy-Based Access Control)のきめ細かいアクセス制御(Fine-Grained Access Control)を実現できる点です。

CedarはAmazonが開発したポリシー言語であり、アクセス制御のルールを人間が理解しやすい形で記述できることが特徴です。

Amazon発のポリシー言語ということで、IAMポリシーに似た構造を持っています。
IAMポリシーはAWSサービスのアクセス制御に特化していますが、Cedarはより汎用的なポリシー言語として設計されています。

Cedar自体はオープンソースとして公開されており、AVPはこのCedarを基盤としたアクセス制御サービスです。

AVPの所感をつかみたい人は、下記の動画のチュートリアルを参考にすると良いです。

https://www.youtube.com/watch?v=OBrSrzfuWhQ

Cedarとは?

Cedarは、Amazonが開発したポリシー言語およびその評価エンジンです。

Cedarの最大の特徴は、アクセス制御のルール(ポリシー)を人間が理解しやすい形で記述でき、かつ機械的に検証できる点にあります。

ポリシーをCedar言語で記述し、Cedarエンジンで評価することで、柔軟かつ堅牢なアクセス制御を実現します。

Cedarの基本的な構造は、以下の3つの要素で構成されています。

  • Principal: アクセスを要求する主体(ユーザーやグループなど)
  • Action: 実行される操作(操作の種類やメソッド)
  • Resource: アクセス対象のリソース (対象のリソースやデータ)

例えば、以下のようなCedarポリシーを記述できます。

permit (
  principal in PhotoFlash::User::"Alice",
  action in PhotoFlash::Action::"SharePhotoLimitedAccess",
  resource in PhotoFlash::Photo::"VacationPhoto94.jpg"
);

この例では、「Alice」というユーザー(prncipal)が「VacationPhoto94.jpg」という写真(resource)に対して「SharePhotoLimitedAccess」という操作(action)を実行できることを許可「permit」しています。

これらの組み合わせにより、Cedarでは下記のようなアクセス制御モデルを実現可能です。

  • RBAC(Role-Based Access Control):ユーザーの役割に基づくアクセス制御
  • ABAC(Attribute-Based Access Control):ユーザーやリソースの属性に基づくアクセス制御
  • ReBAC(Relationship-Based Access Control):ユーザー間の関係性に基づくアクセス制御

近年では、Auth0が主体となってオープンソース化されたOpenFGAなど、他のFGAC(Fine-Grained Access Control)なども注目されています。

こういった流れを見ると、CognitoやAuth0などの認証基盤側が提供するアクセスコントロールソリューションでは限界があり、その部分を外出しした形でのアクセス制御のレイヤーが求められている印象を受けます。

やってみる

ここからは、実際にAVPを使ってWeb APIのアクセス制御を実装する例を紹介します。

今回は、以下のようなユースケースを想定しました。

  • ユースケース
    • Web APIを提供するマルチテナントアプリケーション
    • 1ユーザーが複数テナントに所属可能
    • 各テナントに紐づくリソースへのアクセス制御
    • ユーザーごとに所属テナントのリソースのみアクセス可能
    • client credentils flowを利用したマシン間通信(M2M)のアクセス制御

具体的には、次のようなWeb APIのエンドポイントを用意します。

2025-06-13_11h05_10

  • GET /items
    • 全アイテムの一覧を取得するAPI
    • 全ユーザー/クライアントがアクセス可能(ただし所属テナントの範囲に限定)
  • GET /tenants/{tenant_id}/items
    • 特定テナントのアイテム一覧を取得するAPI
    • 指定したテナントIDtenant_idに所属している場合のみアクセス可能
  • POST /tenants/{tenant_id}/items
    • 特定テナントにアイテムを追加するAPI
    • 指定したテナントIDtenant_idに所属している場合のみアクセス可能

下準備

avp-cedar

今回は、API Gateway(REST API)+Lambda関数コンテナ+AVP+Cognito User Poolという構成を採用し、インフラのプロビジョニングにはTerraformを利用します。

また、アプリケーションフレームワークにはFastAPIを使用し、Pythonで実装します。

一通りのリソースとコードのデプロイは、こちらのGitHubリポジトリを参考にしてください。

ここでは、Cedarのスキーマやポリシーの定義、FastAPIのコード、Terraformの設定ファイルなどを主要な部分に絞って紹介します。

Cedar schema

まずは、Cedarのスキーマを定義します。

スキーマは、ポリシーストアのCedarポリシーで使用するエンティティやアクションの型を定義するものです。

正しい表現か分かりませんが、DBのテーブルスキーマのようなものです。

今回は、AVPがデモで紹介されている情報を参考に、以下のようなスキーマを定義しました。

注目すべきは、

  • entityTypesUserTenantの親子関係を定義している点
  • actionsにAPIのパスをそのまま定義している点
  • Resourceは基本的には利用していない点

の3点です。

後述でも解説しますが、今回のポリシーでは、Resourceは基本的には利用せず、Actionのパスに基づいてアクセス制御を行います。

{
  "FastapiApp": {
    "commonTypes": {
      "PersonType": {
        "type": "Record",
        "attributes": {
          "email": {
            "type": "String"
          }
        }
      },
      "ContextType": {
        "type": "Record",
        "attributes": {
          "authenticated": {
            "type": "Boolean",
            "required": true
          }
        }
      }
    },
    "entityTypes": {
      "User": {
        "shape": {
          "type": "Record",
          "attributes": {
            "sub": {
              "type": "String"
            },
            "personInformation": {
              "type": "PersonType"
            }
          }
        },
        "memberOfTypes": [
          "Tenant"
        ]
      },
      "Client": {
        "shape": {
          "type": "Record",
          "attributes": {}
        }
      },
      "Tenant": {
        "shape": {
          "type": "Record",
          "attributes": {}
        }
      },
      "Application": {
        "shape": {
          "type": "Record",
          "attributes": {}
        },
        "memberOfTypes": [
          "Tenant"
        ]
      }
    },
    "actions": {
      "get /tenants/{tenant_id}/items": {
        "appliesTo": {
          "principalTypes": [
            "Tenant",
            "Client"
          ],
          "resourceTypes": [
            "Application"
          ],
          "context": {
            "type": "ContextType"
          }
        }
      },
      "post /tenants/{tenant_id}/items": {
        "appliesTo": {
          "principalTypes": [
            "Tenant",
            "Client"
          ],
          "resourceTypes": [
            "Application"
          ],
          "context": {
            "type": "ContextType"
          }
        }
      },
      "get /items": {
        "appliesTo": {
          "principalTypes": [
            "Tenant",
            "Client"
          ],
          "resourceTypes": [
            "Application"
          ],
          "context": {
            "type": "ContextType"
          }
        }
      }
    }
  }
}

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/terraform/cedar/cedarschema.json

Policy1

スキーマが定義できたら、次はポリシーを登録します。

こちらは、get /itemsのAPIに対して、認証済みのユーザーであればアクセスを許可するポリシーです。

permit (
  principal,
  action in FastapiApp::Action::"get /items",
  resource
);

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/terraform/cedar/policy1.cedar

Policy2

つづいて、特定のテナントclassmethodに所属するユーザーが、そのテナントのリソースに対して特定の操作(一覧取得・追加)を行えることを許可するポリシーです。

一見、when句が冗長に見えますが、resource in FastapiApp::Tenant::"classmethod"の部分が重要で、これを追加することで、classmethodテナントに所属するユーザーが他のテナントのリソースにアクセスできないように制御しています。

permit (
  principal in FastapiApp::Tenant::"classmethod",
  action in
    [FastapiApp::Action::"get /tenants/{tenant_id}/items",
     FastapiApp::Action::"post /tenants/{tenant_id}/items"],
  resource
)
when
{
  principal in FastapiApp::Tenant::"classmethod" &&
  resource in FastapiApp::Tenant::"classmethod"
};

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/terraform/cedar/policy2.cedar

Policy3

Policy2と同様に、特定のテナントannotationに所属するユーザーが、そのテナントのリソースに対して特定の操作(一覧取得・追加)を行えることを許可するポリシーです。

permit (
  principal in FastapiApp::Tenant::"annotation",
  action in
    [FastapiApp::Action::"get /tenants/{tenant_id}/items",
     FastapiApp::Action::"post /tenants/{tenant_id}/items"],
  resource
)
when
{
  principal in FastapiApp::Tenant::"annotation" &&
  resource in FastapiApp::Tenant::"annotation"
};

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/terraform/cedar/policy3.cedar

Policy4 by Policy Template

ここでは、AVPのポリシーテンプレートを利用して、client credentials flow(Machine to Machine)を利用したアクセスのためのポリシーを登録します。

?principalは、ポリシーテンプレートのプレースホルダで、実際のポリシーの登録時に、具体的な値に置き換えられます。
今回は、Cognitoで発行したM2M用のクライアントのclientIdを設定しました。

ポリシーテンプレートに関連づけられたポリシーは、ポリシーテンプレート側の変更を反映することが可能です。
下記のテンプレートから分かるように、今回M2MアクセスへはReadOnlyアクセスを許可するように設定しています。

  • template
permit (
  principal == ?principal,
  action in [FastapiApp::Action::"get /tenants/{tenant_id}/items"],
  resource
);

下記がテンプレートから、実際に作成されたポリシーになります。

  • policy
permit (
    principal == FastapiApp::Client::"6tpsbt0o9hbjrso9at1m59g74j",
    action in [FastapiApp::Action::"get /tenants/{tenant_id}/items"],
    resource
);

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/terraform/cedar/template1.cedar

Pythonのコード(アクセス制御部分)

実際のアクセス制御を行うFastAPIのコードは、以下のように実装します。

ポイントは、AVPへアクセス判定をリクエストする際に、entitiesにユーザーとテナントの親子関係の情報を付与している点です。

これは、AVPないしCedar自体には、ユーザーやテナントなどのエンティティ自体の情報を管理する機能を持たないため、別途Entityを管理する仕組み(以下エンティティストアと呼び)を用意し、そこから必要な情報を取得してAVPに渡す必要があることを意味しています。

今回は、Cognito User Poolのユーザーとグループ情報を利用しているため、Cognitoの一部がエンティティストアとして機能していますが、特に制限はありません。
ID基盤が持つアクセス制御に縛られずに、独自のエンティティストアを用意して、必要な情報をAVPに渡すといった設計も考えられます。

     # action_idはAPIのリクエストパスに基づいて動的に設定
        action_id = f"{request.method.lower()} {request.scope["route"].path}"
        # pathに含まれるテナントIDを取得
        path_tenant_id = request.path_params.get("tenant_id")
        # 検証済みのtoken payloadからユーザーのアクセスかクライアントのアクセスかを判定
        is_user_access = payload.get("username")

        if is_user_access:
            # ユーザーのアクセス制御
            avp_input = {
                "policyStoreId": POLICY_STORE_ID,
                "principal": {
                    "entityType": "FastapiApp::User",
                    "entityId": payload.get("sub"),
                },
                "action": {
                    "actionType": "FastapiApp::Action",
                    "actionId": action_id 
                },
                "resource": {
                    "entityType": "FastapiApp::Application",
                    "entityId": "Any"
                },
                "entities": { # entitiesにユーザーとテナントの親子関係の情報を付与してAVPに渡す
                    "entityList": [
                        {
                            "identifier": {
                                "entityType": "FastapiApp::User",
                                "entityId": payload.get("sub")
                            },
                            "parents": list(map(
                                lambda tenant_id: {
                                    "entityType": "FastapiApp::Tenant",
                                    "entityId": tenant_id
                                },
                                payload.get("cognito:groups", [])
                            ))
                        },
                        {
                            "identifier": {
                                "entityType": "FastapiApp::Application",
                                "entityId": "Any" # 今回Resourceは明示しないのでAnyに固定
                            },
                            "parents": [
                                {
                                    "entityType": "FastapiApp::Tenant",
                                    "entityId": path_tenant_id # リソースの親にはリクエストパスから取得したテナントIDを設定
                                }] if path_tenant_id else []
                        }
                    ]
                }
            }
            avp_result = avp_client.is_authorized(**avp_input) # AVPにアクセス制御のリクエストを送信
            if avp_result.get("decision") != "ALLOW":
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Not authorized",
                    headers={"WWW-Authenticate": "Bearer"}
                )
            return User(
                sub=payload.get("sub"),
                tenants=payload.get("cognito:groups", [])
            )
        else:
            # クライアントのアクセス制御
            avp_input = {
                "policyStoreId": POLICY_STORE_ID,
                "principal": {
                    "entityType": "FastapiApp::Client",
                    "entityId": payload.get("client_id"),
                },
                "action": {
                    "actionType": "FastapiApp::Action",
                    "actionId": action_id
                },
                "resource": {
                    "entityType": "FastapiApp::Application",
                    "entityId": "Any"
                }
            }
            avp_result = avp_client.is_authorized(**avp_input)
            if avp_result.get("decision") != "ALLOW":
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Not authorized",
                    headers={"WWW-Authenticate": "Bearer"}
                )
            return Client(
                id=payload.get("client_id")
            )
...

コード: https://github.com/seiichi1101/avp-sample-with-fastapi/blob/main/app/auth.py

Cognito User Poolの設定

Cognito User Poolのグループ機能を利用して、ユーザーとグループの作成を行い、ユーザーをグループに所属させます。

cognito-groups

アクセス制御が出来るか確認する

ここではユースケースを毎に、テナントやユーザー属性に基づいたアクセス制御が出来るか確認します。

特定のテナントclassmethodに所属するユーザーのアクセス

  • GET /items では、所属するテナントclassmethodのアイテム一覧を取得できること
curl -X 'GET' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>'

[
  {
    "id": 1,
    "tenant_id": "classmethod"
  },
  {
    "id": 2,
    "tenant_id": "classmethod"
  },
  {
    "id": 3,
    "tenant_id": "classmethod"
  }
]
  • GET /tenants/classmethod/itemsでアイテム一覧を取得できること
curl -X 'GET' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/tenants/classmethod/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>'

[
  {
    "id": 1,
    "tenant_id": "classmethod"
  },
  {
    "id": 2,
    "tenant_id": "classmethod"
  },
  {
    "id": 3,
    "tenant_id": "classmethod"
  }
]
  • GET /tenants/annotation/itemsへはアクセスできないこと
curl -X 'GET' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/tenants/annotation/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>'

{
  "detail": "Not authorized"
}
  • POST /tenants/classmethod/itemsにアイテムを追加できること
curl -X 'POST' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/tenants/classmethod/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d ''

{
  "id": 7,
  "tenant_id": "classmethod"
}

M2Mクライアントのアクセス制御

  • GET /items では全てのアイテム一覧を取得できること
curl -X 'GET' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>'

[
  {
    "id": 1,
    "tenant_id": "classmethod"
  },
  {
    "id": 2,
    "tenant_id": "classmethod"
  },
  {
    "id": 3,
    "tenant_id": "classmethod"
  },
  {
    "id": 4,
    "tenant_id": "annotation"
  },
  {
    "id": 5,
    "tenant_id": "annotation"
  },
  {
    "id": 6,
    "tenant_id": "annotation"
  },
  {
    "id": 7,
    "tenant_id": "classmethod"
  }
]
  • GET /tenants/classmethod/itemsでアイテム一覧を取得できること
curl -X 'GET' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/tenants/classmethod/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>'

[
  {
    "id": 1,
    "tenant_id": "classmethod"
  },
  {
    "id": 2,
    "tenant_id": "classmethod"
  },
  {
    "id": 3,
    "tenant_id": "classmethod"
  },
  {
    "id": 7,
    "tenant_id": "classmethod"
  }
]
  • POST /tenants/classmethod/itemsへはアクセスできないこと
curl -X 'POST' \
  'https://example.execute-api.ap-northeast-1.amazonaws.com/tenants/classmethod/items' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d ''

{
  "detail": "Not authorized"
}

所感

実際にAVPとCedarを使ってみて感じた点をいくつか挙げます。

デザイン力が問われる

Cedarは非常に柔軟なポリシー言語であり、複雑なビジネスロジックや条件付きのアクセス制御も記述可能です。

しかし、その自由度の高さゆえに、デザイン力が求められます。

例えば、今回はAVPのデモを参考に、ポリシーのActionsに静的な値(パス)を設定し、Resourcesは特に設定しない形で実装しました。
こうすることで、ポリシーの設計が比較的シンプルになり、理解しやすくなりました。

しかし、実際にはリソースはResourcesに、アクションはActionsに明示的に分けて設計する方が一般的かもしれません。
制限対象のリソースをActionsに設定するか、Resourcesに設定するかは設計上の悩みどころです。

また、今回利用しませんでしたが、下記の様により抽象的なpolicyを書くことも出来ます。

permit (
  principal,
  action in
    [FastapiApp::Action::"get /tenants/{tenant_id}/items",
     FastapiApp::Action::"post /tenants/{tenant_id}/items"],
  resource
)
when { principal in resource };

こちらは、許可するActionはpolicyで制約されていますが、あとは「principal in resource」という条件を満たす限り、どのようなリソースに対しても適用可能となります。

つまり、principalとresourceの親が同じであれば、どのようなリソースに対してもアクセスを許可することができます。
そのため、この場合、テナント所属確認ロジックはCedar上で評価されないので、クライアント側で明示的に実装する必要があります。
あくまで、ここでは「親が同じであれば、下記のActionに対しては許可する」というポリシーを示しています。

いずれにせよ、アクセス制御部分は一度実装してしまうと、なかなか変更しずらい部分だと思うので、拡張性を持たせる形で慎重に設計する必要があり、とてもデザイン力が必要だと感じました。
下記に、ユースケース別のサンプルがあるので、検討する際は目を通しておくと良さそうです。

https://github.com/cedar-policy/cedar-examples/tree/release/4.4.x/cedar-example-use-cases

Entity Storeの考慮も必要

AVPはポリシーの管理と、評価エンジンを提供しますが、実際のエンティティ(ユーザーやリソース)に関連する情報は、外部ストアが必要です。
今回の様に認証基盤の機能を利用して、疑似的にエンティティストアとすることも可能ですが、アクセス制御をより柔軟に行うためには、独自のエンティティストアを用意するのが良さそうです。

特に、マイクロサービス環境化での利用を想定する場合、各サービスと比べ、アクセス制御部分は1つ上のレイヤーになるかと思います。
システム全体に大きく影響するため、高可用で柔軟な連携ができる仕組みを検討する必要がありそうです。

パッと思いつくのは、DynamoDBやMomentのような低レイテンシで高アクセスをさばけるようなデータストアですが、負荷試験なども含めて十分な検証が必要そうです。

ポリシーテンプレートの制約

ポリシーテンプレートでは、principalresourceのみがプレースホルダ化でき、actionはできません。

また、テンプレートポリシーのwhen句で?principalが使えないなど、テンプレートには一定の制約があります。
下記の様なテンプレートを作成したかったのですが、現状では実現できません。

ここら辺は、Cedarの設計理念もあると思うので、もう少しCedar自体の理解を深める必要がありそうです。

permit (
  principal in ?principal,
  action in
    [FastapiApp::Action::"get /tenants/{tenant_id}/items",
     FastapiApp::Action::"post /tenants/{tenant_id}/items"],
  resource
)
when
{
  principal in ?principal &&
  resource in ?principal
};

https://docs.cedarpolicy.com/policies/templates.html

運用面での課題感

ポリシーの作成は、Cedar言語に慣れていないと難しい部分があります。
たとえば、AVPのコンソール画面から、非エンジニアの方に、直接ポリシー(アクセス制御)を追加・編集してもらうといったことは難しいです。

運用のやり方次第ではありますが、エンジニア以外の人がするユースケースがあるのであれば、何かしらのUIやツールでラップしてあげる必要がありそうです。

その他

  • IsAuthorizedWithToken の利用は微妙かも

今回は利用しませんでしたが、AVPにIdPの設定を追加することで、AVPのIsAuthorizedWithTokenを利用した際に、「トークンの検証」と「アクセス制御の判定」の両方をAVPがやってくれます。

イントロスペクションエンドポイント的に使えそうなのですが、失敗した際のレスポンスから詳細な情報が取得できないため、アプリ側でのクライアントへのレスポンス生成する際に困ります。
また、どこまでスケールするかは不明です。

  • AVPによるカスタムオーソライザー自動作成はチュートリアルとしては使える

AWS Verified Permissionsで自動セットアップしてくれる機能があり、API GatewayとIdPの情報から、Policy Storeの作成、Policyの作成、API Gatewayのカスタムオーソライザー用のLambda(Token検証とACL含む)の作成などを自動で行ってくれます。

ただし、実際のプロジェクトでは、リソースやコードの管理はそれぞれのツールを使って行うことになるし、認可のコードは失敗した際のレスポンスもいろいろ書くことあるので、利用は現実的ではないかもしれません。

ただ、AVPの使い方を理解するにはとても良いです。

https://www.youtube.com/watch?v=OBrSrzfuWhQ

まとめ

Cedar自体が汎用的なポリシー言語であるため、自由度が高いですが、その反面デザイン力を求められます。

今回の利用ケースでは、Resourceを利用していなかったため比較的理解しやすいですが、真面目にResourceを利用していくと、ポリシーの設計の難易度が格段に上がりそうです。

アクセスコントロールについて深く検討していなかったユーザーにとっては、どこから手をつけていいのかわからないのかもしれません。
そんな方は、是非クラスメソッドに相談してみてください!

また、今後のアップデートやエコシステムの発展にも注目したいところです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.