aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.82k stars 821 forks source link

Storage: Include option to set s3:PutObjectAcl when granting auth/guest access to allow acl: public-read (currently available) in aws-amplify/Storage.put method options. #5322

Open mayteio opened 4 years ago

mayteio commented 4 years ago

Is your feature request related to a problem? Please describe. Currently when setting authenticated access to storage, you're given the option to set put, read, delete access for both auth and unauth access. The aws-amplify package allows passing an acl: public-read (see "other options"). When you try to set this with the default write permissions during setup, you receive a 403 error because the role isn't set up with PutObjectAcl - you have to manually add the permission to the s3PermissionsAuthenticatedPublic parameter to storage/parameters.json before pushing again.

Pretty common use case to upload photos that are publicly accessible, so this seems like a no-brainer.

Describe the solution you'd like Add a "Grant public access" option when specifying auth permissions during setup.

Describe alternatives you've considered Manually adding the permission to the parameters is easy, however, might trip up inexperienced developers who (rightly) expect the aws-amplify/Storage.put option 'acl: public-read' to work out of the box.

renebrandel commented 4 years ago

Hi @mayteio - can you give me more details on the scenario you are looking to enable with this? How is your bucket structured?

mayteio commented 4 years ago

Sure thing. Backstory, we use Esri products to manage a lot of geospatial data. The data management tools they have leave a lot to be desired, so we've built our own CMS on top of it that uses Esri for core things like storing geospatial data, basic field management, etc.

We use AWS to manage file uploads per geospatial feature, as this is functionality esri doesn't have. We are using S3 via amplify to manage these uploads (we have more plans to use amplify in this project), storing the object key in an esri field.

We want these files to be publicly available, so when someone is looking at the feature, they can pull the image from S3 without logging in, and not necessarily from an app that is configured for this particular amplify project (so unauth access is out of the question). Thus the need for ACL.

xitanggg commented 4 years ago

Thanks a lot for sharing this @mayteio, much appreciated the suggestion and resolution. It is a no brainer to me either and I expect it to work out of box. (One thing I would add is to be mindful of public access s3PermissionsAuthenticatedPublic, which gives every user the permission to edit the file. I would give PutObjectAcl to protected access s3PermissionsAuthenticatedProtected to limit the editing permission to the owner only.)

@renebrandel To give another scenario. The github page we are currently on where we get user profile images is a perfect example.

The profile image source is structured with this format https://avatars1.githubusercontent.com/u/{userId} (the prefix can be thought of as a bucket). Once the page is loaded with the user's id, it requests the image source directly at https://{bucket-prefix}/{userId}. The data should be publicly available.

flyandi commented 4 years ago

Would be create if we could include this as part of the permission configuration of the function or in general

function-parameters.json

{
  "permissions": {
    "storage": {
      "s3images": [
        "create",
        "read",
        "update",
        "delete",
        "updateacl"
      ]
    }
  },
  "lambdaLayers": []
}

For now I just added it manually to the policy and seems to persist.

image

cpmcmanaman commented 3 years ago

For what it is worth, I also like mayteio's idea of manually adding the permission to the parameters is easy, however, might trip up inexperienced developers who (rightly) expect the aws-amplify/Storage.put option 'acl: public-read' to work out of the box. It is what I intuitively tried to do before reading documentation.

GeorgeBellTMH commented 2 years ago

9162 - this may be broken, please see this bug...

defmtog commented 2 years ago

As of amplify 7.6.20 there is no s3-cloudformation-template.json to edit and place the s3:PutObjectACL into anymore. there is only a cli-input.json with enums for permissions:

export enum S3StorageParamsPermissionType {
  CREATE_AND_UPDATE = 'create/update',
  READ = 'read',
  DELETE = 'delete',
}

and there is no enum for PutObjectACL so the manual edit doesn't work any more. any other suggestions?

benjamindoe commented 2 years ago

I've managed to set s3:PutObjectAcl using overrides in Amplify. In a previous version of Amplify, this worked. More recently it hasn't been working.

I've posted this in #10001 but I thought I'd share here because it is related.

My override looks something like this


export function override(resources: AmplifyS3ResourceTemplate) {
  resources.s3AuthPublicPolicy.policyDocument.Statement[0] = {
    ...resources.s3AuthPublicPolicy.policyDocument.Statement[0],
    Action: [
      "s3:PutObjectAcl",
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject",
    ],
  };
}

This gives a resulting IAM Policy within the authRole IAM role for Public_policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObjectAcl",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET_NAME_HERE>/public/*"
            ],
            "Effect": "Allow"
        }
    ]
}

Uploading using the Storage Amplify API returns a 403 error while logged into the Amplify app

await Storage.put(fileName, file, {
  contentType: file.type,
  level: "public",
  acl: "public-read",
});

Removing the line acl: "public-read" results in a successful upload but it means the files are not publicly accessible via a permanent URL.

benjamindoe commented 2 years ago

I've discovered my issue was to do with cognito user groups. Users in user groups assume a different IAM role that is not the same as the authRole. You need to add overrides for your user groups as well if you want users that are in those groups to be able to have the same actions. I added this just above where I overwrote s3AuthPublicPolicy

resources.addCfnResource(
  {
    type: "AWS::IAM::Policy",
    properties: {
      PolicyName: "<USER_GROUP_NAME>-group-s3-PutObjectAcl-policy",
      PolicyDocument: {
        Version: "2012-10-17",
        Statement: [
          {
            Action: ["s3:PutObjectAcl"],
            Effect: "Allow",
            Resource: {
              "Fn::Join": [
                "",
                [
                  "arn:aws:s3:::",
                  {
                    Ref: "S3Bucket",
                  },
                  "/*",
                ],
              ],
            },
          },
        ],
      },
      Roles: [
        {
          "Fn::Join": [
            "",
            [
              {
                Ref: "auth<AUTH_RESOURCE_NAME>UserPoolId",
              },
              "-<USER_GROUP_NAME>Role",
            ],
          ],
        },
      ],
    },
  },
  "<USER_GROUP_NAME>PutObjectAclPolicy"
);

Although you can do this with overrides, it should really be an option when you apply privileges using the amplify cli.

alphonse92 commented 2 years ago

I've managed to set s3:PutObjectAcl using overrides in Amplify. In a previous version of Amplify, this worked. More recently it hasn't been working.

I've posted this in #10001 but I thought I'd share here because it is related.

My override looks something like this

export function override(resources: AmplifyS3ResourceTemplate) {
  resources.s3AuthPublicPolicy.policyDocument.Statement[0] = {
    ...resources.s3AuthPublicPolicy.policyDocument.Statement[0],
    Action: [
      "s3:PutObjectAcl",
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject",
    ],
  };
}

This gives a resulting IAM Policy within the authRole IAM role for Public_policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:PutObjectAcl",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::<BUCKET_NAME_HERE>/public/*"
            ],
            "Effect": "Allow"
        }
    ]
}

Uploading using the Storage Amplify API returns a 403 error while logged into the Amplify app

await Storage.put(fileName, file, {
  contentType: file.type,
  level: "public",
  acl: "public-read",
});

Removing the line acl: "public-read" results in a successful upload but it means the files are not publicly accessible via a permanent URL.

I was able to do that by overriding with this:


import {AmplifyS3ResourceTemplate} from '@aws-amplify/cli-extensibility-helper';

export function override(resources: AmplifyS3ResourceTemplate) {
  const policyDoc = JSON.parse(
    JSON.stringify(resources.s3AuthPublicPolicy.policyDocument, null, 2),
  );

  const newPolicyDoc = {...policyDoc};
  const newActions = [
    's3:PutObject',
    's3:GetObject',
    's3:DeleteObject',
    's3:PutObjectAcl', // allow to users to set public objects
  ];
  newPolicyDoc.Statement.Action = newActions;

  resources.s3AuthPublicPolicy.policyDocument.Statement.Action = newActions;
}
mineoni commented 2 years ago

Previously (at least Amplify CLI 6.3.1) the public_policy was set to "s3: PutObjectAcl", "s3: PutObject", "s3: GetObject", "s3: DeleteObject". When I pushed with Amplify CLI 8.2.0, there was only "s3: PutObject", and it became 403 when accessing the resource.

Please let me know if anyone knows how this destructive change came about. thanks.

alphonse92 commented 2 years ago

You should overwrite your s3 policy.

amplify override storage

Then copy this:

import {AmplifyS3ResourceTemplate} from '@aws-amplify/cli-extensibility-helper';

export function override(resources: AmplifyS3ResourceTemplate) {
  const policyDoc = JSON.parse(
    JSON.stringify(resources.s3AuthPublicPolicy.policyDocument, null, 2),
  );

  const newPolicyDoc = {...policyDoc};
  const newActions = [
    's3:PutObject',
    's3:GetObject',
    's3:DeleteObject',
    's3:PutObjectAcl', // allow to users to set public objects
  ];
  newPolicyDoc.Statement.Action = newActions;

  resources.s3AuthPublicPolicy.policyDocument.Statement.Action = newActions;
}

and paste that to overwrite.ts in storage folder

mineoni commented 2 years ago

@alphonse92

thanks. but override is not working. Even though "amplify push " after it, it remains putobjet.

Until now, there was no need to override. In another env, the original public role is created without overriding.

harakiro commented 1 year ago

I am also having this issue. No matter how I setup my storage. The objects in public are not being set to public readable. This is very frustrating. Is there a fix for this?