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:
Read-only (RO) users who can list and download objects, nothing else
Read-write (RW) users which have full object control (upload, download, delete, multipart), but no control over the bucket itself
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 user over the life of the cluster, with a single policy per bucket whose Principal is populated with users and/or groups.
Summary
Recommended action sets
Read-only (RO)
s3:GetObject
s3:ListBucket
s3:GetBucketLocationOptional additions depending on the use case:
s3:GetObjectTagging # if apps read object tags
s3:GetObjectVersion # if versioning is enabled and users need
s3:ListBucketVersions # access to non-current versionsNotes:
s3:ListBucketis required for clients (s3cmd, AWS CLIs3 ls, most SDK tools) to list objects. Grantings3:GetObjectalone 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:GetBucketLocationis a quality-of-life call. Some SDKs and tools call it during client initialization, and omitting it produces confusingAccessDeniederrors before any data operation runs.
Read-write (RW)
Everything in the RO set, plus:
s3:PutObject
s3:DeleteObject
s3:AbortMultipartUpload
s3:ListMultipartUploadParts
s3:ListBucketMultipartUploadsOptional additions depending on the use case:
s3:PutObjectTagging # if applications write object tags
s3:DeleteObjectTagging
s3:DeleteObjectVersion # if versioning is enabled and users may
# remove specific versionsNotes:
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:AbortMultipartUploadin particular is needed for clients to clean up after failed transfers. Omitting it leaves orphaned multipart uploads accumulating in the bucket.s3:DeleteObjectis what separates "read-write" from "write-once / append-style" access. For ingest buckets where users should add data but never remove it, omits3:DeleteObject(ands3: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.
s3:PutObject
s3:AbortMultipartUpload
s3:ListMultipartUploadPartsNotes:
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.
CompleteMultipartUploaditself is authorized bys3: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:ListBucketand nos3:GetObjectis the point of this grant — but be aware of the operational consequences:aws s3 cpworks;aws s3 syncdoes 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:PutObjectpermits 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
Resourceto 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:
s3:PutBucketPolicy
s3:GetBucketPolicy
s3:DeleteBucketPolicy
s3:PutBucketAcl
s3:PutObjectAcl
s3:DeleteBucket
s3:PutBucketVersioning
s3:PutLifecycleConfiguration
s3:PutBucketCORS
s3:PutBucketNotification
s3:PutBucketTaggingIn 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:
{
"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 withs3:GetObjectwill 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:
Apply the candidate policy to a test bucket owned by User A.
Attempt an action using the designated target principal (User B) to confirm access is granted.
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 runsaws s3 lsis governed by thes3_visibility_groupssetting in the VAST view policy. BecauseListAllMyBucketsis 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 viabucket_creators_groupson the S3 endpoint view, paired with the user-levelallow_create_bucketprivilege.
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
{
"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
{
"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
{
"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:
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.
{
"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 atAccessDeniedtime.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/configand~/.aws/credentials, includingendpoint_urlentries 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
AWS reference for action semantics: Actions, resources, and condition keys for Amazon S3