kakakakakku blog

Weekly Tech Blog: Keep on Learning!

AWS CDK で Amazon API Gateway の「CloudWatch ログのロール ARN」を設定する

Amazon API Gateway でアクセスログを有効化してデプロイしようとすると CloudWatch Logs role ARN must be set in account settings to enable logging というエラーが出る場合がある😇もしかしたら Amazon API Gateway の設定「CloudWatch ログのロール ARN (CloudWatch log role ARN)」が不足している可能性がある.

「CloudWatch ログのロール ARN」は Amazon API Gateway の API 単位の設定ではなく「アカウント/リージョン」単位の設定で,少し珍しいと思う.もしマルチアカウント戦略を採用できてなく,複数のプロダクトで同じアカウントを共有していたりすると,管理が面倒になることもあると思う😇

AWS CDK でエラーに対処する

選択肢 1: aws_apigateway.RestApi

まず aws_apigateway.RestApi には cloudWatchRole という設定があって(デフォルトは false),true にすると自動的に IAM Role を追加して Amazon API Gateway に設定してくれる👌以下にサンプルコードを載せておく.

docs.aws.amazon.com

しかし後述する理由で使わない方が良いと思う💨

import {
    Stack,
    StackProps,
    aws_apigateway,
    aws_logs,
} from 'aws-cdk-lib'
import { Construct } from 'constructs'

export class ApiGatewayCloudWatchRoleStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props)

        const apiAccessLogGroup = new aws_logs.LogGroup(this, 'ApiGatewayAccessLogGroup', {
            logGroupName: 'sandbox-api-gateway-access-logs',
            retention: aws_logs.RetentionDays.ONE_MONTH,
        })

        const api = new aws_apigateway.RestApi(this, 'ApiGateway', {
            restApiName: 'sandbox-api-gateway-cloudwatch-role',
            deployOptions: {
                accessLogDestination: new aws_apigateway.LogGroupLogDestination(apiAccessLogGroup),
                accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields(),
            },
            cloudWatchRole: true,
        })
        api.root.addMethod('GET', new aws_apigateway.HttpIntegration('https://dog.ceo/api/breeds/image/random'))
    }
}

実際にデプロイすると arn:aws:iam::000000000000:role/ApiGatewayCloudWatchRoleS-ApiGatewayCloudWatchRoleA-nA8OCNOvnkCS という IAM Role が設定されていた💡

もし cloudWatchRole を設定した Amazon API Gateway を2つ以上デプロイする場合に IAM Role が上書きされてしまうという課題がある.

import {
    Stack,
    StackProps,
    aws_apigateway,
    aws_logs,
} from 'aws-cdk-lib'
import { Construct } from 'constructs'

export class ApiGatewayCloudWatchRoleStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props)

        const apiAccessLogGroup = new aws_logs.LogGroup(this, 'ApiGatewayAccessLogGroup', {
            logGroupName: 'sandbox-api-gateway-access-logs',
            retention: aws_logs.RetentionDays.ONE_MONTH,
        })

        const api = new aws_apigateway.RestApi(this, 'ApiGateway', {
            restApiName: 'sandbox-api-gateway-cloudwatch-role',
            deployOptions: {
                accessLogDestination: new aws_apigateway.LogGroupLogDestination(apiAccessLogGroup),
                accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields(),
            },
            cloudWatchRole: true,
        })
        api.root.addMethod('GET', new aws_apigateway.HttpIntegration('https://dog.ceo/api/breeds/image/random'))

        const api2 = new aws_apigateway.RestApi(this, 'ApiGateway2', {
            restApiName: 'sandbox-api-gateway-cloudwatch-role2',
            deployOptions: {
                accessLogDestination: new aws_apigateway.LogGroupLogDestination(apiAccessLogGroup),
                accessLogFormat: aws_apigateway.AccessLogFormat.jsonWithStandardFields(),
            },
            cloudWatchRole: true,
        })
        api2.root.addMethod('GET', new aws_apigateway.HttpIntegration('https://dog.ceo/api/breeds/image/random'))
    }
}

実際にデプロイすると arn:aws:iam::000000000000:role/ApiGatewayCloudWatchRoleS-ApiGateway2CloudWatchRole-gT1C2Om2Nr4Y という IAM Role に上書きされていた😇 AWS CDK でリソースを管理しているのにデプロイするリソースの順番によって「CloudWatch ログのロール ARN」の値が変わってしまうのは微妙だと思う.

選択肢 2: aws_apigateway.CfnAccount

そこで aws_apigateway.CfnAccount を使うと「CloudWatch ログのロール ARN」を独立したリソースとして管理できる❗️ドキュメントには以下のように書いてあった.できれば AWS CDK Stack も別にしておくと良いと思う👌

To avoid overwriting other roles, you should only have one AWS::ApiGateway::Account resource per region per account.

docs.aws.amazon.com

以下のように aws_iam.Roleaws_apigateway.CfnAccount を組み合わせて実装できる.

import {
    Duration,
    Stack,
    StackProps,
    aws_apigateway,
    aws_iam,
} from 'aws-cdk-lib'
import { Construct } from 'constructs'

export class ApiGatewayAccountStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props)

        const role = new aws_iam.Role(this, 'ApiGatewayLogsRole', {
            roleName: 'api-gateway-logs-role',
            assumedBy: new aws_iam.ServicePrincipal('apigateway.amazonaws.com'),
            managedPolicies: [
                aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonAPIGatewayPushToCloudWatchLogs'),
            ],
            maxSessionDuration: Duration.hours(1),
        })

        new aws_apigateway.CfnAccount(this, 'ApiGatewayAccount', {
            cloudWatchRoleArn: role.roleArn,
        })
    }
}

実際にデプロイすると arn:aws:iam::000000000000:role/api-gateway-logs-role という指定した IAM Role を設定できていた👏

関連ドキュメント

docs.aws.amazon.com