-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(s3): Bucket.grantWrite() no longer adds s3:PutObject* permission #12391
Conversation
c5b4b09
to
e01ea04
Compare
* Use a feature flag to make sure existing customers who might be relying | ||
* on the overly-broad permissions are not broken. | ||
*/ | ||
export const S3_BUCKET_GRANT_WRITE_ACL = '@aws-cdk/aws-s3:BucketGrantWriteAcl'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the name this looks like it 'grants write acl', but in reality it seems to 'SKIP grants write acl'.
Can we name it something more clear like LimitGrantWrite
? (I'm willing to forego the word "Bucket" since we already scope this to S3).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Changed to S3_GRANT_WRITE_WITHOUT_ACL
/ '@aws-cdk/aws-s3:grantWriteWithoutAcl'
, let me know if this is better!
@@ -117,6 +127,7 @@ const FUTURE_FLAGS_DEFAULTS: { [key: string]: boolean } = { | |||
[DOCKER_IGNORE_SUPPORT]: false, | |||
[SECRETS_MANAGER_PARSE_OWNED_SECRET_NAME]: false, | |||
[KMS_DEFAULT_KEY_POLICIES]: false, | |||
[S3_BUCKET_GRANT_WRITE_ACL]: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the new project default and the V2 default be the same? We want this to become the standard behavior, yeah?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought FUTURE_FLAGS
is the one that will be used for V2? And this is only used for the defaults?
Their JSDocs certainly seem to indicate that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but shouldn't the value be true
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you put it as true
here, you will change the behavior to have s3:PutObject
instead of s3:PutObject*
if the feature flag is not explicitly set, kind of defeating the point of having the feature flag at all... (we risk breaking people that created their CDK project before this change was out, when they update to the newest version)
@@ -584,7 +593,7 @@ abstract class BucketBase extends Resource implements IBucket { | |||
* @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') | |||
*/ | |||
public grantWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { | |||
return this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, | |||
return this.grant(identity, this.writeActions, perms.KEY_WRITE_ACTIONS, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm kinda expecting this change to break this:
this.props.bucket.grantWrite(options.role); |
if this field is set:
readonly accessControl?: s3.BucketAccessControl; |
I think it makes sense to add a method to explicitly opt in the old behavior, which we're going to need to use in at least the CodePipeline Action, and potentially other cases.
It's also a lot more user friendly, for everyone that intentionally needs PutObjectAcl
. It's a much better advisory to say:
"Replace grantWrite
with grantFullWrite
, because security"
Than to say:
"Replace grantWrite
with grant("s3:GetObject*", "s3:Get*", "s3:DeleteObject*", "s3:Abort*", "s3:PutObject*", ...)
, because security"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't disagree, but do you maybe want to wait for our users to let us know if it breaks, instead of doing it in this PR? What if you're wrong, and it works? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm proposing you test 😝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did it for you (props to the people writing the error message here!):
Repro in case you need it:
import * as cpipe from '@aws-cdk/aws-codepipeline';
import { App, RemovalPolicy, SecretValue, Stack, StackProps } from '@aws-cdk/core';
import { Construct } from 'constructs';
import * as s3 from '@aws-cdk/aws-s3';
import * as actions from '../../lib';
class MyStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const bukkit = new s3.Bucket(this, 'Bukkit', {
removalPolicy: RemovalPolicy.DESTROY,
});
const arto = new cpipe.Artifact();
const pipe = new cpipe.Pipeline(this, 'Pipe');
pipe.addStage({
stageName: 'Source',
actions: [
new actions.GitHubSourceAction({
actionName: 'GitHub',
oauthToken: SecretValue.secretsManager('github-token'),
output: arto,
owner: 'rix0rrr',
repo: 'kessel-run',
branch: 'main',
}),
],
});
pipe.addStage({
stageName: 'PutErThere',
actions: [
new actions.S3DeployAction({
actionName: 'InS3',
bucket: bukkit,
input: arto,
accessControl: s3.BucketAccessControl.PUBLIC_READ,
}),
],
});
}
}
const app = new App({
context: {
'@aws-cdk/aws-s3:grantWriteWithoutAcl': true,
},
});
new MyStack(app, 'DeployStack', {
env: {
region: process.env.CDK_DEFAULT_REGION,
account: process.env.CDK_DEFAULT_ACCOUNT,
},
});
app.synth();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Rico! I completely blanked on the fact that accessControl
in S3DeployAction
actually uses Bucket ACLs to achieve its functionality.
I confirmed that setting any accessControl
does require s3:PutObjectAcl
. Although it's possible to deploy publicly readable objects into a Bucket without using accessControl
(you have to change the resource policy of the Bucket to allow everyone to read), we have to support accessControl
as well.
I've added a grantPutAcl()
to IBucket
, and changed the S3DeployAction
to call that method if the feature flag is set and accessControl
was passed.
I only added s3:PutObjectAcl
permissions in grantPutAcl()
for now; contrary to the (I agree excellent) error message above, I was not able to get a situation where s3:PutObjectVersionAcl
would be required (yes, I tried overwriting the same file multiple times, didn't cause see any issues with just s3:PutObjectAcl
), so I left it out for now; we can always add it later.
Let me know if this works!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I tried overwriting the same file multiple times
Did you try this with a versioned bucket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I tried overwriting the same file multiple times
Did you try this with a versioned bucket?
Yep, I did. Exactly the same result (no error even though the Action's Role did not have s3:PutObjectVersionAcl
permissions, the resulting object was readable publicly, multiple versions were created without problems).
c5fa1e8
to
46475bd
Compare
@rix0rrr this is ready for another round of reviews! |
46475bd
to
4cf1842
Compare
@rix0rrr if you could take another pass, I would appreciate it! |
codepipeline.ActionConfig { | ||
// pipeline needs permissions to write to the S3 bucket | ||
this.props.bucket.grantWrite(options.role); | ||
|
||
if (this.props.accessControl !== undefined && | ||
FeatureFlags.of(scope).isEnabled(cxapi.S3_GRANT_WRITE_WITHOUT_ACL)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels worse because it's tying implementation details of two different classes together.
To my mind, we should be replacing
this.props.bucket.grantWrite(options.role);
With either:
this.props.bucket.grant(options.role,
's3:PutObject',
's3:PubObject*Acl',
's3:DeleteObject',
's3:...WhateverElseHere
);
OR
this.props.bucket.grantWriteWithAcl(options.role);
CDK methods should be intent-based. You tell me what you need, I'll make it happen.
Not: "you tell me be a bit of what you need, and you'll predict what I'll have done based on some flags, and then you tell me to do some more"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like either of those options 😕.
this.props.bucket.grant(options.role, 's3:PutObject', 's3:PubObject*Acl', ...)
I think is out of the question - it's way too low-level, and completely not intent-based.
I think grantWriteWithAcl()
would actually be a mistake. One of the problems that were raised with grantWrite()
giving the permissions it does now is that s3:PutObjectAcl
is not a write permissions - it's a permission-changing permission, and that it's surprising that it's present on a method that deals with writing. So, having grantWriteWithAcl()
encourages a practice that our security team considers bad (mixing permissions in different categories).
Let's just always call bucket.grantPutAcl()
if accessControl
was passed, regardless of the presence of the feature flag. The code will be simple and, like you said, intent-based, and we'll just have superfluous permissions in the case the feature flag is not set.
I've changed the code to match, let me know if this works!
@@ -584,7 +593,7 @@ abstract class BucketBase extends Resource implements IBucket { | |||
* @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') | |||
*/ | |||
public grantWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { | |||
return this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, | |||
return this.grant(identity, this.writeActions, perms.KEY_WRITE_ACTIONS, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I tried overwriting the same file multiple times
Did you try this with a versioned bucket?
Right now, Bucket.grantWrite() adds an 's3:PutObject*' permission to the passed principal. That means it grants the 's3:PubObjectAcl' permission, which allows changing the ACL of an individual object written to the Bucket, potentially giving it more visibility than other objects in the Bucket. Change that behavior to grant 's3:PutObject' instead. Since customers might be relying on the old overly-broad permissions, gate that change behind a feature flag, which means only newly created CDK projects will get this behavior.
4cf1842
to
cf0e6b2
Compare
@rix0rrr incorporated your comments, please re-review, especially considering #12391 (comment) . |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
I'm using BitBucketSourceAction as a part of my pipeline. It seems that this action requires
Without it, I observe an access denied error when the source action runs. Should I open a separate issue for this problem? |
@aav Yes please! |
@skinny85 after the If this problem will re-appear, I will investigate more and open an issue if needed. |
…ross-account Apparently, when removing the s3:PutObject* permissions in aws#12391, we broke the CodeCommitSourceAction when it's cross-account. Not entirely sure why is that permission required only when the action is cross-account, but I have confirmed this fixes the problem, so add an explicit call to `Bucket.grantPutAcl()` when the actions is cross-account. Fixes aws#14156
…ross-account (#14260) Apparently, when removing the s3:PutObject* permissions in #12391, we broke the CodeCommitSourceAction when it's cross-account. Not entirely sure why is that permission required only when the action is cross-account, but I have confirmed this fixes the problem, so add an explicit call to `Bucket.grantPutAcl()` when the actions is cross-account. Fixes #14156 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…ross-account (aws#14260) Apparently, when removing the s3:PutObject* permissions in aws#12391, we broke the CodeCommitSourceAction when it's cross-account. Not entirely sure why is that permission required only when the action is cross-account, but I have confirmed this fixes the problem, so add an explicit call to `Bucket.grantPutAcl()` when the actions is cross-account. Fixes aws#14156 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…ross-account (aws#14260) Apparently, when removing the s3:PutObject* permissions in aws#12391, we broke the CodeCommitSourceAction when it's cross-account. Not entirely sure why is that permission required only when the action is cross-account, but I have confirmed this fixes the problem, so add an explicit call to `Bucket.grantPutAcl()` when the actions is cross-account. Fixes aws#14156 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…nd `grantPut` methods (#18494) In this pull request I try to fix a big behavioral change in the bucket grant methods for write actions. This change was introduced due to security issues as stated in [this pull request](#12391) and substituted the `s3:PutObject*` action glob pattern with the simple `s3:PutObject` to exclude the dangerous `s3:PutObjectAcl` and `s3:PutObjectVersionAcl` from the equation. While I agree about the security issues, I think that other useful actions - if safe, obviously - should be added when calling the `grantWrite`, `grantReadWrite` and `grantPut` methods. This pull request adds the following actions: * s3:PutObjectLegalHold * s3:PutObjectRetention * s3:PutObjectTagging * s3:PutObjectVersionTagging I also added the `s3:PutObjectVersionAcl` action to the `grantPutAcl` method, along with the existing `s3:PutObjectAcl`. I adapted existing unit and integ tests to accept these new actions. Fixes #13616 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…nd `grantPut` methods (aws#18494) In this pull request I try to fix a big behavioral change in the bucket grant methods for write actions. This change was introduced due to security issues as stated in [this pull request](aws#12391) and substituted the `s3:PutObject*` action glob pattern with the simple `s3:PutObject` to exclude the dangerous `s3:PutObjectAcl` and `s3:PutObjectVersionAcl` from the equation. While I agree about the security issues, I think that other useful actions - if safe, obviously - should be added when calling the `grantWrite`, `grantReadWrite` and `grantPut` methods. This pull request adds the following actions: * s3:PutObjectLegalHold * s3:PutObjectRetention * s3:PutObjectTagging * s3:PutObjectVersionTagging I also added the `s3:PutObjectVersionAcl` action to the `grantPutAcl` method, along with the existing `s3:PutObjectAcl`. I adapted existing unit and integ tests to accept these new actions. Fixes aws#13616 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
…nd `grantPut` methods (aws#18494) In this pull request I try to fix a big behavioral change in the bucket grant methods for write actions. This change was introduced due to security issues as stated in [this pull request](aws#12391) and substituted the `s3:PutObject*` action glob pattern with the simple `s3:PutObject` to exclude the dangerous `s3:PutObjectAcl` and `s3:PutObjectVersionAcl` from the equation. While I agree about the security issues, I think that other useful actions - if safe, obviously - should be added when calling the `grantWrite`, `grantReadWrite` and `grantPut` methods. This pull request adds the following actions: * s3:PutObjectLegalHold * s3:PutObjectRetention * s3:PutObjectTagging * s3:PutObjectVersionTagging I also added the `s3:PutObjectVersionAcl` action to the `grantPutAcl` method, along with the existing `s3:PutObjectAcl`. I adapted existing unit and integ tests to accept these new actions. Fixes aws#13616 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Right now, Bucket.grantWrite() adds an 's3:PutObject*' permission to the passed principal.
That means it grants the 's3:PubObjectAcl' permission,
which allows changing the ACL of an individual object written to the Bucket,
potentially giving it more visibility than other objects in the Bucket.
Change that behavior to grant 's3:PutObject' instead.
Since customers might be relying on the old overly-broad permissions,
gate that change behind a feature flag,
which means only newly created CDK projects will get this behavior.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license