# PutBucketAcl

PutBucketAcl sets a bucket's access control list (ACL) - who may read the bucket's objects. On
STRG a bucket's ACL is always one of two canned ACLs, and it is the bucket's *Bucket
Protection* setting in the dashboard: `private` is *Protected*, `public-read` is *Public*. Setting
it through the API is handy wherever a script, a tool or an agent looks after a bucket, and
[GetBucketAcl](https://docs.strg.com/s3-api/GetBucketAcl) reads it back.

**Info: Good to know on STRG**

- A bucket's ACL is `private` or `public-read`, the two choices of *Bucket Protection*, so who can
  read a bucket is always a yes-or-no question.
- The setting travels in the `x-amz-acl` header, which every request carries; without it,
  STRG answers `400 InvalidRequest`.
- An `AccessControlPolicy` body and the `x-amz-grant-*` headers answer `501 NotImplemented`, so a
  client knows straight away that the canned ACL is the way to set access.
- The key needs *Manage buckets*, a permission for keys with access to all buckets.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.

## When you'd use it

- **Publishing files.** A website's images, a product's downloads or a dataset shared by link
  live happily in a public bucket. A setup script makes the bucket `public-read`, and from then on
  every object in it can be fetched with a plain URL, no signature needed.
- **Closing a bucket again.** When a launch is over, or a file was shared by mistake, setting
  `private` makes the bucket *Protected* once more - from a script or an agent, without opening
  the dashboard.
- **Tools written for Amazon S3.** Clients and infrastructure tools that manage a bucket's canned
  ACL as part of its configuration work as they are, as long as they ask for `private` or
  `public-read`.

## Request

```http
PUT /?acl HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-acl: public-read
Content-Length: 0
```

The path-style form is `PUT /my-bucket?acl` on `s3.eu-central-1.strg.com` -
[Addressing](https://docs.strg.com/s3-api/overview#addressing) explains both.

### Permissions

The access key needs **Manage buckets** (`buckets:write`), a permission held by keys with access
to all buckets. A key without it answers `403 AccessDenied`. [Access keys](https://docs.strg.com/access-keys)
has the details.

**Warning: `public-read` opens every object**

This is the same change as choosing *Public* under *Bucket Protection* in the dashboard, without
the dashboard's confirmation. `public-read` makes every object in the bucket, including the ones
already in it, readable by anyone without a signature - with [GetObject](https://docs.strg.com/s3-api/GetObject) and
[HeadObject](https://docs.strg.com/s3-api/HeadObject) at `https://s3.<region>.strg.com/<bucket>/<key>` or
`https://<bucket>.s3.<region>.strg.com/<key>`. Listing the bucket, uploading, changing and
deleting still take an access key. The dashboard can take up to a minute to show a change made
through the API.

**Note: After switching back to `private`**

Anonymous readers can still be served an object STRG has cached for them **for up to
5 minutes**, since nothing is cleared when the ACL changes. An object is cached only when its own
`Cache-Control` lets a shared cache keep it (`max-age` or `s-maxage` above zero, and no
`no-store`, `no-cache` or `private`), and never for more than 5 minutes. An object stored without
a `Cache-Control` is never cached. [Anonymous reads](https://docs.strg.com/s3-api/overview#anonymous-reads) describes the
cache.

### URI parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Bucket` | String | Yes | The bucket whose ACL is set. |
| `acl` | Flag | Yes | Selects the ACL subresource. Sent with no value. |

### Request headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `x-amz-acl` | String | Yes | The canned ACL: `private` or `public-read`. Any other canned ACL (`public-read-write`, `authenticated-read`, ...) answers `501 NotImplemented`, and a request without the header answers `400 InvalidRequest`. |
| `x-amz-grant-full-control` | String | No | STRG answers `501 NotImplemented`; `x-amz-acl` sets the bucket's access. |
| `x-amz-grant-read` | String | No | STRG answers `501 NotImplemented`; `x-amz-acl` sets the bucket's access. |
| `x-amz-grant-read-acp` | String | No | STRG answers `501 NotImplemented`; `x-amz-acl` sets the bucket's access. |
| `x-amz-grant-write` | String | No | STRG answers `501 NotImplemented`; `x-amz-acl` sets the bucket's access. |
| `x-amz-grant-write-acp` | String | No | STRG answers `501 NotImplemented`; `x-amz-acl` sets the bucket's access. |
| `Content-MD5` | String | No | Welcome. The request has no body, so there is nothing for it to protect. |
| `x-amz-sdk-checksum-algorithm` | String | No | Welcome, together with the checksum header the SDK sends beside it. |
| `x-amz-expected-bucket-owner` | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |

### Request body

None. The bucket's access is set entirely by `x-amz-acl`, so an `AccessControlPolicy` document
answers `501 NotImplemented`.

## Response

### Response headers

| Name | Description |
| --- | --- |
| `x-amz-request-id` | The request ID, handy for a support request. |

### Response body

None. A successful request answers `200 OK` with an empty body.

## Errors

These are the errors this request can return, and the situation behind each one.

| Code | HTTP status | When |
| --- | --- | --- |
| `InvalidRequest` | 400 | The request has no `x-amz-acl` header. |
| `NotImplemented` | 501 | The request carries an `AccessControlPolicy` body, an `x-amz-grant-*` header, a canned ACL other than `private` or `public-read`, or a query parameter other than `acl` (presigning's `X-Amz-*` parameters aside). |
| `NoSuchBucket` | 404 | The bucket does not exist, it is in a region other than the endpoint's, or the account has no storage in this endpoint's region yet. |
| `AccessDenied` | 403 | The access key lacks *Manage buckets*, the account is not active, an `x-amz-*` header is unsigned, or the request is unsigned. |
| `InvalidAccessKeyId` | 403 | The access key does not exist, has expired or been revoked - or it was created moments ago and is not usable yet in this region where the account already stores data, in which case the same request works shortly afterwards. |
| `SignatureDoesNotMatch` | 403 | The request signature does not match. |
| `RequestTimeTooSkewed` | 403 | The request time is more than 15 minutes from the server's. |
| `AuthorizationHeaderMalformed` | 400 | The signature's credential scope names a region other than the endpoint's. |
| `SlowDown` | 503 | Too many requests. The `Retry-After` header says when a retry is welcome; the AWS SDKs retry on their own. |
| `ServiceUnavailable` | 503 | A temporary failure; the same request can be retried. |
| `InternalError` | 500 | An unexpected failure; the same request can be retried. |

## Differences from Amazon S3

The table lists each place where STRG behaves differently, and what that means for a client.

| Aspect | Amazon S3 | STRG |
| --- | --- | --- |
| Canned ACLs | `private`, `public-read`, `public-read-write`, `authenticated-read` | `private` and `public-read`, the two choices of *Bucket Protection*. Any other answers `501 NotImplemented` |
| Explicit grants | `x-amz-grant-*` headers or an `AccessControlPolicy` body | The canned ACL is the whole setting, so there are no grants to keep track of. Grant headers and a body answer `501 NotImplemented` |
| `x-amz-acl` | Optional, when grants or a body are sent instead | Part of every request, since it carries the setting: `400 InvalidRequest` without it |
| Anonymous requests to a `public-read` bucket | `GetObject`, `HeadObject` and `ListObjects` are served | `GetObject` and `HeadObject` are served, which is all a shared link needs; listing and every other operation take a signed request and answer `403 AccessDenied` unsigned. [Authentication](https://docs.strg.com/s3-api/overview#authentication) has the details |
| Bucket in another region | `301` redirect with `x-amz-bucket-region` | `404 NoSuchBucket`. A bucket is reached through its own regional endpoint |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check |

## Examples

Making `my-bucket` public-read:

**AWS CLI**

```bash
aws s3api put-bucket-acl \
  --bucket my-bucket \
  --acl public-read \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

s3.put_bucket_acl(Bucket="my-bucket", ACL="public-read")
```

**JavaScript (AWS SDK v3)**

```javascript
import { PutBucketAclCommand, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

await client.send(new PutBucketAclCommand({ Bucket: "my-bucket", ACL: "public-read" }));
```

Making `my-bucket` private again - the same as choosing *Protected* in the dashboard:

**AWS CLI**

```bash
aws s3api put-bucket-acl \
  --bucket my-bucket \
  --acl private \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

**Python (boto3)**

```python
import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

s3.put_bucket_acl(Bucket="my-bucket", ACL="private")
```

**JavaScript (AWS SDK v3)**

```javascript
import { PutBucketAclCommand, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

await client.send(new PutBucketAclCommand({ Bucket: "my-bucket", ACL: "private" }));
```

## Related

- [GetBucketAcl](https://docs.strg.com/s3-api/GetBucketAcl)
- [PutObjectAcl](https://docs.strg.com/s3-api/PutObjectAcl)
- [GetObjectAcl](https://docs.strg.com/s3-api/GetObjectAcl)
- [GetObject](https://docs.strg.com/s3-api/GetObject) and [HeadObject](https://docs.strg.com/s3-api/HeadObject), which read a `public-read` bucket's objects without a signature
- [Access keys](https://docs.strg.com/access-keys)
- [S3 API overview](https://docs.strg.com/s3-api/overview)
