--- title: "Recommended S3 Actions for Read-Only and Read-Write Bucket Policy Grants (Non-Admin Users)" slug: "recommended-s3-actions-for-read-only-and-read-write-bucket-policy-grants" description: "Learn how to provision S3 buckets with tailored policies for read-only, read-write, and write-only users in VAST Cluster 5.x for optimal storage management." updated: 2026-11-03T00:33:12Z published: 2026-11-03T00:33:12Z canonical: "docs.vastdata.com/recommended-s3-actions-for-read-only-and-read-write-bucket-policy-grants" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # Recommended S3 Actions for Read-Only and Read-Write Bucket Policy Grants (Non-Admin Users) **Applies to:** VAST Cluster 5.x with S3 enabled **Audience:** Storage administrators provisioning buckets for end users --- ## Question When provisioning buckets for non-admin users, like regular consumers of the S3 Storage, what are the recommended sets of S3 actions to grant in a bucket policy for: 1. **Read-only (RO)** users who can list and download objects, nothing else 2. **Read-write (RW)** users who have full object control (upload, download, delete, multipart), but **no** control over the bucket itself 3. **Write-only (WO)** users who can upload objects only; cannot list the bucket or read anything back (the "drop box" pattern) This comes up when buckets are created for end users over the life of the cluster, with a single policy per bucket whose `Principal` is populated with users and/or groups. ## Summary | Capability | S3 actions | RO | RW | WO | | --- | --- | --- | --- | --- | | List objects | `s3:ListBucket`, `s3:GetBucketLocation` | ✓ | ✓ | — | | Download objects | `s3:GetObject` | ✓ | ✓ | — | | Upload objects | `s3:PutObject` | — | ✓ | ✓ | | Delete objects | `s3:DeleteObject` | — | ✓ | — | | Multipart uploads | `s3:AbortMultipartUpload`, `s3:ListMultipartUploadParts`, `s3:ListBucketMultipartUploads` (RW only) | — | ✓ | ✓ | | Modify bucket policy / ACLs | `s3:PutBucketPolicy`, `s3:PutBucketAcl`, `s3:PutObjectAcl` | — | — | — | | Delete the bucket | `s3:DeleteBucket` | — | — | — | | Change bucket configuration | `s3:PutBucketVersioning`, `s3:PutLifecycleConfiguration`, `s3:PutBucketCORS`, `s3:PutBucketNotification`, `s3:PutBucketTagging` | — | — | — | ## Recommended action sets ### Read-only (RO) ```plaintext s3:GetObject s3:ListBucket s3:GetBucketLocation ``` Optional additions depending on the use case: ```plaintext s3:GetObjectTagging        # if apps read object tags s3:GetObjectVersion        # if versioning is enabled and users need s3:ListBucketVersions      #   access to non-current versions ``` Notes: - `s3:ListBucket` is required for clients (s3cmd, AWS CLI `s3 ls`, most SDK tools) to list objects. Granting `s3:GetObject` alone produces a bucket users can read from only if they already know exact keys, which is sometimes needed, but not the normal read-only case. - `s3:GetBucketLocation` is a quality-of-life call. Some SDKs and tools call it during client initialization, and omitting it produces confusing `AccessDenied` errors before any data operation runs. ### Read-write (RW) Everything in the RO set, plus: ```plaintext s3:PutObject s3:DeleteObject s3:AbortMultipartUpload s3:ListMultipartUploadParts s3:ListBucketMultipartUploads ``` Optional additions depending on the use case: ```plaintext s3:PutObjectTagging        # if applications write object tags s3:DeleteObjectTagging s3:DeleteObjectVersion     # if versioning is enabled and users may                           #   remove specific versions ``` **Notes**: - The three multipart actions are **not optional** in practice. Any client uploading objects above the SDK multipart threshold (8 MiB for most SDKs by default) uses multipart uploads, and `s3:AbortMultipartUpload`in particular is needed for clients to clean up after failed transfers. Omitting it leaves orphaned multipart uploads accumulating in the bucket. - `s3:DeleteObject` is what separates "read-write" from "write-once / append-style" access. For ingest buckets where users should add data but never remove it, omit `s3:DeleteObject` (and `s3:DeleteObjectVersion`) and keep the rest of the RW set. ### Write-only (WO) — the "drop box" pattern For ingest endpoints where users deposit data but must never see what else the bucket contains: log shippers, backup targets, per-customer upload inboxes, data submission from semi-trusted parties. ```plaintext s3:PutObject s3:AbortMultipartUpload s3:ListMultipartUploadParts ``` Notes: - The two multipart permissions are still required. Clients uploading anything above their multipart threshold need them, and they only expose parts of the grantee's *own in-flight uploads*; they do not reveal completed objects or other users data. `CompleteMultipartUpload` itself is authorized by `s3:PutObject`. - `s3:ListBucketMultipartUploads` (a bucket-level action) is deliberately **omitted**: it enumerates all in-flight uploads on the bucket, which leaks key names from other writers. Most clients do not need it for uploads; grant it only if a specific client fails without it. - **No** `s3:ListBucket` **and no** `s3:GetObject` is the point of this grant — but be aware of the operational consequences: - `aws s3 cp` works; `aws s3 sync` does **not** (sync requires list to compute the delta). Tools that "verify" an upload by reading it back will also fail. Set client expectations accordingly. - Without `s3:GetObject`, writers cannot confirm their own uploads landed beyond the HTTP 200 on the PUT. For most ingest pipelines the 200 is sufficient (adding checksums is a good idea for later). - **Overwrite is silent.** `s3:PutObject` permits overwriting existing keys, and a write-only user cannot list or read to discover collisions — two writers using the same key clobber each other invisibly. Mitigations, in order of preference: - Use **conditional writes** (`If-None-Match: *` on PUT) so a PUT to an existing key fails instead of overwriting — VAST supports S3 conditional operations. *(Verify availability on the target release.)* - Enforce a key-naming convention with per-writer prefixes, and optionally scope the `Resource` to that prefix: `arn:aws:s3:::ingest-bucket/${writer-prefix}/*`. - Enable versioning on the bucket so overwrites are recoverable by an administrator. ### Deliberately excluded from all grants These actions hand the grantee control over the bucket itself and should remain with administrators: ```plaintext s3:PutBucketPolicy s3:GetBucketPolicy s3:DeleteBucketPolicy s3:PutBucketAcl s3:PutObjectAcl s3:DeleteBucket s3:PutBucketVersioning s3:PutLifecycleConfiguration s3:PutBucketCORS s3:PutBucketNotification s3:PutBucketTagging ``` In particular, granting `s3:PutBucketPolicy` to a non-admin user allows that user to rewrite the policy and grant themselves (or others) anything — it is equivalent to making them an administrator of the bucket. ## Understanding Principal Syntax on VAST Before deploying bucket policies, it is critical to understand how the VAST policy engine evaluates different `Principal` formats. ### The Wildcard Principal (`"Principal": "*"`) In VAST, the wildcard `"*"` denotes **all principals**. However, because VAST requires signed requests via access keys by default, this wildcard actually resolves to **all authenticated tenant users**, rather than the public internet. This makes it a safe and efficient way to apply tenant-wide access rules, such as granting baseline access to a shared resource: ```plaintext {  "Sid": "GrantFullAccessToTenantUsers",  "Effect": "Allow",  "Principal": "*",  "Action": "s3:*",  "Resource": [    "arn:aws:s3:::bucketa",    "arn:aws:s3:::bucketa/*"  ] } ``` > **The Anonymous Exception:** If anonymous access is explicitly enabled on the underlying S3 view, a policy using `"Principal": "*"` combined with `s3:GetObject` **will** expose data to unauthenticated public requests (e.g., public web browsing). Because this requires a deliberate two-step configuration (the view setting plus the bucket policy), treat any policy combining these two factors as entirely public and review it strictly. ### Named Users and Groups When scoping policies to specific users or groups, VAST uses distinct `"User"` and `"Group"` blocks rather than standard AWS syntax: - **Specific User:** `"Principal": { "User": "username" }` - **Specific Group:** `"Principal": { "Group": "groupname" }` **Domain Qualification Rule:** If your tenant uses multiple authentication or authorization providers (such as Active Directory alongside local users), you **must** append the domain name (e.g., `"username@domainname"`). If a user or group exists on an Active Directory provider and the domain is omitted, VAST Cluster will automatically attempt to match it against the joined domain. For purely local setups, the `@domain` suffix can be safely omitted. ### Recommended Testing Pattern To verify that your principal forms are resolving correctly before moving to production, use a simple **two-user, two-bucket validation pattern**: 1. Apply the candidate policy to a test bucket owned by User A. 2. Attempt an action using the designated target principal (User B) to confirm access is granted. 3. Attempt the same action using a completely different authenticated tenant user (User C). If User C is unexpectedly allowed access, the principal block failed to match a specific identity and the engine may be defaulting to a broader tenant-wide grant. Catching this mismatch in a test environment prevents accidental data exposure. ## VAST-Specific Controls (Outside of Bucket Policies) While bucket policies are ideal for managing granular read, write, delete, and list operations within an existing bucket, certain administrative workflows live entirely at the VAST View/View Policy layer (via the VMS) and cannot be controlled via bucket JSON. - **Bucket Visibility (**`ListAllMyBuckets`**):** Controlling whether a bucket appears when a user runs `aws s3 ls` is governed by the `s3_visibility_groups` setting in the VAST view policy. Because `ListAllMyBuckets` is a global service action rather than a bucket-scoped action, a bucket policy cannot grant or deny it. Users excluded from visibility groups can still read/write to their buckets if they know the name, but they won't see them enumerated. This provides an excellent layer of isolation for multi-tenant or per-customer workflows. - **Bucket Creation (**`CreateBucket`**):** A bucket policy cannot govern its own creation because the policy context doesn't exist until the bucket does. Consequently, bucket creation is managed via `bucket_creators_groups` on the S3 endpoint view, paired with the user-level `allow_create_bucket` privilege. Combining these VAST-native view controls with standard bucket policies provides a secure, end-to-end data provisioning pipeline. ## Example policies The examples below grant access to the groups `proj-alpha-ro` (read-only), `proj-alpha-rw` (read-write), and `proj-alpha-ingest` (write-only) on bucket `proj-alpha`. Note the `Resource` split: bucket-level actions (`s3:ListBucket`, `s3:GetBucketLocation`, `s3:ListBucketMultipartUploads`) target the bucket ARN, while object-level actions target `bucket/*`. ### RO policy statement ```plaintext {  "Version": "2012-10-17",  "Statement": [    {      "Sid": "ProjAlphaReadOnly",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-ro"] },      "Action": [        "s3:GetObject"      ],      "Resource": "arn:aws:s3:::proj-alpha/*"    },    {      "Sid": "ProjAlphaReadOnlyBucket",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-ro"] },      "Action": [        "s3:ListBucket",        "s3:GetBucketLocation"      ],      "Resource": "arn:aws:s3:::proj-alpha"    }  ] } ``` ### Example policies ```plaintext {  "Version": "2012-10-17",  "Statement": [    {      "Sid": "ProjAlphaReadWrite",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-rw"] },      "Action": [        "s3:GetObject",        "s3:PutObject",        "s3:DeleteObject",        "s3:AbortMultipartUpload",        "s3:ListMultipartUploadParts"      ],      "Resource": "arn:aws:s3:::proj-alpha/*"    },    {      "Sid": "ProjAlphaReadWriteBucket",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-rw"] },      "Action": [        "s3:ListBucket",        "s3:GetBucketLocation",        "s3:ListBucketMultipartUploads"      ],      "Resource": "arn:aws:s3:::proj-alpha"    }  ] } ``` ### WO policy statement ```plaintext {  "Version": "2012-10-17",  "Statement": [    {      "Sid": "ProjAlphaWriteOnly",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-ingest"] },      "Action": [        "s3:PutObject",        "s3:AbortMultipartUpload",        "s3:ListMultipartUploadParts"      ],      "Resource": "arn:aws:s3:::proj-alpha/inbox/*"    }  ] } ``` Note the `Resource` is scoped to the `inbox/` prefix rather than the whole bucket — recommended (though not required) for write-only grants, so ingest traffic lands in a known location and per-writer prefixes can be carved out later without policy redesign. There is no bucket-ARN statement at all: a write-only principal needs no bucket-level actions. All three statements can live in the same policy on the same bucket — one statement block per access tier. ## Advanced example: one bucket, per-prefix tiers, one policy A common consolidation of the pattern above: instead of a bucket per tier, use a single bucket (`proj`) with a prefix per access tier, and one bucket policy covering all of it: ```plaintext proj/ ├── alpha-ro/    ← group proj-alpha-ro:  read-only ├── alpha-rw/    ← group proj-alpha-rw:  read-write └── alpha-wo/    ← group proj-alpha-wo:  write-only (drop box) ``` Object-level actions scope naturally — the `Resource` just gains the prefix (`arn:aws:s3:::proj/alpha-ro/*`). The tricky part is **listing**: `s3:ListBucket` is a *bucket-level* action, so its `Resource` must be the bucket ARN, not a prefix. To restrict what a principal can list, the grant must carry a `Condition` on the `s3:prefix` key. This is the one place in this KB where conditions are unavoidable. ```plaintext {  "Version": "2012-10-17",  "Statement": [    {      "Sid": "AlphaRoObjects",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-ro"] },      "Action": ["s3:GetObject"],      "Resource": "arn:aws:s3:::proj/alpha-ro/*"    },    {      "Sid": "AlphaRoList",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-ro"] },      "Action": ["s3:ListBucket"],      "Resource": "arn:aws:s3:::proj",      "Condition": {        "StringLike": { "s3:prefix": ["alpha-ro/*"] }      }    },    {      "Sid": "AlphaRwObjects",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-rw"] },      "Action": [        "s3:GetObject",        "s3:PutObject",        "s3:DeleteObject",        "s3:AbortMultipartUpload",        "s3:ListMultipartUploadParts"      ],      "Resource": "arn:aws:s3:::proj/alpha-rw/*"    },    {      "Sid": "AlphaRwList",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-rw"] },      "Action": [        "s3:ListBucket",        "s3:ListBucketMultipartUploads"      ],      "Resource": "arn:aws:s3:::proj",      "Condition": {        "StringLike": { "s3:prefix": ["alpha-rw/*"] }      }    },    {      "Sid": "AlphaWoObjects",      "Effect": "Allow",      "Principal": { "AWS": ["proj-alpha-wo"] },      "Action": [        "s3:PutObject",        "s3:AbortMultipartUpload",        "s3:ListMultipartUploadParts"      ],      "Resource": "arn:aws:s3:::proj/alpha-wo/*"    },    {      "Sid": "AlphaBucketLocation",      "Effect": "Allow",      "Principal": {        "AWS": ["proj-alpha-ro", "proj-alpha-rw"]      },      "Action": ["s3:GetBucketLocation"],      "Resource": "arn:aws:s3:::proj"    }  ] } ``` ### How to read this policy **Six statements, three concerns.** Each tier gets an object-level statement scoped to its prefix; the tiers that can list get a bucket-level statement with a prefix condition; and `s3:GetBucketLocation` is granted once to the listing tiers (it has no prefix concept, so it's all-or-nothing). **The prefix condition changes client behavior in a visible way.** `aws s3 ls s3://proj/alpha-ro/` works for the RO group because the client sends `prefix=alpha-ro/`, which satisfies the condition. But `aws s3 ls s3://proj/` — listing the bucket root, no prefix — is **denied** for everyone in this policy, by design. Users must address their prefix directly. Document this for end users or it will generate tickets. (To allow root listing of just the top-level prefix names, add `""` and bare prefix names like `"alpha-ro"` to the `s3:prefix` list with `"s3:delimiter": "/"` — but that lets each group see the other prefixes *exist*, so decide whether that's acceptable.) **The WO prefix has no list statement at all** — consistent with the drop-box semantics from the write-only section. The WO group's multipart parts listing (`s3:ListMultipartUploadParts`) is object-level and rides on the object statement. `s3:ListBucketMultipartUploads` **carries the same prefix condition** for the RW group, so in-flight uploads in other prefixes aren't enumerable. *(Verify the* `s3:prefix` *condition key is honored on both* `s3:ListBucket` *and* `s3:ListBucketMultipartUploads` *for the target VAST release — this is the single most important lab check before deploying this pattern.)* ## Advanced example variant: three tiers, one shared prefix If RO/RW/WO are meant to be different access levels to the **same data** rather than three separate areas, keep the policy above but point every object statement's `Resource` at `arn:aws:s3:::proj/alpha/*` and every `s3:prefix` condition at `alpha/*`. The WO group then drops files into the same tree the RO group reads — the classic ingest-then-consume pipeline — and the overwrite-mitigation guidance from the write-only section (conditional writes, per-writer sub-prefixes, versioning) becomes essential rather than optional. ## Best-practice recommendations **Grant to groups, not users.** When buckets are provisioned per end user or per project over the life of the cluster, populate `Principal` with groups (e.g., `proj-alpha-ro`, `proj-alpha-rw`) and manage membership in your **identity provider** (ldap or other provider). Policies then stay stable as people join and leave; the alternative — enumerating users in every bucket policy — turns every personnel change into a policy edit across buckets. **Enumerate actions explicitly; avoid wildcards.** Prefer the explicit lists above over patterns like `s3:Get*` or `s3:*`. Explicit lists are self-documenting, audit cleanly, and do not silently broaden when new actions become supported in a later release. `s3:*` in particular grants bucket-management actions and must never appear in a non-admin grant. **Standardize the tiers as templates.** Since the plan is to add buckets continuously, treat the RO, RW, and WO statement blocks as fixed templates where only the bucket name and group names change per request. This keeps every bucket's policy reviewable at a glance and makes drift obvious. ## Tooling: managing policies with tooling When buckets are added regularly, the operational burden is not writing the first policy — it's viewing, editing, and applying policies across a growing set of buckets without mistakes. The default workflow (`aws s3api get-bucket-policy`, edit JSON in an external editor, `put-bucket-policy`) offers no validation and no undo. VAST has an internal terminal-based bucket policy manager, available on request through your VAST account team, built for exactly this workflow against VAST S3 endpoints: - **Three-pane layout** — profiles, buckets, and a policy editor in one screen: pick a cluster profile, pick a bucket, and the current policy loads ready to edit. - **Live validation** — JSON syntax errors with line:column, plus structural policy checks (`Version`, `Statement`, `Effect`, `Action`, `Resource`, `Principal`, `Condition`) before you save — catching malformed-policy mistakes at edit time instead of at `AccessDenied` time. - **Automatic backups** — every save and delete snapshots the before/after policy state to timestamped local files, so any change can be reviewed or rolled back. A paper trail comes free with every edit. - **AWS shared-config aware** — reads `~/.aws/config` and `~/.aws/credentials`, including `endpoint_url` entries for VAST clusters, so existing profiles work as-is. - **Editor of your choice** — edit inline in the TUI or shell out to `$EDITOR`. `┌─Profiles────┐ ┌─Buckets (3)─┐ ┌─Policy: bucket-x -- NORMAL -- ────────────────┐` `│ • var204 │ │ bucket-x │ │ { │` `│ default │ │ bucket-y │ │ "Version": "2012-10-17", │` `│ prod │ │ bucket-z │ │ "Statement": [...] │` `│ │ │ │ │ } │` `│ │ │ │ │ ✓ policy looks valid │` `└─────────────┘ └─────────────┘ └────────────────────────────────────────────────┘` ` ● connected · var204 TLS-skip ON <status messages here>` ` i edit · e $EDITOR · f format · s save · d delete · r reload · ← back · q quit` For the per-request bucket provisioning pattern described above, the workflow becomes: connect to the cluster profile, select the new bucket, paste the appropriate RO/RW/WO template, adjust the group names, confirm validation passes, and save. The pre-change backup will be written automatically. Contact your VAST account team or SE if you’d like to try it. ## Related documentation - VAST documentation on KB: [*Managing S3 Bucket Policies*](/docs-5-5/docs/managing-s3-access) - AWS reference for action semantics: [Actions, resources, and condition keys for Amazon S3](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html)