Skip to content

Access control

Three things decide whether an S3 request is allowed:

  1. The account boundary. A bucket belongs to one organization (one account when self-hosting). Keys from elsewhere can't touch it unless its policy says so.
  2. The key's scope. A key can be limited to some buckets, and to reading.
  3. The bucket policy. An optional IAM-style document on the bucket that can deny anyone, or allow everyone ("*") to do something.

An explicit Deny in a bucket policy wins over everything, the owner included. The bucket's own keys are allowed within their scope. Anyone else needs an Allow in the policy. Everything else is refused with 403 AccessDenied.

Scoped keys

In the console, choose the buckets a key may use and whether it is read-only when you create it. When self-hosting:

Terminal
swiftfs admin key create --account acme --bucket uploads --bucket thumbnails
swiftfs admin key create --account acme --bucket reports --read-only

Give each application the narrowest key that works: a thumbnailing job that reads uploads and writes thumbnails needs no access to invoices.

Public read

To serve a prefix to anyone without a signature (images for a website, say), allow s3:GetObject on it for "*":

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicMedia",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::acme-site/media/*"
    }
  ]
}
Terminal
aws --endpoint-url "$S3_ENDPOINT" s3api put-bucket-policy --bucket acme-site --policy file://policy.json
aws --endpoint-url "$S3_ENDPOINT" s3api get-bucket-policy-status --bucket acme-site   # IsPublic

Anonymous GET /acme-site/media/logo.png now works. Listing the bucket, writing, and reading outside media/ are still refused. Anonymous requests are billed to the bucket's owner and rate-limited per client IP.

Remove the policy to make the bucket private again:

Terminal
aws --endpoint-url "$S3_ENDPOINT" s3api delete-bucket-policy --bucket acme-site

Denying by network

A Deny with an IP condition keeps everyone, the owner included, to known networks:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::acme-records", "arn:aws:s3:::acme-records/*"],
      "Condition": { "NotIpAddress": { "aws:SourceIp": ["203.0.113.0/24"] } }
    }
  ]
}

The owner can always read, replace or delete the policy itself, so a mistake can't lock you out.

The policy language

SwiftFS implements a subset of AWS's IAM policy grammar and rejects anything outside it with MalformedPolicy, rather than quietly ignoring it:

ElementSupported
Version2012-10-17 (or 2008-10-17)
EffectAllow, Deny
Principal"*" (or {"AWS": "*"}) only
Action, NotActions3: actions, with * and ? wildcards
Resourcearn:aws:s3:::bucket and arn:aws:s3:::bucket/key-pattern, this bucket only
ConditionIpAddress, NotIpAddress on aws:SourceIp

Grants to specific other accounts are not available yet.

In SwiftFS Cloud

Console roles decide who may manage buckets, keys and members; see organizations and roles. A member's access keys never allow more than their role.