CDKのデプロイ前にセキュリティの静的解析ができるcdk-nagを試す

cdk-nagを触ってみた。

Security HubやAWS Configを使った設定チェックはデプロイ後のチェックになるのに比べ、cdk-nagはデプロイ前に静的解析できるのが特長。違反しているとそもそもデプロイできないので開発メンバーに確実にルールを守ってもらえる。CIにも組み込みやすい。

環境

  • node 16.15.0
  • typescript 5.0.2
  • aws-cdk-lib 2.70.0
  • constructs 10.1.289
  • cdk-nag 2.23.5

サンプルコードを用意

こんな感じのAPIGW+Lambda定義を用意する。

// lib/apigateway-stack.ts
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";

export class ApigatewayStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // APIGW Lambda関数
    const apiFn = new cdk.aws_lambda_nodejs.NodejsFunction(this, "apiFn", {
      runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
      entry: "src/lambda/api-handler.ts",
      bundling: {
        sourceMap: true,
      },
      timeout: cdk.Duration.seconds(29),
    });

    // APIGW
    const api = new cdk.aws_apigateway.RestApi(this, "api", {
      deployOptions: {
        tracingEnabled: true,
        stageName: "api",
      },
    });
    api.root.addProxy({
      defaultIntegration: new cdk.aws_apigateway.LambdaIntegration(apiFn),
    });
  }
}
// bin/aws-playground.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ApigatewayStack } from "../lib/apigateway-stack";

const region = "ap-northeast-1";
const app = new cdk.App();

new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});

以下コマンドを実行:

npx cdk synth 

特にエラー発生せずCFn定義が出力される。

cdk-nag設定を加える

cdk-nagによるチェックをかぶせてみる。ルールパックは特に何も考えず AwsSolutionsChecks を適用。

// bin/aws-playground.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { ApigatewayStack } from "../lib/apigateway-stack";
+ import { AwsSolutionsChecks } from "cdk-nag";

const region = "ap-northeast-1";
const app = new cdk.App();

new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});

+ cdk.Aspects.of(app).add(new AwsSolutionsChecks());

コマンド実行:

npx cdk synth 

そうすると以下のエラーがずらずらと出てくる:

[Error at /ApigatewayStack/apiFn/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/Resource] AwsSolutions-APIG2: The REST API does not have request validation enabled.

[Error at /ApigatewayStack/api/CloudWatchRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG1: The API does not have access logging enabled.

[Warning at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG3: The REST API stage is not associated with AWS WAFv2 web ACL.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG6: The REST API Stage does not have CloudWatch logging enabled for all methods.

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

Found errors

Errorに抑制もしくは対応していく(今回はWarningはスルー)。

エラーに対応する場合

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG1: The API does not have access logging enabled.

[Error at /ApigatewayStack/api/DeploymentStage.api/Resource] AwsSolutions-APIG6: The REST API Stage does not have CloudWatch logging enabled for all methods.

まず、上記ルールに対してリソース定義を追加することで対応してみる(正攻法)。

    // 追加
    const apiLogAccessLogGroup = new cdk.aws_logs.LogGroup(
      this,
      "apiAccessLogGroup",
      {
        logGroupName: `/aws/apigateway/apiAccessLogGroup`,
        retention: 365,
      }
    );
    // APIGW
    const api = new cdk.aws_apigateway.RestApi(this, "api", {
      deployOptions: {
        tracingEnabled: true,
        stageName: "api",
        // 追加
        loggingLevel: cdk.aws_apigateway.MethodLoggingLevel.INFO,
        accessLogDestination: new cdk.aws_apigateway.LogGroupLogDestination(
          apiLogAccessLogGroup
        ),
        accessLogFormat: cdk.aws_apigateway.AccessLogFormat.clf(),
      },
    });

スタックレベルでエラー抑制する場合

続いて抑制する場合。

[Error at /ApigatewayStack/api/Default/{proxy+}/ANY/Resource] AwsSolutions-COG4: The API GW method does not use a Cognito user pool authorizer.

[Error at /ApigatewayStack/apiFn/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies.

[Error at /ApigatewayStack/api/Resource] AwsSolutions-APIG2: The REST API does not have request validation enabled.

上記ルールに対してスタックレベルで抑制をかける。

const apigatewayStack = new ApigatewayStack(app, "ApigatewayStack", {
  env: { region },
});
// 追加
NagSuppressions.addStackSuppressions(apigatewayStack, [
  {
    id: "AwsSolutions-COG4",
    reason: "本サービスではCognitoを使用しない",
  },
  {
    id: "AwsSolutions-IAM4",
    reason: "本サービスではAWS管理ポリシーをアタッチしても良い",
  },
  {
    id: "AwsSolutions-APIG2",
    reason:
      "本サービスではリクエストバリデーションはLambda関数のロジックで行う",
  },
]);

エラー内容を厳密に確認したわけではないので、上記理由が要領を得ていないかもしれない可能性がある(その場合すみませんがコメントください)。

リソースレベルでエラー抑制する場合

[Error at /ApigatewayStack/api/Default/ANY/Resource] AwsSolutions-APIG4: The API does not implement authorization.

上記ルールに対してリソースレベルで抑制をかける。

    api.root.addProxy({
      defaultIntegration: new cdk.aws_apigateway.LambdaIntegration(apiFn),
    });
    // 追加
    NagSuppressions.addResourceSuppressions(
      api,
      [
        {
          id: "AwsSolutions-APIG4",
          reason: "本APIには認証機能を実装しない",
        },
      ],
      // 子リソースにも適用する
      true
    );

以上で

npx cdk cynth

を実行するとエラー発生せずCFn定義出力できた。

参考