Documentation Index

Fetch the complete documentation index at: https://kb.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)

Prev Next

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 which 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 user 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

The defining property of both grants is what they omit: no bucket-management actions of any kind. "Read-write" means read-write on objects, not administrative control of the bucket.

Read-only (RO)

s3:GetObject
s3:ListBucket
s3:GetBucketLocation

Optional 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 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:

s3:PutObject
s3:DeleteObject
s3:AbortMultipartUpload
s3:ListMultipartUploadParts
s3:ListBucketMultipartUploads

Optional 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 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:AbortMultipartUploadin 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.

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:

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:

{
  "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.

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

{
  "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 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.