5

I am very new to all this but have been able to get an avatar/image uploader to work in my Rails application. A user can upload a new avatar to my S3 bucket and the avatar shows within the web application.

To this end, I've had to grant "AmazonS3FullAccess" policy to the user. That seems like a bit too much, since the user from the application only needs write (upload his avatar) and read (show the avatar on the web page) permission.

Would you agree that it is therefore better to write a custom policy rather than use AmazonS3FullAccess? If so, I've tried the policy code (adopted from here) below but this didn't work (403 Forbidden error when trying to upload an avatar image). Any suggestions how to correct this code?

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::mybucket"]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": ["arn:aws:s3:::mybucket/*"]
    }
  ]
}
Nick
  • 3,496
  • 7
  • 42
  • 96

1 Answers1

10

I've grown white hair trying to figure out the proper configuration. Here's one that's working for me:

{
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Action": [
                "s3:ListBucket",
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:DeleteObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::mybucket/*",
                "arn:aws:s3:::mybucket"
            ]
        }
    ]
}

This policy should be attached to the proper entity ("Attached entity tab"), which can be a dedicated User. If you need API/Access keys, head to the "Security Credentials" tab for that user and generate some. This allows you more control over who's using that policy.

You can also edit this policy to allow anonymous access by specifying "Principal":"*" as suggested by @therealprashant in the comments, see the docs for more infos.

But you also need to set your CORS configuration. Open the S3 console, click on your bucket, show its Properties (right panel) and click on Permissions, you'll be able to edit the configuration.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>http://*.example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
    <CORSRule>
        <AllowedOrigin>http://example.com</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Add as many CORSRule as you need, especially if you need https as well.

Hope that will help.

Edit

Here's the modified version I'm actually using nowadays.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:AbortMultipartUpload",
                "s3:ListBucket",
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:DeleteObject",
                "s3:GetObjectVersion"
            ],
            "Resource": [
                "arn:aws:s3:::mybucket/*",
                "arn:aws:s3:::mybucket"
            ]
        }
    ]
}

Note: "principal" can be omitted (like I did here) in IAM policies that are attached to an IAM user, group, or role. During authorization, "principal" is evaluated to be the entity that the policy is attached to.

Arnaud
  • 17,268
  • 9
  • 65
  • 83
  • Thanks Nycen, I tried it but unfortunately without succes (still 403 Forbidden in logs). My domain is https://mydomain.herokuapp.com. I've tried in the CORS configuration: `https://mydomain.herokuapp.com`, `https://*.herokuapp.com`, `http://mydomain.herokuapp.com`, `http://*.herokuapp.com` but all without succes when trying to upload an avatar image. In the policy I replaced `mybucket` with the name of my bucket. – Nick May 21 '15 at 08:12
  • By the way, would I also need to set a Grantee under Permissions for the bucket? – Nick May 21 '15 at 08:18
  • No you don't need to. But you need to make sure that your html form is properly setup. You can use something like https://github.com/waynehoover/s3_direct_upload – Arnaud May 21 '15 at 08:24
  • I think the html form is set up correctly. I used the steps of railstutorial.org (https://www.railstutorial.org/book/user_microposts#sec-micropost_images) and the form functions correctly if I set the policy of the user to AmazonS3FullAccess. – Nick May 21 '15 at 08:37
  • The problem also doesn't seem to be the CORS configuration. Because I have also tried * in the CORS configuration, while additionally allowing DELETE and HEAD as methods. And it still didn't work. So that leaves the policy, I guess. – Nick May 21 '15 at 08:49
  • Have been playing with the policy. If I added `"s3:*"` to the policy its actions, it mades no difffernce. But when I add `"*"` to the resource it all works. I don't understand it and of course double checked whether I spelled the name of the bucket correctly. Any ideas what could be wrong within resource? Within the bucket there are up to 5 levels of subdirectories, but that shouldn't matter right? – Nick May 21 '15 at 09:14
  • 2
    I found the cause: there was a space in front of the bucket name. You might want to remove that space from your answer for any future visitors with the same issue. Thanks for helping solve it! – Nick May 21 '15 at 09:18
  • Damn, sorry about that space. I edited my answer. Really glad you solved it :) – Arnaud May 21 '15 at 12:58
  • Hi @Nycen I am getting : "Statement is missing required element - Statement "AllowPublicRead" is missing "Principal" element " from this answer – therealprashant Feb 21 '16 at 06:26
  • you are missing "Principal": "*", in the bucket policy. Please edit it whenever you get time. And thanks for the direction. Upvoted! – therealprashant Feb 21 '16 at 07:02
  • @therealprashant Well, I partially agree, see my edited answer :) – Arnaud Feb 22 '16 at 07:25
  • Hey @nycen dont you get "principal element missing " error – therealprashant Feb 22 '16 at 09:35
  • Nope @therealprashant, I get "This policy is valid." when I press the Validate button. – Arnaud Feb 22 '16 at 09:42
  • Glad I could help. I'm wondering why you would need the Principal element when I don't though, let me know if you figure it out. – Arnaud Feb 23 '16 at 08:27
  • @therealprashant Just learned this: "principal" can be omitted in IAM policies that are attached to an IAM user, group, or role. During authorization, "principal" is evaluated to be the entity that the policy is attached to. Must explained why you need it and I don't. – Arnaud Mar 06 '16 at 18:18