AWS AppSync API の認可方式について

SCSKの畑です。2回目の投稿です。

今回は、以下のようなアーキテクチャにおけるAWS AppSync API 実行時の認可について、どのように設計・実装したのかについてまとめました。

前提条件

まず、説明にあたり前提条件について記載しておきます。今回は言及すべき案件のバックグラウンドもとい事情があまりなかったため、簡単にまとめました。図に関しては初回のエントリからの再掲です。

構築・開発中のアプリケーションアーキテクチャ概要図です。

AWS AppSync API の呼び出し元

少し分かりづらいのですが、上記アーキテクチャにおいて AWS AppSync の API を呼び出しているのは、以下2つとなります。

  • アプリケーション(正確にはクライアント端末の Web ブラウザ上で動作しているアプリケーション)
  • AWS Lambda

Lambda に関しては必ずしも AppSync API を使用する必要はないのですが、アプリケーションと同一の仕組みを使用した方が効率など含めて都合が良いことが多かったためそのような方針としました。

アプリケーションユーザの管理・認証方式

こちらは図から分かる通り、Amazon Cognito を使用しています。案件によってはお客さんの管理する外部 IdP から連携したりすることもあると思いますが、今回はそのような要件がなかったため完全に Cognito に閉じた方式としています。

また、こちらも図内に記載の通りですが、本アプリケーションを使用するユーザは Cognito で認証されていることが前提となります。

採用した AWS AppSync API の認可方式

以下ドキュメントの通り、現在5種類の認可方式がサポートされています。

  1. API キー認可: 認証されていない API のスロットリングを制御し、シンプルなセキュリティオプションを提供します。
  2. Lambda 認可: 関数の入力と出力を詳細に説明するカスタム認可ロジックを有効にします。
  3. IAM 認可: AWS の署名バージョン 4 の署名プロセスを使用し、IAM ポリシーによるきめ細かなアクセスコントロールを可能にします。
  4. OpenID Connect 認可: ユーザー認証のために OIDC 準拠のサービスと統合します。
  5. Cognito ユーザープール: Cognito のユーザー管理機能を使用して、グループベースのアクセスコントロールを実装します。
GraphQL API を保護するための認可と認証の設定 - AWS AppSync
AWS AppSyncユーザーの認証と認可について学習する。

先述した前提条件を踏まえ、今回は認可方式として IAM を選択しました。

まず、以下理由の通り、APIキー、Lambda、OpenID connect の3つは候補から外れました。

  • API キー:有効期限が最大1年間であり、毎年キーを更新する必要があるため本番運用に適さない
  • Lambda:認可に際し、複雑なロジックを組む必要のあるような要件がない
  • OpenID connect:連携元の IdP 等が存在しないため考慮外

すると IAM or Cognito ユーザープールの2択となりますが、Lambda から AWS AppSync API を呼び出す場合は(この2択だと)IAM しかありません。実質的に Lambda が AppSync のバックエンドとして動作する(=アプリケーションから直接 Lambda を実行しない)以上、Lambda が直接 Cognito の認証情報を扱う必要がないためです。Cognito によって認証/認可されたユーザが AppSync 経由で Lambda を実行することになるため、認可フローとしても問題ありません。

となると、後は AppSync 側でどちらの方式を使用するかになりますが、 Cognito の場合は上記の通りグループ単位のアクセス制御となります。このため、グループ単位で API のアクセス制御を行うような要件があれば必要になるものの、今回はそのような要件がなかったため、 IAM で統一した方がトータルで効率的と判断しました。

AWS IAM ポリシーの権限設定

ということで、まずは IAM ポリシーの権限設定ですが、こちらは以下ドキュメントの通りです。下記設定例では Resource 句をドキュメント通りに厳密に設定していますが、特定の AppSync API 配下の query や mutation などを全て実行できるようにするだけであれば 「”arn:aws:appsync:${Region}:${Account}:apis/${GraphQLAPIId}/*”」としてしまっても良いと思います。

AWS AppSync のアクション、リソース、および条件キー - サービス認可リファレンス
Word へのアクセスを制御するために IAM ポリシーで使用できるサービス固有のリソースやアクション、条件キーを一覧表示します AWS AppSync。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "1",
      "Effect": "Allow",
      "Action": [
        "appsync:GraphQL"
      ],
      "Resource": [
        "arn:aws:appsync:${Region}:${Account}:apis/${GraphQLAPIId}",
        "arn:aws:appsync:${Region}:${Account}:apis/${GraphQLAPIId}/types/*/fields/*",
      ]
    }
  ]
}

Lambda については、上記内容を含む IAM ロールを実行ロールに割り当てるだけで権限上は OK です。

AWS Cognito における IAM ロールとの紐付け

一方、アプリケーションから AppSync API を実行する場合、 本案件においては Cognito でユーザ認証されていることを前提としています。つまり、Cognito 経由で付与される IAM ロールに先述した IAM ポリシーの内容が含まれていれば権限上 OK ということになります。

Cognito 経由でアプリケーションユーザに IAM ロールを付与する方法は大別して以下の2パターンあるかと思いますが、先述の通りグループ単位で IAM 権限を制御する要件がなく、純粋なアプリケーション上の権限管理のみに使用しています。そのため、前者の場合は IAM ロール付与用/アプリケーション上の権限管理用という用途の異なるグループが併存してしまうため、後者の方がスマートと判断しました。

  • ユーザプールのグループに IAM ロールを割り当てて、そのグループにユーザを追加
  • ユーザプールと ID プールを紐付けた上で、ID プールの「認証されたアクセス」に IAM ロールを割り当て

1点、ID プールに割り当てる IAM ロールには、以下ドキュメントに記載されているような信頼関係の設定が必要なことに留意してください。こちらも設定例載せようと思ったのですが、ほぼドキュメントの内容そのままだったので省略します。

IAM roles - Amazon Cognito
Use IAM roles with Amazon Cognito identity pools.

なお、後者の仕組みにおいて、Cognito ユーザ認証されていないアクセスにも「ゲストアクセス」として IAM ロールを割り当てることができます。こちらについては今回使用していないのですが、今考えてみるとアプリケーションの初期化処理などには有用かもしれません。このあたりの話は別の機会に改めて書きたいと思います。

AWS AppSync における設定(AWS Amplify 使用時)

最後に、冒頭で記載したような AppSync の認可設定をする必要があります。今回 AppSync の構成・設定に Amplify を使用しているため、その設定ファイルの種類ごとに記載しています・・が、後述するようにここでハマりました。

schema.graphql ファイル

これは Amplify でアプリケーション開発を行う際に必ず編集するファイルかと思いますので、詳細については割愛します。記法や設定できる認可ルールについては以下ドキュメント等を参照ください。 

Customize authorization rules - JavaScript - AWS Amplify Gen 1 Documentation
Add authorization rules to your GraphQL schema to control access to your data. AWS Amplify Documentation

ここでは、AppSync のデータソースとして使用しているDynamoDB と Lambda 関数の実装例を記載してみます。

アプリケーション開発や動作確認の観点から便利なため、下記例ではAPIキーについても認可対象に追加しています。このように1つの type や query などに対して複数の認可対象を指定することが可能です。
DynamoDB
type AccessControl
@model
@auth(rules: [
    {allow: public, provider: apiKey},
    {allow: private, provider: iam},
])
{
    id: String! @primaryKey
    groups: [String!]
}

認可については @auth 句にて設定しています。許可するオペレーション(read / writeなど)も詳細に指定できますが、今回はそのような要件はないため指定していません。

Lambda 関数
type Query {
    ExecuteStateMachine(input: ExecuteStateMachineInput!): ExecuteStateMachineResponse
    @function(name: "<Lambda関数名>")
    @aws_api_key
    @aws_iam
}

Lambda 関数をデータソースとする場合は Query ないしは Mutation を使用することになりますが、その定義として @function 句で Lambda 関数名を指定しています。認可については上記の通り「@aws_api_key」「@aws_iam」のように指定しています。

custom-roles.json ファイル

さて、実装観点ではこのセクションが本投稿のメインの内容です。つまりハマりどころでした。

ここまでの内容で AppSync の IAM 認可に必要な設定は9割方完了していますが、この状態でアプリケーション or Lambda 関数から AppSync API を実行すると、以下のような権限エラーが出て失敗してしまいます。(以下はアプリケーションから実行した場合の例)

{
    "path": [
        "ExecuteStateMachine"
    ],
    "data": null,
    "errorType": "Unauthorized",
    "errorInfo": null,
    "locations": [{
        "line": 2,
        "column": 3,
        "sourceName": null
    }],
    "message": "Not Authorized to access ExecuteStateMachine on type Query"
}

ただ、先に記載した IAM ポリシー/ロールの設定において必要な権限が設定されているにも関わらず、なぜこのような事象が発生するのかが当初よく理解できなかったこともあり、開発中に本事象が発生した際は難儀しました。。最終的に以下のサイト様の情報に辿り着き、同情報をベースにAmplify 公式ドキュメント等を見返してようやく解決に至った次第です。

Amplify GraphQL APIをLambda Functionから操作する

まず、結論から書いてしまうと、schema.graphql ファイルと同じ階層に以下設定で custom-roles.json ファイルを作成し、再度 amplify push することで認可が通るようになりました。

{
    "adminRoleNames": ["arn:aws:sts::${Account}:assumed-role"]
}

何故これで上手くいくようになったのかですが、まずは以下 Amplify 公式ドキュメントの内容を見てみましょう。

Customize authorization rules - JavaScript - AWS Amplify Gen 1 Documentation
Add authorization rules to your GraphQL schema to control access to your data. AWS Amplify Documentation

IAM-based @auth rules are scoped down to only work with Amplify-generated IAM roles. To access the GraphQL API with IAM authorization within your AppSync console, you need to explicitly allow list the IAM user’s name. Add the allow-listed IAM users by adding them to amplify/backend/api/<your-api-name>/custom-roles.json. (Create the custom-roles.json file if it doesn’t exist). Append the adminRoleNames array with the IAM role or user names:

{ “adminRoleNames”: [“<YOUR_IAM_USER_OR_ROLE_NAME>”] }

するといきなり衝撃的なことが書いてあります。曰く、schema.graphql ファイルの項目で記載したような @auth のような記法はあくまでも Amplify が生成した IAM ロールに対して有効であり、任意の IAM ロールを指定したい場合は先述したような内容のファイルを作成する必要があるとのこと。(ドキュメント内では AppSync コンソールから API を実行したい場合の設定という記載ですが)

そもそも、Amplify によって作成された IAM ロールなるものに心当たりがなかったので、AWS マネジメントコンソールから探してみたところ、確かに API 名の付いている IAM ロールができているんですよね。

このへんに気付けなかったのは、今回 Cognito を Amplify の構成対象外としていることも影響してるかもしれません。IAM ロールの名前や生成されるパイプラインリゾルバの設定等からも、ID プールの「認証されたアクセス」「ゲストアクセス」にそれぞれ割り当てられる想定のものなのかなと推測します。

amplifyが自動作成するIAMロールの情報です

とは言うものの、IAM ロールの中身を見る限りは普通に AppSync 関連の権限設定が入っているだけだったので、どこに有意な差があるか分からなかったので、Amplify が構成した スキーマ内のパイプラインリゾルバの中身を改めて見た所、その疑問が氷解しました。

パイプラインリゾルバ内のbefore mapping template(adminRoles設定前)

まず、Before mapping template で authRole や unAuthRole の ARN が定義されています。

パイプラインリゾルバ内認可用関数のロジック(adminRoles設定前)

実際にパイプラインリゾルバ実行時の認可を司っているのは、末尾に「auth0Function」という名前の付いている関数になるようです。こちらも中身を見たところ、要するに $context.identity.userArn が authRole と同一だった場合に認可される(=Amplify が生成した IAM ロールと同一かを判定している)ことが分かりました。つまりこれが有意な差であり、この判定を通すために冒頭で記載したようなワークアラウンドが必要となると理解しました。

AWS AppSync のリゾルバーのマッピングテンプレートのコンテキストリファレンス - AWS AppSync
AWS AppSync のリゾルバーのマッピングテンプレートのコンテキストリファレンス

実際に adminRoleNames の設定を追加してみると、

パイプラインリゾルバ内のbefore mapping template(adminRoles設定後)

このように、Before mapping template にて adminRoles の定義が追加されています。

パイプラインリゾルバ内認可用関数のロジック(adminRoles設定後)

認可のロジックについても変更されており、$context.identity.userArn が adminRoles で定義されている ARN を含んでいれば、パイプラインリゾルバ実行が認可されるようになっています。では、adminRoles に Lambda の実行ロール及び Cognito ID プールの「認証されたアクセス」に割り当てた IAM ロールの ARN を指定すれば良いのかというと、そうではありません。

先に示した Before mapping template における authRole や unAuthRole の定義を見ると分かる通り、これらの ARN は IAM ロール自身のものではなく、Cognito ID プールでの認可時にAssumeRole により発行される一時クレデンシャルのものになっています。Lambda 実行時も同様に一時クレデンシャルが発行されるため、どちらの場合も IAM ロール自身の ARN とは異なってくるためです。

ただ、ARN は以下の通り「arn:aws:sts::${Account}:assumed-role」から始まるため、それを adminRoleNames の定義として指定してあげれば良いという結論となります。この設定追加により、無事にアプリケーション及び Lambda から AppSync API が実行できるようになりました。

  • Lambda
    • arn:aws:sts::${Account}:assumed-role/LambdaExecutionRole/${Session}
  • Cognito
    • arn:aws:sts::${Account}:assumed-role/${IAMRoleName}/CognitoIdentityCredentials

まとめ

AppSync の認可に IAM を使用するという方針そのものはそれほど時間を要さずに決められたのですが、どのように設計・実装するかという部分についてはハマった点も含めて FIX するのに時間を要しました。一方で、AppSync のパイプラインリゾルバの中身や AssumeRole などの仕組みについてイメージでしか掴めていなかった部分も含めて理解が深まったことは、今後の案件対応を考えると非常に良かったと思っています。

本記事の内容がどなたかの役に立てば幸いです。

タイトルとURLをコピーしました