Steps:

create a AWS s3 bucket and then create a new AWS IAM user:

  1. create new user in AWS IAM (copy access_key and sectet and store them to Rails credentials)
  2. on “Set Permission” step click on “Attach existing policies directly”
  3. click on “create policy” button
  4. new window will popup where you paste the JSON policy from section “AWS Policy” bellow. Name it what you want
  5. on “Set Permission” select the newly created policy (you many need to reload the page so it appears)

Best practices for AWS are that for every Rails app enviroment you should have own user and own bucket. That means for production create s3 bucket my-project-prod and IAM user my-project-prod and for staging s3 bucket my-project-stg and IAM user my-project-stg. Same for develop if you are planing to use S3 there

AWS Policy

from ActiveStorage S3 guide

The core features of Active Storage require the following permissions: s3:ListBucket, s3:PutObject, s3:GetObject, and s3:DeleteObject. If you have additional upload options configured such as setting ACLs then additional permissions may be required.

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

Sources

Cross-origin resource sharing (CORS)

If you want to use Direct upload you need to configure the CORS on AWS S3 bucket

[
    {
        "AllowedHeaders": [
            "Authorization"
        ],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    },
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT"
        ],
        "AllowedOrigins": [
            "https://www.my-website.com"
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    }
]