Skip to content
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

Merged
merged 2 commits into from
Jan 13, 2021

Conversation

skinny85
Copy link
Contributor

@skinny85 skinny85 commented Jan 7, 2021

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

@gitpod-io
Copy link

gitpod-io bot commented Jan 7, 2021

@skinny85 skinny85 self-assigned this Jan 7, 2021
@skinny85 skinny85 requested a review from rix0rrr January 7, 2021 02:08
@github-actions github-actions bot added the @aws-cdk/aws-s3 Related to Amazon S3 label Jan 7, 2021
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Jan 7, 2021
@skinny85 skinny85 force-pushed the fix/s3-grant-write-acl branch from c5b4b09 to e01ea04 Compare January 7, 2021 02:11
* 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';
Copy link
Contributor

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).

Copy link
Contributor Author

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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...

Copy link
Contributor

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?

Copy link
Contributor Author

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)

packages/@aws-cdk/aws-s3/lib/bucket.ts Outdated Show resolved Hide resolved
@@ -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,
Copy link
Contributor

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"

Copy link
Contributor Author

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? 🙂

Copy link
Contributor

@rix0rrr rix0rrr Jan 11, 2021

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 😝

Copy link
Contributor

@rix0rrr rix0rrr Jan 11, 2021

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!):

image

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();

Copy link
Contributor Author

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!

Copy link
Contributor

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?

Copy link
Contributor Author

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).

@skinny85 skinny85 force-pushed the fix/s3-grant-write-acl branch 2 times, most recently from c5fa1e8 to 46475bd Compare January 8, 2021 00:11
@skinny85 skinny85 requested a review from rix0rrr January 8, 2021 00:11
@skinny85
Copy link
Contributor Author

skinny85 commented Jan 8, 2021

@rix0rrr this is ready for another round of reviews!

@skinny85 skinny85 force-pushed the fix/s3-grant-write-acl branch from 46475bd to 4cf1842 Compare January 12, 2021 00:45
@skinny85
Copy link
Contributor Author

@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)) {
Copy link
Contributor

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"

Copy link
Contributor Author

@skinny85 skinny85 Jan 12, 2021

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,
Copy link
Contributor

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.
@skinny85 skinny85 force-pushed the fix/s3-grant-write-acl branch from 4cf1842 to cf0e6b2 Compare January 12, 2021 23:06
@skinny85 skinny85 requested a review from rix0rrr January 12, 2021 23:07
@skinny85
Copy link
Contributor Author

@rix0rrr incorporated your comments, please re-review, especially considering #12391 (comment) .

@mergify
Copy link
Contributor

mergify bot commented Jan 13, 2021

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-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: f954d70
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Jan 13, 2021

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).

@mergify mergify bot merged commit cd437cf into aws:master Jan 13, 2021
@skinny85 skinny85 deleted the fix/s3-grant-write-acl branch January 13, 2021 17:51
@aav
Copy link

aav commented Jan 28, 2021

I'm using BitBucketSourceAction as a part of my pipeline. It seems that this action requires s3:PutObjectAcl to write sources to the artifact_bucket. I had to add the following line as a workaround:

pipeline.artifact_bucket.grant_put_acl(pipeline.role)

Without it, I observe an access denied error when the source action runs.

Should I open a separate issue for this problem?

@skinny85
Copy link
Contributor Author

@aav Yes please!

@aav
Copy link

aav commented Jan 28, 2021

@skinny85 after the artifact_bucket was re-created, the problem disappeared. So it's hard to say under which conditions source action requires s3:PutObjectAcl.

If this problem will re-appear, I will investigate more and open an issue if needed.

skinny85 added a commit to skinny85/aws-cdk that referenced this pull request Apr 19, 2021
…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
mergify bot pushed a commit that referenced this pull request Apr 21, 2021
…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*
john-tipper pushed a commit to john-tipper/aws-cdk that referenced this pull request May 10, 2021
…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*
hollanddd pushed a commit to hollanddd/aws-cdk that referenced this pull request Aug 26, 2021
…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*
mergify bot pushed a commit that referenced this pull request Jan 25, 2022
…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*
LukvonStrom pushed a commit to LukvonStrom/aws-cdk that referenced this pull request Jan 26, 2022
…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*
TikiTDO pushed a commit to TikiTDO/aws-cdk that referenced this pull request Feb 21, 2022
…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*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-s3 Related to Amazon S3 contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants