# S3 permissions
What an identity needs to use a hyperfile container, by what it does with
it. Everything a container stores is under its own prefix — `P/` below, for
a container at `s3://bucket/P` — and the WAL, if enabled, under the prefix
of its own `root_uri`.
## General purpose buckets
| Open read-only and read, including `open_cno` | `s3:GetObject` | `arn:aws:s3:::bucket/P/*` |
| | `s3:ListBucket`, with `s3:prefix` limited to `P/*` | `arn:aws:s3:::bucket` |
| Also create, write, flush, change attributes | `s3:PutObject` | `arn:aws:s3:::bucket/P/*` |
| Also `unlink` | `s3:DeleteObject` | `arn:aws:s3:::bucket/P/*` |
| The WAL | `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject` | the WAL prefix, `/*` |
| | `s3:ListBucket`, with `s3:prefix` limited to it | `arn:aws:s3:::bucket` |
A minimal writer's policy, then:
```json
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::bucket/P/*" },
{ "Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bucket",
"Condition": { "StringLike": { "s3:prefix": "P/*" } } }
]
}
```
Notes:
- `s3:ListBucket` is needed even though a container that is only read and
written is never listed. Hyperfile learns that an object does not exist
from a `HeadObject` answered 404 — whether the inode is there before a
create, whether the next checkpoint was written before an open — and
without `s3:ListBucket` S3 answers 403 for a missing object instead.
Create and open then fail with `PermissionDenied` on the `HeadObject` of
the inode.
- `s3:PutObject` covers a segment written as a multipart upload: creating
it, its parts, and completing it. Aborting one after a failure is
`s3:AbortMultipartUpload`, which S3 allows the identity that created the
upload by default; it has to be granted only where it is denied, or in a
VPC endpoint policy, which does not extend that default. Without it a
failed upload is left open, the flush's error says so, and only a
lifecycle rule for incomplete multipart uploads removes it.
- Every write hyperfile makes is conditional: `PutObject` and
`CompleteMultipartUpload` carry `If-None-Match: *` or `If-Match`. A bucket
policy that refuses unconditional writes — [Enforce conditional
writes](https://docs.aws.amazon.com/AmazonS3/latest/userguide/conditional-writes-enforce.html),
with `s3:ObjectCreationOperation` exempting the multipart create and
parts — does not get in its way.
- Without the WAL nothing is deleted outside `unlink`. With it, the log is
truncated as checkpoints are published and transaction markers are
removed. Without `s3:DeleteObject` those deletes fail with a warning and
the log grows, and aborting a transaction fails, since that deletes its
records.
- The cleaner (`hyperfile-cleaner`, a separate tool) removes segments and
lists the container: `s3:DeleteObject`, and `s3:ListBucket` on `P/*`.
- On a bucket encrypted with SSE-KMS, the identity also needs
`kms:Decrypt` and `kms:GenerateDataKey` on the key.
## Directory buckets (S3 Express One Zone)
Requests to a directory bucket are authorised by the session the SDK opens
with `s3express:CreateSession`, not action by action:
| Open read-only and read | `s3express:CreateSession`, `s3express:SessionMode` `ReadOnly` |
| Everything else, WAL included | `s3express:CreateSession`, `s3express:SessionMode` `ReadWrite` |
on `arn:aws:s3express:region:account:bucket/bucket-name`. A `ReadWrite`
session covers aborting a multipart upload and deleting. Without the
permission to open a session at all, the SDK's first request fails before
reaching the bucket, and hyperfile reports it as a dispatch failure
(`ConnectionReset`), not as `PermissionDenied`.
## How this was checked
Each row was run against a general purpose and a directory bucket with
temporary credentials restricted by a session policy to that row: create
and flush, reopen and a multipart flush, a read-only open that reads it all
back, a multipart flush with a part refused so that it is aborted, and
`unlink`. Without `s3:ListBucket`, every step failed at the inode's
`HeadObject`; with it, all but `unlink` succeeded, and failed uploads were
aborted without `s3:AbortMultipartUpload` being granted; `unlink` needed
`s3:DeleteObject`. A read-only identity (`s3:GetObject` and
`s3:ListBucket`, or a `ReadOnly` session) opened and read a container
another identity wrote. With writes allowed only when they carry
`If-None-Match` or `If-Match`, every step succeeded in both the default and
`wal` builds. The WAL rows are from the source, not from such a run.