aws-cdk-lib/aws-s3: Removing object lock from s3 bucket leads to InternalFailure due to getObjectLockEnabled() is null
Describe the bug
I have cdk code to create a S3 bucket with ObjectLock optionally turned on, depending on a property in the respective stage. When I deploy the stack with ObjectLock turned off and then change it to on this change can be deployed flawlessly (and ObjectLock then indeed is turned on on the bucket).
However, when I then try to disable ObjectLock again I get: `7:23:45 AM | UPDATE_FAILED | AWS::S3::Bucket | foobucketB1767E36 Resource handler returned message: "Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled()" is null" (RequestToken: 2b263817-blah-bla-foo-bar, HandlerErrorCode: InternalFailure)
❌ mystack failed: _ToolkitError: The stack named mystack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled()" is null" (RequestToken: 2b263817-blah-bla-foo-bar, HandlerErrorCode: InternalFailure)`
CDK code looks like: ` this.bucketprops = { removalPolicy: props.removalPolicy, versioned: true, lifecycleRules: [ { //expiration: cdk.Duration.days(1), transitions: [ { storageClass: s3.StorageClass.GLACIER_INSTANT_RETRIEVAL, transitionAfter: cdk.Duration.days(1), } ] } ], };
if (props.objectLockDefaultRetention !== undefined) {
this.bucketprops = Object.assign(this.bucketprops, {objectLockDefaultRetention: s3.ObjectLockRetention.governance(cdk.Duration.days(props.objectLockDefaultRetention))});
}
this.inbucket = new s3.Bucket(this, 'foobucket', this.bucketprops);`
Regression Issue
- [ ] Select this option if this issue appears to be a regression.
Last Known Working CDK Library Version
No response
Expected Behavior
When stack deployed and props.objectLockDefaultRetention is some integer, change that prop to undefined and redeploy leads to object lock turned off on bucket.
Current Behavior
When stack deployed and props.objectLockDefaultRetention is some integer, change that prop to undefined and redeploy leads to `7:23:45 AM | UPDATE_FAILED | AWS::S3::Bucket | foobucketB1767E36 Resource handler returned message: "Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled()" is null" (RequestToken: 2b263817-blah-bla-foo-bar, HandlerErrorCode: InternalFailure)
❌ mystack failed: _ToolkitError: The stack named mystack failed to deploy: UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled()" is null" (RequestToken: 2b263817-blah-bla-foo-bar, HandlerErrorCode: InternalFailure)`
Reproduction Steps
When stack deployed and props.objectLockDefaultRetention is some integer, change that prop to undefined and redeploy.
Possible Solution
"software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled() is null" and "InternalFailure" sounds like a bug to me - should be fixed. If there is some error/mistake/misunderstanding on my side, please bear with me - this are my first steps in cdk... ;-)
Additional Information/Context
No response
AWS CDK Library version (aws-cdk-lib)
AWS CDK CLI version
2.1017.1 (build 60506e5)
Node.js Version
v22.15.0
OS
github devcontainer Ubuntu 20.04.6 LTS
Language
TypeScript
Language Version
TypeScript (5.8.3)
Other information
No response
Hi @newton-wi,
Thank you for reporting this issue! You've encountered a combination of an AWS S3 service limitation and poor error handling in the CDK/CloudFormation stack.
Root Cause Analysis:
The error occurs because you're attempting to remove Object Lock from a bucket that already has it enabled. Here's what's happening:
- You initially deployed with
props.objectLockDefaultRetentionset to an integer value, which enabled Object Lock - You then changed
props.objectLockDefaultRetentiontoundefined, expecting to disable Object Lock - The CDK's
parseObjectLockConfigmethod returnsundefinedwhen no retention is specified - This causes CloudFormation to attempt transitioning from Object Lock enabled to disabled
- The AWS S3 service rejects this change, but the CloudFormation resource handler fails with a Java NullPointerException instead of a clear error message
AWS S3 Object Lock Limitation:
According to the AWS documentation:
Important: After you enable Object Lock on a bucket, you can't disable Object Lock or suspend versioning for that bucket.
The Bug:
While the AWS limitation is expected, the CDK should provide a clear validation error instead of allowing CloudFormation to fail with this cryptic message:
"Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "software.amazon.s3.bucket.ResourceModel.getObjectLockEnabled()" is null"
Workarounds:
Keep Object Lock enabled but remove default retention rules:
this.bucketprops = {
// ... other props
objectLockEnabled: true, // Keep this enabled
// Don't set objectLockDefaultRetention - this removes default retention but keeps Object Lock enabled
};
Proposed Solution: Since the CDK cannot track previous deployment state, the best approach would be to:
- Improve validation: Require users to explicitly set
objectLockEnabled: falsewhen attempting to disable Object Lock, then throw a clear error message - Better documentation: Add warnings to the
objectLockDefaultRetentionproperty documentation about the permanent nature of Object Lock - CloudFormation fix: The underlying issue is that the AWS CloudFormation S3 resource handler should provide a meaningful error instead of a Java NullPointerException
The CDK fix would involve modifying the parseObjectLockConfig method to detect explicit attempts to disable Object Lock and provide clear guidance, rather than trying to track state transitions.
I am making it a p2 and we welcome PRs to improve the user experience!
Huh? Using the AWS Console I can disable object lock on a bucket where it was enabled. So, it definitively is possible in principle. (As seen in the code we are using object lock in gouvernance mode - might be a different case in compliance mode. There you should not be able to remove object lock from objects in the bucket - if it is possible to remove the default object lock on the bucket I do not know).
But thanks for the hint with specifying the objectLockEnabled property.
Changing my conditional variable definition to
if (props.objectLockDefaultRetention !== undefined) {
this.bucketprops = Object.assign(this.bucketprops, {
objectLockDefaultRetention: s3.ObjectLockRetention.governance(cdk.Duration.days(props.objectLockDefaultRetention)),
objectLockEnabled: true,
});
} else {
this.bucketprops = Object.assign(this.bucketprops, {
objectLockEnabled: false,
});
}
I do not longer get an error BUT the default object lock is not removed from the bucket. Probably due to see https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html?icmpid=docs_amazons3_console
To override or remove governance-mode retention settings, you must have the s3:BypassGovernanceRetention permission and must explicitly include x-amz-bypass-governance-retention:true as a request header with any request that requires overriding governance mode.
Note By default, the Amazon S3 console includes the x-amz-bypass-governance-retention:true header. If you try to delete objects protected by governance mode and have the s3:BypassGovernanceRetention permission, the operation will succeed.
I have s3:BypassGovernanceRetention, so doing it via console works, I guess cdk does not set the x-amz-bypass-governance-retention:true header.