[APIGateway.1] API ゲートウェイRESTと WebSocket API実行のログ記録を有効にする必要があります を CloudFormation Guard でチェックする

[APIGateway.1] API ゲートウェイRESTと WebSocket API実行のログ記録を有効にする必要があります を CloudFormation Guard でチェックする

Clock Icon2025.02.17

こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。

みなさん CloudFormation テンプレートをデプロイする前に、 Security Hub の検知項目に引っかかっていないか、チェックしたいなぁと思ったことはありますでしょうか。

そういった時に、役に立つのが CloudFormation Guard や cfn-nag、Trivy、Snyk IaC などです。

AWS Foundational Security Best Practices v1.0.0 (FSBP) 標準に準拠したいケースもあるのではないでしょうか。(私です。)

そこで、今回は [APIGateway.1] API ゲートウェイRESTと WebSocket API実行のログ記録を有効にする必要があります を CloudFormation Guard で表現してみます。

https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/apigateway-controls.html#apigateway-1

https://dev.classmethod.jp/articles/securityhub-fsbp-remediation-apigateway-1/

前置き

「すでにこういった取り組みがあるのでは?」と思われた方もいるのではないかと思います。実際、ありました。aws-guard-rules-registry というものが、

ただし、今回私が求める AWS Foundational Security Best Practices v1.0.0 (FSBP) 標準に最適化されたものは少し違ったり、

https://github.com/aws-cloudformation/aws-guard-rules-registry/blob/main/rules/aws/api_gateway/api_gw_execution_logging_enabled.guard

API Gateway v2 の方は反映されてなかったりするため、コードを参考にさせていただきつつ、自前で実装してみたいと思います。

https://github.com/aws-cloudformation/aws-guard-rules-registry/blob/main/rules/aws/api_gateway_v2/api_gw_execution_logging_enabled.guard

作成したコード

作成したコードは以下になります。

api-gw-execution-logging-enabled.guard
#################################################
#                 API Gateway.01                 
##################################################
# [APIGateway.1] API Gateway REST and WebSocket API execution logging should be enabled
# Control URL: https://docs.aws.amazon.com/securityhub/latest/userguide/apigateway-controls.html#apigateway-1
#
# Category: Identify > Logging
# Severity: Medium
# Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
# AWS Config rule: api-gw-execution-logging-enabled
# Config Rule URL:https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/api-gw-execution-logging-enabled.html

let valid_logging_levels = ['ERROR', 'INFO']

let api_gw_execution_logging_enabled = Resources.*[ Type == 'AWS::ApiGateway::Stage'
  Metadata.guard.SuppressedRules not exists or
  Metadata.guard.SuppressedRules.* != "API_GW_EXECUTION_LOGGING_ENABLED"
]

let api_gw_v2_execution_logging_enabled = Resources.*[ Type == 'AWS::ApiGatewayV2::Stage'
  Metadata.guard.SuppressedRules not exists or
  Metadata.guard.SuppressedRules.* != "API_GW_EXECUTION_LOGGING_ENABLED"
]

# AWS::ApiGateway::Stage
rule api_gw_execution_logging_check when %api_gw_execution_logging_enabled !empty {
  %api_gw_execution_logging_enabled.Properties {
    # MethodSettings が存在するかチェック
    MethodSettings exists
    <<
      RuleID: APIGateway.1
      Category: Identify > Logging
      Severity: Medium
      Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
      Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
    >>

    # MethodSettings が存在した場合、全ての LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when MethodSettings exists {
      MethodSettings[*] {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
        >>
      }
    }
  }
}

# AWS::ApiGatewayV2::Stage
rule api_gw_v2_execution_logging_check when %api_gw_v2_execution_logging_enabled !empty {
  %api_gw_v2_execution_logging_enabled.Properties {
    # DefaultRouteSettings が存在するかチェック
    DefaultRouteSettings exists
    <<
      RuleID: APIGateway.1
      Category: Identify > Logging
      Severity: Medium
      Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
      Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
    >>

    # DefaultRouteSettings が存在した場合、LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when DefaultRouteSettings exists {
      DefaultRouteSettings {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
        >>
      }
    }

    # RouteSettings が存在した場合、全ての LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when RouteSettings exists {
      RouteSettings.* {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if:
          - DefaultRouteSettings is missing or its LoggingLevel isn't ERROR or INFO
          - Any RouteSettings entry is missing LoggingLevel or its LoggingLevel isn't ERROR or INFO
        >>
      }
    }
  }
}

Metadata

まず、この手のチェックツールは Suppress できないと、オオカミ少年になりがちです。そのため今回は本家を参考に CloudFormation テンプレートの Metadata で、次のように SuppressedRules に API_GW_EXECUTION_LOGGING_ENABLED が含まれない場合はチェックしないよう設定しました。

template.yml
Resources:
  ApiGatewayStage:
+    Metadata:
+      guard:
+        SuppressedRules:
+          - API_GW_EXECUTION_LOGGING_ENABLED
    Type: "AWS::ApiGateway::Stage"
    Properties:
        StageName: "hoge"
        DeploymentId: "ad6uyy"
        RestApiId: "yfrjlllnh3"
        CacheClusterEnabled: false
        CacheClusterSize: "0.5"
        MethodSettings: 

チェックロジック部分

# SuppressedRules が存在しない or API_GW_EXECUTION_LOGGING_ENABLED が含まれない場合、
let api_gw_execution_logging_enabled = Resources.*[ Type == 'AWS::ApiGateway::Stage'
  Metadata.guard.SuppressedRules not exists or
  Metadata.guard.SuppressedRules.* != "API_GW_EXECUTION_LOGGING_ENABLED"
]

let api_gw_v2_execution_logging_enabled = Resources.*[ Type == 'AWS::ApiGatewayV2::Stage'
  Metadata.guard.SuppressedRules not exists or
  Metadata.guard.SuppressedRules.* != "API_GW_EXECUTION_LOGGING_ENABLED"
]

MethodSettings

AWS::ApiGateway::Stage では MethodSettings で LoggingLevel を指定します。

  CacheDataEncrypted: Boolean
  CacheTtlInSeconds: Integer
  CachingEnabled: Boolean
  DataTraceEnabled: Boolean
  HttpMethod: String
  LoggingLevel: String
  MetricsEnabled: Boolean
  ResourcePath: String
  ThrottlingBurstLimit: Integer
  ThrottlingRateLimit: Number

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-stage-methodsetting.html

逆を言えば MethodSettings を設定しなければ、LoggingLevel を設定できないため、MethodSettings が存在しているかどうかを判定ロジックに利用しました。

また、LoggingLevel in valid_logging_levels とすることで、LoggingLevel が 'ERROR' でも 'INFO' でもない場合にエラーの流量を減らすようにしてみました。今回、Custom Error Message を利用してみましたが、調べてみた限りではひとつにまとめられなかったため、冗長的な書き方をしています。

# AWS::ApiGateway::Stage
rule api_gw_execution_logging_check when %api_gw_execution_logging_enabled !empty {
  %api_gw_execution_logging_enabled.Properties {
    # MethodSettings が存在するかチェック
    MethodSettings exists
    <<
      RuleID: APIGateway.1
      Category: Identify > Logging
      Severity: Medium
      Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
      Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
    >>

    # MethodSettings が存在した場合、全ての LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when MethodSettings exists {
      MethodSettings[*] {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
        >>
      }
    }
  }
}

DefaultRouteSettings

AWS::ApiGatewayV2::Stage では、 DefaultRouteSettings と RouteSettings でログレベルの設定を行います。

  DataTraceEnabled: Boolean
  DetailedMetricsEnabled: Boolean
  LoggingLevel: String
  ThrottlingBurstLimit: Integer
  ThrottlingRateLimit: Number

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-stage-routesettings.html

RouteSettings では次のように、特定ルート(hoge)でのログレベルの上書きを行えます。

Resources:
  Stage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      StageName: stg
      StageVariables: {}
      ApiId: XXXXXXXXX
      RouteSettings:
        $hoge:
          DataTraceEnabled: false
          DetailedMetricsEnabled: false
          LoggingLevel: ERROR
      DefaultRouteSettings:
        DataTraceEnabled: false
        DetailedMetricsEnabled: false
        LoggingLevel: INFO

そのため、どちらのパターンも拾えるよう、次のように記載しました。

# AWS::ApiGatewayV2::Stage
rule api_gw_v2_execution_logging_check when %api_gw_v2_execution_logging_enabled !empty {
  %api_gw_v2_execution_logging_enabled.Properties {
    # DefaultRouteSettings が存在するかチェック
    DefaultRouteSettings exists
    <<
      RuleID: APIGateway.1
      Category: Identify > Logging
      Severity: Medium
      Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
      Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
    >>

    # DefaultRouteSettings が存在した場合、LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when DefaultRouteSettings exists {
      DefaultRouteSettings {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGateway::Stage, AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if the loggingLevel isn't ERROR or INFO for all stages of the API. Unless you provide custom parameter values to indicate that a specific log type should be enabled, Security Hub produces a passed finding if the logging level is either ERROR or INFO.
        >>
      }
    }

    # RouteSettings が存在した場合、全ての LoggingLevel が %valid_logging_levels 内のどれかに当てはまるかチェック
    when RouteSettings exists {
      RouteSettings.* {
        LoggingLevel in %valid_logging_levels
        <<
          RuleID: APIGateway.1
          Category: Identify > Logging
          Severity: Medium
          Resource type: AWS::ApiGatewayV2::Stage
          Description: This control checks whether all stages of an Amazon API Gateway REST or WebSocket API have logging enabled. The control fails if:
          - DefaultRouteSettings is missing or its LoggingLevel isn't ERROR or INFO
          - Any RouteSettings entry is missing LoggingLevel or its LoggingLevel isn't ERROR or INFO
        >>
      }
    }
  }
}

テストしてみる

次のテストコードを用意しました。

api-gw-execution-logging-enabled_test.yaml
# api-gw-execution-logging-enabled.yaml
---
# テストケース1: 空のテンプレート
# 期待値: SKIP - リソースが存在しないため、ルールはスキップされる
- name: Empty
  input: {}
  expectations:
    rules:
      api_gw_execution_logging_check: SKIP
      api_gw_v2_execution_logging_check: SKIP

# テストケース2: 空のリソースセクション
# 期待値: SKIP - 評価対象のリソースが存在しないため、ルールはスキップされる
- name: No resources
  input:
    Resources: {}
  expectations:
    rules:
      api_gw_execution_logging_check: SKIP
      api_gw_v2_execution_logging_check: SKIP

# テストケース3: MethodSettings未設定のAPI Gateway Stage
# 期待値: FAIL - MethodSettingsが必須だが設定されていないため、チェックは失敗する
- name: No Method set in API GW Stage
  input:
    Resources:
      MyApiStage:
        Type: AWS::ApiGateway::Stage
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          Variables:
            Stack: Prod
  expectations:
    rules:
      api_gw_execution_logging_check: FAIL

# テストケース4: LoggingLevelがINFOに設定されたケース
# 期待値: PASS - INFOは有効なログレベルの一つ
- name: LoggingLevel set to INFO
  input:
    Resources:
      myApiGWStage:
        Type: AWS::ApiGateway::Stage
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          MethodSettings:
            - ResourcePath: /
              HttpMethod: GET
              MetricsEnabled: true
              LoggingLevel: "INFO"
  expectations:
    rules:
      api_gw_execution_logging_check: PASS

# テストケース5: LoggingLevelがERRORに設定されたケース
# 期待値: PASS - ERRORは有効なログレベルの一つ
- name: LoggingLevel set to ERROR
  input:
    Resources:
      myApiGWStage:
        Type: AWS::ApiGateway::Stage
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          MethodSettings:
            - ResourcePath: /
              HttpMethod: GET
              MetricsEnabled: true
              LoggingLevel: "ERROR"
  expectations:
    rules:
      api_gw_execution_logging_check: PASS

# テストケース6: 無効なLoggingLevel値
# 期待値: FAIL - "SUPER"は有効なログレベルではない
- name: LoggingLevel set to invalid value
  input:
    Resources:
      myApiGWStage:
        Type: AWS::ApiGateway::Stage
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          MethodSettings:
            - ResourcePath: /
              HttpMethod: GET
              MetricsEnabled: true
              LoggingLevel: "SUPER"
  expectations:
    rules:
      api_gw_execution_logging_check: FAIL

# テストケース7: LoggingLevel設定なし
# 期待値: FAIL - LoggingLevelは必須パラメータ
- name: LoggingLevel missing
  input:
    Resources:
      myApiGWStage:
        Type: AWS::ApiGateway::Stage
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          MethodSettings:
            - ResourcePath: /
              HttpMethod: GET
              MetricsEnabled: true
  expectations:
    rules:
      api_gw_execution_logging_check: FAIL

# テストケース8: ルール抑制が設定されたケース
# 期待値: SKIP - Metadataでルールが抑制されているため
- name: Rule suppressed
  input:
    Resources:
      myApiGWStage:
        Type: AWS::ApiGateway::Stage
        Metadata:
          guard:
            SuppressedRules:
              - "API_GW_EXECUTION_LOGGING_ENABLED"
        Properties:
          StageName: Prod
          Description: Prod Stage
          RestApiId: !Ref MyRestApi
          DeploymentId: !Ref TestDeployment
          MethodSettings:
            - ResourcePath: /
              HttpMethod: GET
              MetricsEnabled: true
  expectations:
    rules:
      api_gw_execution_logging_check: SKIP

# テストケース9: WebSocket APIでINFOログレベル
# 期待値: PASS - INFOは有効なログレベル
- name: WebSocket API with INFO logging
  input:
    Resources:
      MyWebSocketStage:
        Type: AWS::ApiGatewayV2::Stage
        Properties:
          DefaultRouteSettings:
            LoggingLevel: "INFO"
  expectations:
    rules:
      api_gw_v2_execution_logging_check: PASS

# テストケース10: WebSocket APIでERRORログレベル
# 期待値: PASS - ERRORは有効なログレベル
- name: WebSocket API with ERROR logging
  input:
    Resources:
      MyWebSocketStage:
        Type: AWS::ApiGatewayV2::Stage
        Properties:
          DefaultRouteSettings:
            LoggingLevel: "ERROR"
  expectations:
    rules:
      api_gw_v2_execution_logging_check: PASS

# テストケース11: WebSocket APIで無効なログレベル
# 期待値: FAIL - DEBUGは許可されていないログレベル
- name: WebSocket API with invalid logging level
  input:
    Resources:
      MyWebSocketStage:
        Type: AWS::ApiGatewayV2::Stage
        Properties:
          DefaultRouteSettings:
            LoggingLevel: "DEBUG"
  expectations:
    rules:
      api_gw_v2_execution_logging_check: FAIL

うまくテストケースが通っていますね。

takakuni@ api_gateway % cfn-guard test -r api-gw-execution-logging-enabled.guard -t api-gw-execution-logging-enabled_test.yaml
Test Case #1
Name: Empty
  PASS Rules:
    api_gw_execution_logging_check: Expected = SKIP
    api_gw_v2_execution_logging_check: Expected = SKIP

Test Case #2
Name: No resources
  PASS Rules:
    api_gw_v2_execution_logging_check: Expected = SKIP
    api_gw_execution_logging_check: Expected = SKIP

Test Case #3
Name: No Method set in API GW Stage
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = FAIL

Test Case #4
Name: LoggingLevel set to INFO
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = PASS

Test Case #5
Name: LoggingLevel set to ERROR
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = PASS

Test Case #6
Name: LoggingLevel set to invalid value
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = FAIL

Test Case #7
Name: LoggingLevel missing
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = FAIL

Test Case #8
Name: Rule suppressed
  No Test expectation was set for Rule api_gw_v2_execution_logging_check
  PASS Rules:
    api_gw_execution_logging_check: Expected = SKIP

Test Case #9
Name: WebSocket API with INFO logging
  No Test expectation was set for Rule api_gw_execution_logging_check
  PASS Rules:
    api_gw_v2_execution_logging_check: Expected = PASS

Test Case #10
Name: WebSocket API with ERROR logging
  No Test expectation was set for Rule api_gw_execution_logging_check
  PASS Rules:
    api_gw_v2_execution_logging_check: Expected = PASS

Test Case #11
Name: WebSocket API with invalid logging level
  No Test expectation was set for Rule api_gw_execution_logging_check
  PASS Rules:
    api_gw_v2_execution_logging_check: Expected = FAIL

まとめ

以上、「[APIGateway.1] API ゲートウェイRESTと WebSocket API実行のログ記録を有効にする必要があります を CloudFormation Guard でチェックする」でした。

ブログネタに困ったら、このシリーズを擦ろうと思いました。つもりに積もって、完成したら嬉しいですね。

クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.