From a0e2dc31684c3ab59122cebd93b29a01ee5656bd Mon Sep 17 00:00:00 2001 From: Abhishek Chauhan <60182103+abhu85@users.noreply.github.com> Date: Tue, 24 Feb 2026 10:29:33 +0000 Subject: [PATCH] fix(WS2001): handle intrinsic functions in AccessLogSetting.Format When AccessLogSetting.Format or AccessLogSettings.Format is set to an intrinsic function like !Ref or !Sub, the _check_log_format method would crash with "'dict_node' object has no attribute 'strip'" because it expected a string but received a dict-like node. This fix adds a type check at the start of _check_log_format to skip validation when the format is not a string (i.e., when it's an intrinsic function). This prevents the crash and allows templates with parameterized log formats to pass linting. Fixes awslabs/serverless-rules#253 Co-Authored-By: Claude Opus 4.6 --- .../cfn_lint_serverless/rules/api_gateway.py | 7 ++++ .../ws2001-http-sub-format.pass.yaml | 23 ++++++++++ .../ws2001-rest-ref-format.pass.yaml | 42 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 cfn-lint-serverless/tests/templates/ws2001-http-sub-format.pass.yaml create mode 100644 cfn-lint-serverless/tests/templates/ws2001-rest-ref-format.pass.yaml diff --git a/cfn-lint-serverless/cfn_lint_serverless/rules/api_gateway.py b/cfn-lint-serverless/cfn_lint_serverless/rules/api_gateway.py index b24d2d9..602afd7 100644 --- a/cfn-lint-serverless/cfn_lint_serverless/rules/api_gateway.py +++ b/cfn-lint-serverless/cfn_lint_serverless/rules/api_gateway.py @@ -72,8 +72,15 @@ def _check_log_format(self, log_format: str) -> bool: As users can use non-string keys like '$context.integration.latency', we need to transform the log format into something that can be decoded into JSON first. + + If the log format is not a string (e.g., it's an intrinsic function like + !Ref or !Sub), we skip validation and return True. """ + # Skip validation when Format is an intrinsic function (Ref/Sub/etc.) + if not isinstance(log_format, str): + return True + # Strip any leading/trailing quotes and whitespace that might be in the string log_format = log_format.strip() if (log_format.startswith("'") and log_format.endswith("'")) or ( diff --git a/cfn-lint-serverless/tests/templates/ws2001-http-sub-format.pass.yaml b/cfn-lint-serverless/tests/templates/ws2001-http-sub-format.pass.yaml new file mode 100644 index 0000000..98559d4 --- /dev/null +++ b/cfn-lint-serverless/tests/templates/ws2001-http-sub-format.pass.yaml @@ -0,0 +1,23 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Test WS2001 when Format is !Sub for HTTP APIs + +Resources: + HttpApi: + Type: AWS::ApiGatewayV2::Api + Properties: + Name: test-http-api + ProtocolType: HTTP + + HttpApiStage: + Type: AWS::ApiGatewayV2::Stage + Properties: + ApiId: !Ref HttpApi + StageName: prod + AutoDeploy: true + AccessLogSettings: + DestinationArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/apigateway/http' + Format: !Sub '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","httpMethod":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength"}' + DefaultRouteSettings: + ThrottlingBurstLimit: 1000 + ThrottlingRateLimit: 10 diff --git a/cfn-lint-serverless/tests/templates/ws2001-rest-ref-format.pass.yaml b/cfn-lint-serverless/tests/templates/ws2001-rest-ref-format.pass.yaml new file mode 100644 index 0000000..014f6ec --- /dev/null +++ b/cfn-lint-serverless/tests/templates/ws2001-rest-ref-format.pass.yaml @@ -0,0 +1,42 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Test WS2001 when Format is !Ref + +Parameters: + LogFormat: + Type: String + Default: '{"requestId":"$context.requestId"}' + +Resources: + Api: + Type: AWS::Serverless::Api + Properties: + StageName: prod + AccessLogSetting: + DestinationArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/apigateway' + Format: !Ref LogFormat + DefinitionBody: + openapi: "3.0.1" + info: + title: "test-api" + version: 1.0.0 + paths: + /: + get: + responses: + "200": + description: "OK" + x-amazon-apigateway-integration: + requestTemplates: + application/json: '{"statusCode": 200}' + passthroughBehavior: when_no_match + responses: + default: + statusCode: "200" + type: mock + MethodSettings: + - HttpMethod: "*" + ResourcePath: "/*" + ThrottlingRateLimit: 10 + ThrottlingBurstLimit: 1000 + TracingEnabled: true