Open1

Tips: API GatewayのRequest Authorizerの罠(AWS)

ないつぅーないつぅー

Request Authorizerの罠にハマったのでメモ
生成するauthorizerが返却するIAMを気をつけないと、identitySources(認証結果をキャッシュする際のキー値)が不整合になるよと言う話です。

具体例

CDKで以下のようにAPI GatewayのRequest Authorizerを定義したとする。

const authorizerTeam = new apigateway.RequestAuthorizer(this, 'WebAPIAuthrizerWorkspace', {
  authorizerName: getLokaV2ResourceName(this, 'webApiAuthWorkspacePrivate'),
  handler: new V2Lambda(this, 'WebAPIAuthrizerLambdaWorkspace', {
    srcFileFolder: path.join(__dirname, './bin', 'authorizer', 'workspace'),
    description: 'webapi lambda authrizer workspace private',
  }),
  identitySources: [
    apigateway.IdentitySource.header('Authorization'), 
    apigateway.IdentitySource.header('Workspace'), 
    apigateway.IdentitySource.context('resourcePath'),
    apigateway.IdentitySource.context('httpMethod'),
  ],
});

identitySourcesを以下の設定である。

  • header('Authorization'):認証トークン
  • header('Workspace'):ユーザが操作対象としているワークスペース
  • context('resourcePath'):APIエンドポイント
  • context('httpMethod'):APIエンドポイントに対するHTTPメソッド

authorizerが生成するIAMが以下とすると不具合を起こす。(Golangっぽい擬似コード)

resource := context('methodArn')

return events.APIGatewayCustomAuthorizerResponse{
    PrincipalID: (トークンから取得したユーザーのsub),
    PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{
    Version: "2012-10-17",
    Statement: []events.IAMPolicyStatement{
        {
            Action:   []string{"execute-api:Invoke"},
            Effect:   effect,
            Resource: []string{resource},
        },
    },
}

解説

問題箇所は「context('methodArn')をIAMのResourceに指定」しているところ。
context('methodArn')はarnが取れるがパスパラメータが存在するエンドポイントの場合、例として「arn:aws:execute-api:ap-northeast-1:999999999:apigateway_unique/prod/GET/users/100/posts」のようになる。
IdentitySourceに指定している「context('resourcePath')」は「/users/{user_id}/posts」となる。
この場合、認証のキャッシュキーは「/users/{user_id}/posts」のため、初回に「/users/100/posts」へアクセスし、その後に「/users/200/posts」アクセスした場合に返却されるIAMは以下になる。

{
    PrincipalID: (トークンから取得したユーザーのsub),
    PolicyDocument: {
    Version: "2012-10-17",
    Statement: [
        {
            Action:   ["execute-api:Invoke"],
            Effect:   "effect",
            Resource: [arn:aws:execute-api:ap-northeast-1:999999999:apigateway_unique/prod/GET/users/100/posts],
        },
    ],
}

Resourceの部分のパスパラメータ部分が{user_id}=100の場合のIAMが返却されるため、{user_id}=200のアクセス時にこのIAMを利用すると権限エラーになる。
正しくはワイルドカードを使用した以下をResource値に返却しないといけない。
「arn:aws:execute-api:ap-northeast-1:999999999:apigateway_unique/prod/GET/users/*/posts」

疑似コードでは以下のような感じ。(eventはauthorizerに渡されている統合リクエストのパラメータ値)

apiResource := event.Resource // 例:/users/{user_id}/posts
method := event.HttpMethod    // 例:GET

arnParts := strings.Split(arn, method)
basePath := arnParts[0] + method
replacedPath := regexp.MustCompile(`\{[^}]+\}`).ReplaceAllString(apiResource, "*")

resource := basePath + replacedPath // 例:arn:aws:execute-api:ap-northeast-1:999999999:apigateway_unique/prod/GET/users/*/posts

return events.APIGatewayCustomAuthorizerResponse{
    PrincipalID: principalID,
    PolicyDocument: events.APIGatewayCustomAuthorizerPolicy{
        Version: "2012-10-17",
        Statement: []events.IAMPolicyStatement{
            {
                Action:   []string{"execute-api:Invoke"},
                Effect:   effect,
                Resource: []string{resource},
            },
        },
    },
    Context: contextData,
}

上記のようにすれば、キャッシュキーがパスパラメータ違いを無視し、実際に発行する権限もパスパラメータ違いを無視する権限となる。

残った謎

identitySourcesで指定できる値と、Authorizer側で受け取れるパラメータが現状よくわかっていない。
受け取るペイロードについて下記ドキュメントに発見した。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.troubleshooting

が、Golang上で「pathParameters」を以下のように取得しようとしたがこの場合、lambda handlerが正しく起動しなかった。(おそらく「APIGatewayRequestAuthorizerEvent」とペイロードのマッピングに失敗している模様)

type APIGatewayRequestAuthorizerEvent struct {
	MethodArn  string            `json:"methodArn"`
	Resource   string            `json:"resource"`
	HttpMethod string            `json:"httpMethod"`
	Headers    map[string]string `json:"headers"`
	PathParameters map[string]string      `json:"pathParameters"`
}

// API Gateway Lambda AuthorizerTeamPrivate
func AuthorizerTeamPrivate() func(ctx context.Context, event APIGatewayRequestAuthorizerEvent) (events.APIGatewayCustomAuthorizerResponse, error) {
    // IAM生成処理
}

API Gateway自身のログについて

本気でマッピングについて調べる場合は以下のログ設定をすることでAPI Gateway自身のログを出力させることができる模様。
これを設定しログ内容を確認すればもう少し分かるかも。(が、とりま解決したのでマッピング可能な値についてはこれ以上調査しないです。。。)

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-logging.html
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway-readme.html#access-logging