Describe the bug
LogGroupLogDestination sets the REST API Stage property AccessLogSetting.DestinationArn to the log group logGroupArn, which carries a trailing :*. API Gateway stores this ARN without the :*. As a result, CloudFormation drift detection reports the Stage as MODIFIED on every drift run, even though nothing was changed and access logging works correctly.
CloudFormation Drift detection is only useful if IN_SYNC is the normal, trusted state. A resource that is permanently MODIFIED trains operators to ignore drift, which then hides real drift. Many teams also gate deploys, audits, or compliance checks on a clean drift status, so a permanent false positive weakens those gates. "Just ignore it" or "add it to an allow list" does not scale across stacks and environments, and it suppresses real drift on the same property. The goal is a clean IN_SYNC state without manual suppression.
Regression Issue
Last Known Working CDK Library Version
No response
Expected Behavior
LogGroupLogDestination sets AccessLogSetting.DestinationArn to the log group ARN without the trailing :*, so the synthesized template matches what API Gateway stores and drift detection reports the Stage as IN_SYNC.
Current Behavior
The synthesized DestinationArn points at the log group Arn attribute, which includes the :*:
{
"AccessLogSetting": {
"DestinationArn": {
"Fn::GetAtt": ["AccessLogs8B620ECA", "Arn"]
}
}
}
At deploy time this resolves to an ARN with a trailing :*:
arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs:*
API Gateway stores the ARN without the :*. Reading it back from the service:
aws apigateway get-stage \
--rest-api-id <rest-api-id> \
--stage-name prod \
--query 'accessLogSettings.destinationArn' \
--output text
arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs
CloudFormation drift detection then reports the Stage as MODIFIED with a single property difference:
aws cloudformation describe-stack-resource-drifts \
--stack-name <stack-name> \
--stack-resource-drift-status-filters MODIFIED
{
"LogicalResourceId": "ApiDeploymentStageprod...",
"ResourceType": "AWS::ApiGateway::Stage",
"PropertyDifferences": [
{
"PropertyPath": "/AccessLogSetting/DestinationArn",
"ExpectedValue": "arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs:*",
"ActualValue": "arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs",
"DifferenceType": "NOT_EQUAL"
}
],
"StackResourceDriftStatus": "MODIFIED"
}
The only difference is the trailing :*. This repeats on every drift run and affects every stack that enables access logging this way.
Reproduction Steps
Below you can find a sample CDK code in TypeScript. After synth, inspect the Stage in the generated template.
import { App, Stack } from 'aws-cdk-lib';
import { RestApi, LogGroupLogDestination, AccessLogFormat, MethodLoggingLevel } from 'aws-cdk-lib/aws-apigateway';
import { LogGroup } from 'aws-cdk-lib/aws-logs';
const app = new App();
const stack = new Stack(app, 'DefaultStack');
const logGroup = new LogGroup(stack, 'AccessLogs', {
logGroupName: '/aws/apigateway/demo/accessLogs',
});
new RestApi(stack, 'Api', {
deployOptions: {
accessLogDestination: new LogGroupLogDestination(logGroup),
accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
loggingLevel: MethodLoggingLevel.ERROR,
},
}).root.addMethod('GET');
app.synth();
In the synthesized DefaultStack template, the AWS::ApiGateway::Stage resource has AccessLogSetting.DestinationArn set to { "Fn::GetAtt": ["AccessLogs...", "Arn"] }, which resolves with the trailing :*. Deploy the stack and run drift detection to see the Stage reported as MODIFIED.
Possible Solution
The :* on LogGroup.logGroupArn is intended and should not be changed globally, because IAM policies rely on it. The relevant source confirms this.
LogGroupLogDestination.bind() (packages/aws-cdk-lib/aws-apigateway/lib/access-log.ts) returns the ARN unchanged:
public bind(_stage: IStageRef): AccessLogDestinationConfig {
return {
destinationArn: this.logGroup.logGroupRef.logGroupArn,
};
}
logGroupArn is documented to include the :* (packages/aws-cdk-lib/aws-logs/lib/log-group.ts):
/**
* The ARN of this log group, with ':*' appended
*
* @attribute
*/
readonly logGroupArn: string;
The same file documents the IAM reason for the :* in grant():
// A LogGroup ARN out of CloudFormation already includes a ':*' at the end to
// include the log streams under the group.
So the fix belongs in LogGroupLogDestination, which should use the log group ARN without the :* for this destination. CDK already knows how to build the :*-free form: in log-group.ts, fromLogGroupName builds the ARN with formatArn(... ArnFormat.COLON_RESOURCE_NAME) from the name, and fromLogGroupArn starts by stripping the suffix with logGroupArn.replace(/:\*$/, ''). The destination could reuse the same approach.
An alternative or additional fix is on the CloudFormation side: give the AWS::ApiGateway::Stage resource type a propertyTransform for AccessLogSetting.DestinationArn so the server side normalization is not reported as drift. See Preventing false drift detection results for resource types. That page documents an equivalent case already solved this way: AWS::Route53::HostedZone uses a propertyTransform for a trailing . on the Name property, which is structurally the same problem as the trailing :* here. Note that this would be a change to the AWS owned resource type schema, so it can only be done by AWS, not by CDK or the user. Its advantage is that it would fix existing stacks without any template change or redeploy.
Before, I would create a PR, I'd like to check the following question with you:
-
(1) Preferred fix location: normalize the ARN inside LogGroupLogDestination, or add a propertyTransform for AWS::ApiGateway::Stage, or both. The propertyTransform route fixes drift for existing stacks without any template change, which is the cleanest path to IN_SYNC.
-
(2) If the fix is in LogGroupLogDestination, the synthesized template changes from Fn::GetAtt ... Arn to a name based ARN, so existing stacks will see a diff on the next cdk diff. The diff is an in place property update, not a resource replacement, and the value stored in API Gateway does not change. Should this go behind a feature flag so existing users do not get an unexpected diff on a plain version upgrade? This question is independent of how small the code change is: the need for a feature flag depends only on whether the synthesized template changes for existing stacks, not on the size of the fix.
I am happy to open a PR once there is agreement on the fix location and on whether a feature flag is wanted.
Additional Information/Context
Workaround using a custom IAccessLogDestination that rebuilds the ARN from the log group name. The :* is only added when the Arn attribute is resolved at deploy time, so trimming the string at synth time does not work; rebuilding from the name avoids the :* entirely.
import { Stack, ArnFormat } from 'aws-cdk-lib';
import { IAccessLogDestination, AccessLogDestinationConfig } from 'aws-cdk-lib/aws-apigateway';
import { LogGroup } from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
class LogGroupLogDestinationWithoutWildcard implements IAccessLogDestination {
private readonly destinationArn: string;
constructor(scope: Construct, logGroup: LogGroup) {
this.destinationArn = Stack.of(scope).formatArn({
service: 'logs',
resource: 'log-group',
resourceName: logGroup.logGroupName,
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
});
}
bind(): AccessLogDestinationConfig {
return { destinationArn: this.destinationArn };
}
}
With the workaround the synthesized DestinationArn no longer contains the :*, and drift detection reports the Stage as IN_SYNC:
{
"AccessLogSetting": {
"DestinationArn": {
"Fn::Join": [
"",
[
"arn:", { "Ref": "AWS::Partition" },
":logs:", { "Ref": "AWS::Region" },
":", { "Ref": "AWS::AccountId" },
":log-group:", { "Ref": "AccessLogs8B620ECA" }
]
]
}
}
}
Related: aws-cdk #18253 "(logs): Log Group ARN has extra :*" describes the same underlying behavior with a WAF example, and suggests the same formatArn(... COLON_RESOURCE_NAME) approach. It notes that it may be better to normalize the ARN in the L2 constructs that consume the log group ARN rather than changing logGroupArn globally. This issue applies that idea to LogGroupLogDestination and the API Gateway Stage drift specifically. The same fix pattern could later be reused for other consumers of the log group ARN, such as the WAF logging configuration in #18253.
AWS CDK Library version (aws-cdk-lib)
2.268.0
AWS CDK CLI version
2.1140.0
Node.js Version
v24.20.0
OS
Linux
Language
TypeScript
Language Version
TypeScript (7.0.2)
Other information
No response
Describe the bug
LogGroupLogDestinationsets the REST API Stage propertyAccessLogSetting.DestinationArnto the log grouplogGroupArn, which carries a trailing:*. API Gateway stores this ARN without the:*. As a result, CloudFormation drift detection reports the Stage asMODIFIEDon every drift run, even though nothing was changed and access logging works correctly.CloudFormation Drift detection is only useful if
IN_SYNCis the normal, trusted state. A resource that is permanentlyMODIFIEDtrains operators to ignore drift, which then hides real drift. Many teams also gate deploys, audits, or compliance checks on a clean drift status, so a permanent false positive weakens those gates. "Just ignore it" or "add it to an allow list" does not scale across stacks and environments, and it suppresses real drift on the same property. The goal is a cleanIN_SYNCstate without manual suppression.Regression Issue
Last Known Working CDK Library Version
No response
Expected Behavior
LogGroupLogDestinationsetsAccessLogSetting.DestinationArnto the log group ARN without the trailing:*, so the synthesized template matches what API Gateway stores and drift detection reports the Stage asIN_SYNC.Current Behavior
The synthesized
DestinationArnpoints at the log groupArnattribute, which includes the:*:{ "AccessLogSetting": { "DestinationArn": { "Fn::GetAtt": ["AccessLogs8B620ECA", "Arn"] } } }At deploy time this resolves to an ARN with a trailing
:*:API Gateway stores the ARN without the
:*. Reading it back from the service:CloudFormation drift detection then reports the Stage as
MODIFIEDwith a single property difference:{ "LogicalResourceId": "ApiDeploymentStageprod...", "ResourceType": "AWS::ApiGateway::Stage", "PropertyDifferences": [ { "PropertyPath": "/AccessLogSetting/DestinationArn", "ExpectedValue": "arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs:*", "ActualValue": "arn:aws:logs:eu-central-1:111122223333:log-group:/aws/apigateway/demo/accessLogs", "DifferenceType": "NOT_EQUAL" } ], "StackResourceDriftStatus": "MODIFIED" }The only difference is the trailing
:*. This repeats on every drift run and affects every stack that enables access logging this way.Reproduction Steps
Below you can find a sample CDK code in TypeScript. After synth, inspect the Stage in the generated template.
In the synthesized
DefaultStacktemplate, theAWS::ApiGateway::Stageresource hasAccessLogSetting.DestinationArnset to{ "Fn::GetAtt": ["AccessLogs...", "Arn"] }, which resolves with the trailing:*. Deploy the stack and run drift detection to see the Stage reported asMODIFIED.Possible Solution
The
:*onLogGroup.logGroupArnis intended and should not be changed globally, because IAM policies rely on it. The relevant source confirms this.LogGroupLogDestination.bind()(packages/aws-cdk-lib/aws-apigateway/lib/access-log.ts) returns the ARN unchanged:logGroupArnis documented to include the:*(packages/aws-cdk-lib/aws-logs/lib/log-group.ts):The same file documents the IAM reason for the
:*ingrant():So the fix belongs in
LogGroupLogDestination, which should use the log group ARN without the:*for this destination. CDK already knows how to build the:*-free form: inlog-group.ts,fromLogGroupNamebuilds the ARN withformatArn(... ArnFormat.COLON_RESOURCE_NAME)from the name, andfromLogGroupArnstarts by stripping the suffix withlogGroupArn.replace(/:\*$/, ''). The destination could reuse the same approach.An alternative or additional fix is on the CloudFormation side: give the
AWS::ApiGateway::Stageresource type apropertyTransformforAccessLogSetting.DestinationArnso the server side normalization is not reported as drift. See Preventing false drift detection results for resource types. That page documents an equivalent case already solved this way:AWS::Route53::HostedZoneuses apropertyTransformfor a trailing.on theNameproperty, which is structurally the same problem as the trailing:*here. Note that this would be a change to the AWS owned resource type schema, so it can only be done by AWS, not by CDK or the user. Its advantage is that it would fix existing stacks without any template change or redeploy.Before, I would create a PR, I'd like to check the following question with you:
(1) Preferred fix location: normalize the ARN inside
LogGroupLogDestination, or add apropertyTransformforAWS::ApiGateway::Stage, or both. ThepropertyTransformroute fixes drift for existing stacks without any template change, which is the cleanest path toIN_SYNC.(2) If the fix is in
LogGroupLogDestination, the synthesized template changes fromFn::GetAtt ... Arnto a name based ARN, so existing stacks will see a diff on the nextcdk diff. The diff is an in place property update, not a resource replacement, and the value stored in API Gateway does not change. Should this go behind a feature flag so existing users do not get an unexpected diff on a plain version upgrade? This question is independent of how small the code change is: the need for a feature flag depends only on whether the synthesized template changes for existing stacks, not on the size of the fix.I am happy to open a PR once there is agreement on the fix location and on whether a feature flag is wanted.
Additional Information/Context
Workaround using a custom
IAccessLogDestinationthat rebuilds the ARN from the log group name. The:*is only added when theArnattribute is resolved at deploy time, so trimming the string at synth time does not work; rebuilding from the name avoids the:*entirely.With the workaround the synthesized
DestinationArnno longer contains the:*, and drift detection reports the Stage asIN_SYNC:{ "AccessLogSetting": { "DestinationArn": { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":logs:", { "Ref": "AWS::Region" }, ":", { "Ref": "AWS::AccountId" }, ":log-group:", { "Ref": "AccessLogs8B620ECA" } ] ] } } }Related: aws-cdk #18253 "(logs): Log Group ARN has extra
:*" describes the same underlying behavior with a WAF example, and suggests the sameformatArn(... COLON_RESOURCE_NAME)approach. It notes that it may be better to normalize the ARN in the L2 constructs that consume the log group ARN rather than changinglogGroupArnglobally. This issue applies that idea toLogGroupLogDestinationand the API Gateway Stage drift specifically. The same fix pattern could later be reused for other consumers of the log group ARN, such as the WAF logging configuration in #18253.AWS CDK Library version (aws-cdk-lib)
2.268.0
AWS CDK CLI version
2.1140.0
Node.js Version
v24.20.0
OS
Linux
Language
TypeScript
Language Version
TypeScript (7.0.2)
Other information
No response