BackWPup: Amazon S3 Access Denied

I’m working on setting up a nightly backup addon for my hosting clients, and since most of my clients are on WordPress, I decided to first focus on setting up the backup to occur via WordPress plugin. Backups are going to an Amazon S3 bucket, and because there is a limit on the number of buckets per account, I wanted to make sure all client backups go to a single bucket.

The challenge with that, of course, is security. I don’t want all my clients to have access to or even view each other’s backups and if the keys are lost or compromised, I don’t want all of my clients’ data to be vulnerable.

Thankfully, the AWS blog has my back and there is a wonderful blog post describing exactly how to set up user permissions so that users only have access to their own home directories. Having done that (at this point, I am using the exact code at the bottom of the AWS blog post), all that was left was to create the users themselves, plug their keys into a plugin and we’re done.

After experimenting with multiple plugins, I settled on BackWPup. I plugged in the keys, annnnd…

[27-May-2015 20:00:10] 1. Trying to send backup file to S3 Service …
[27-May-2015 20:00:10] Connected to S3 Bucket "rainworks-hosting-backups" in us-west-2
[27-May-2015 20:00:10] Checking for not aborted multipart Uploads …
[27-May-2015 20:00:10] ERROR: S3 Service API: Access Denied
[27-May-2015 20:00:10] 2. Trying to send backup file to S3 Service …
[27-May-2015 20:00:11] Connected to S3 Bucket "rainworks-hosting-backups" in us-west-2
[27-May-2015 20:00:11] Checking for not aborted multipart Uploads …
[27-May-2015 20:00:11] ERROR: S3 Service API: Access Denied
[27-May-2015 20:00:11] 3. Trying to send backup file to S3 Service …
[27-May-2015 20:00:11] Connected to S3 Bucket "rainworks-hosting-backups" in us-west-2
[27-May-2015 20:00:11] Checking for not aborted multipart Uploads …
[27-May-2015 20:00:11] ERROR: S3 Service API: Access Denied

Googling on the exact BackWPup messaging brought unhelpful results, but after enabling access logging on my bucket, I got better error messaging (which has been trimmed for clarity):

REST.HEAD.BUCKET - "HEAD / HTTP/1.1" 403 AccessDenied 243 - 16
REST.GET.UPLOADS - "GET /?uploads&prefix=rainworkswebdevelopment.com%2F HTTP/1.1" 403 AccessDenied 243 - 6

So permissions aren’t quite what they need to be to allow the plugin to do what it needs to do. After more reading and research (the comments on the afore-linked AWS blog post were quite helpful!), this is the final version of my policy document (the lines I added or modified from Amazon’s code are highlighted):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowGroupToSeeBucketListInTheConsole",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetBucketLocation",
                "s3:ListBucketMultipartUploads"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::*"
            ]
        },
        {
            "Sid": "AllowRootListingWithoutPrefix",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::rainworks-hosting-buckets"
            ],
            "Condition": {
                "Null": {
                    "s3:prefix": "true"
                },
                "StringEquals": {
                    "s3:delimiter": [
                        "/"
                    ]
                }
            }
        },
        {
            "Sid": "AllowRootAndHomeListingOfCompanyBucket",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::rainworks-hosting-backups"
            ]
        },
        {
            "Sid": "AllowListingOfUserFolder",
            "Action": [
                "s3:ListBucket",
                "s3:ListBucketMultipartUploads"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::rainworks-hosting-backups"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "${aws:username}/*"
                    ]
                }
            }
        },
        {
            "Sid": "AllowAllS3ActionsInUserFolder",
            "Action": [
                "s3:*"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::rainworks-hosting-backups/${aws:username}/*"
            ]
        }
    ]
}

Everything works like a charm now! Hopefully this saves someone else some trouble!

About Kelly Carter

I'm a freelance web developer, doing business under the name Rainworks Web Development. I'm a skeptical technophile, voracious reader, softcore gamer, and haphazard tinkerer. I have a long-term partner, a cat, and no time for glass ceilings.

Speak Your Mind

*