Cloud Storage IAM vs ACLs: What to Use and Why

Cloud Storage supports two access control systems: Cloud IAM and legacy ACLs. The decision between them is not a close call. IAM with uniform bucket-level access is the correct model for every new bucket. ACLs are a legacy mechanism you will encounter in older environments, and understanding them matters mainly so you can migrate away from them safely.

The short version

If you are new to Cloud Storage permissions, here is what you need to know:

  • IAM controls who can access a bucket by granting roles at the bucket or project level. A role applies to every object in the bucket. It integrates with all GCP tooling and is the current standard.
  • ACLs are an older system that attaches a separate permission list to individual objects. They predate Cloud IAM and have mostly been superseded.
  • The risk: Both systems can be active at the same time on older buckets. If either grants access, the request succeeds. An object can be publicly readable via a forgotten ACL even if the bucket’s IAM policy is completely locked down.
  • The fix: Enable uniform bucket-level access on every new bucket. This disables ACL evaluation entirely and makes IAM the only access control mechanism.

What IAM is in Cloud Storage

Cloud IAM is the standard access control system across GCP. In Cloud Storage, you grant an IAM role to an identity on a bucket. That role applies to every object in the bucket, consistently and auditably.

You can also grant roles at the project level, which applies to all buckets in the project. For most application service accounts, bucket-level grants are the right choice. A compromised credential can then only access the buckets it was explicitly granted. See least-privilege IAM for guidance on scoping grants correctly.

IAM integrates natively with Cloud Audit Logs, Organisation Policies, and IAM Conditions. You can review who has access to a bucket in one policy view, which is a core advantage over ACLs. For a deeper look at how IAM roles work across GCP, see GCP IAM Roles Explained.

Mental model

Think of an IAM role like a job title. When you give someone the title “warehouse picker” at a distribution centre, that role defines exactly what they can do: pick items, not drive forklifts, not access the office. In Cloud Storage, granting roles/storage.objectViewer on a bucket works the same way. The identity can read objects. Nothing else. You check the role list once and know exactly what everyone can do.

Common IAM roles for Cloud Storage:

  • roles/storage.objectViewer — read objects only
  • roles/storage.objectCreator — upload objects; cannot read or delete
  • roles/storage.objectAdmin — full object management; no bucket configuration access
  • roles/storage.admin — full control including bucket configuration; reserve for infrastructure automation only
# Grant objectViewer on a bucket to a service account
gcloud storage buckets add-iam-policy-binding gs://my-app-data \
  --member="serviceAccount:reader@my-project.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

# View all IAM bindings on a bucket
gcloud storage buckets get-iam-policy gs://my-app-data

What ACLs are in Cloud Storage

ACLs (Access Control Lists) predate the current Cloud IAM system. An ACL is a list attached to a specific bucket or object. Each entry specifies a scope (who) and one of three fixed permission levels: READER, WRITER, or OWNER.

Unlike IAM, ACLs can be set on individual objects. Two objects in the same bucket can have completely different ACLs. This was useful before better patterns existed. Today it is the source of most Cloud Storage security incidents involving unexpected public exposure, because an object-level ACL can grant access independently of the bucket’s IAM policy.

ACL scope types:

  • user:{email} — a specific Google account
  • group:{email} — a Google Group
  • serviceAccount:{email} — a service account
  • allUsers — anyone on the internet, no authentication required
  • allAuthenticatedUsers — any Google account in the world, not just your organisation
  • domain:{domain} — all users in a Google Workspace domain
Easy to misread

allAuthenticatedUsers sounds like it means users authenticated to your application or your organisation. It does not. It means any Google account anywhere in the world. Setting this scope on an object effectively makes your data accessible to hundreds of millions of people. Almost no legitimate use case needs it.

How access works when IAM and ACLs both exist

Cloud Storage calls the dual-system model fine-grained access. When fine-grained access is active, both IAM and ACLs are evaluated for every request. If either grants access, the request succeeds.

In practice this means:

  • An object with an allUsers: READER ACL is publicly readable, even if the bucket’s IAM policy is fully private
  • An identity can have access to a specific object through an ACL even with no IAM binding on the bucket at all
  • A complete security audit must check both the IAM policy and every object’s ACL, which is impractical in any bucket with significant object counts
The real risk

Most Cloud Storage data exposure incidents in older GCP environments trace back to ACLs that nobody remembered setting. The bucket’s IAM policy looks fine. The console shows no public access. But a single object has an allUsers: READER ACL from an old script or third-party integration, and that object has been publicly readable for months. Fine-grained access makes this class of mistake possible. Uniform bucket-level access makes it impossible.

# Check whether a bucket uses fine-grained (ACL) or uniform access
gcloud storage buckets describe gs://my-legacy-bucket \
  --format="value(iamConfiguration.uniformBucketLevelAccess.enabled)"
# Returns "True" if uniform access is on; empty or False if fine-grained is active

# View the ACL on a specific object
gsutil acl get gs://my-legacy-bucket/some-object.txt

# Audit ACLs across a bucket before migrating
gsutil ls -L gs://my-legacy-bucket/ > bucket-acl-audit.txt

IAM vs ACLs: comparison

DimensionIAMACLs
ScopeBucket or project levelIndividual bucket or object
GranularityNamed roles with many permissionsThree fixed levels: READER, WRITER, OWNER
AuditabilitySingle IAM policy view; integrates with Cloud Audit LogsMust check each object individually; no central view
ConsistencyOne policy applies to all objects in a bucketEach object can have a different ACL
Risk of accidental exposureLow — one policy, one place to reviewHigher — any object can have an ACL that bypasses the bucket policy
Toolinggcloud, Terraform, org policies, consoleMainly gsutil; some newer APIs do not support ACL management
Temporary accessUse signed URLs; IAM itself has no time-limited object grantsACLs are persistent, not time-limited
Modern recommendationYes — use IAM with uniform bucket-level accessLegacy only — migrate away

When to use IAM

Use IAM for all new buckets, all production workloads, and any environment where security and auditability matter:

  • All new buckets: Enable uniform bucket-level access at creation. There is no legitimate reason to start a new bucket in fine-grained mode.
  • Application service accounts: Grant the minimum IAM role on the specific bucket the application needs. Never grant project-level storage admin to an application service account.
  • CI/CD pipelines: Use dedicated service accounts with bucket-level IAM roles. Separating the build account from the deploy account keeps the blast radius of a credential compromise small.
  • Public content: If a bucket should be publicly readable, grant roles/storage.objectViewer to allUsers via IAM on a dedicated public bucket. Enable uniform bucket-level access first. Do not use object ACLs to make individual files public.
  • Team environments: Grant IAM roles to groups, not individuals. Removing someone from the group revokes their access automatically.
Good default

If you are creating a new bucket right now, pass —uniform-bucket-level-access to the create command. That single flag means IAM is the only thing controlling access to every object in the bucket, from the moment it exists.

For the broader IAM policy design principles that apply across GCP, see IAM Policies.

When ACLs still appear

You will encounter ACLs most often in:

  • Older buckets: Buckets created before uniform bucket-level access became the default recommendation. Many legacy GCP environments still have fine-grained access active.
  • Third-party tools: Some older SDKs, backup tools, and data platforms set object ACLs as part of their upload workflow. Check the tool’s documentation before enabling uniform access on a bucket it manages.
  • Legacy static website patterns: Older guides for hosting static sites on Cloud Storage often set allUsers: READER ACLs on individual objects. The modern equivalent is a uniform-access bucket with an IAM binding for allUsers.
  • Default ACL configurations: Some legacy gsutil setups apply a default ACL to every new object uploaded to a bucket, without the uploader being aware of it.

When auditing a GCP environment, always check whether production buckets have uniform bucket-level access enabled. If they do not, treat the bucket as potentially having ACL-based grants that may not be documented anywhere.

Why uniform bucket-level access matters

Uniform bucket-level access solves the core problem of the dual-system model by disabling ACL evaluation entirely. Once enabled, Cloud Storage ignores all object ACLs. IAM is the only mechanism. You can read the bucket’s IAM policy and know exactly who has access to every object, with no hidden overrides and no exceptions.

Analogy

Imagine a building where some rooms use the central card access system and others have old-fashioned keys left on the door handle. The security team audits the card system carefully. But nobody keeps track of every door key. One day a contractor leaves a key on a door nobody visits. That room has been open to anyone who found the key for years. Uniform bucket-level access is what happens when you remove all the door keys and make the card system the only way in. One list, complete picture.

The 90-day lock-in is intentional. After 90 days, uniform access cannot be disabled. This prevents an administrator from temporarily turning it off “just to check something” and forgetting to re-enable it. Treat the lock-in as a feature, not a limitation.

Enable uniform bucket-level access at bucket creation whenever possible. Retroactively enabling it on legacy buckets requires auditing existing ACLs first. See the migration section below.

# Enable uniform bucket-level access at bucket creation
gcloud storage buckets create gs://my-app-data \
  --location=europe-west2 \
  --uniform-bucket-level-access

# Enable it on an existing bucket
gcloud storage buckets update gs://my-legacy-bucket \
  --uniform-bucket-level-access

# Confirm the setting
gcloud storage buckets describe gs://my-legacy-bucket \
  --format="value(iamConfiguration.uniformBucketLevelAccess.enabled)"
Note

You have 90 days after enabling uniform bucket-level access to revert if you discover a misconfiguration. After 90 days, the change is permanent. Audit existing ACLs and verify all applications still have the access they need before that window closes.

Common mistakes

  1. Auditing only the IAM policy and ignoring ACLs. On a bucket without uniform bucket-level access, the IAM policy shows only half the access picture. An object can be publicly readable through an ACL set by a script or third-party integration that is completely invisible in the IAM console view. Always check both when auditing legacy buckets.

  2. Enabling uniform bucket-level access without auditing first. If any application relies on ACL-based access rather than IAM, enabling uniform access breaks it immediately. Audit all ACLs, add equivalent IAM bindings, then enable uniform access. Verify that all services still work before the 90-day window closes.

  3. Confusing allAuthenticatedUsers with “only my users”. This scope means any authenticated Google account in the world, not users of your application or members of your organisation. Setting it on an object or IAM binding effectively makes your data accessible to any Google account holder. Almost no legitimate use case needs this scope.

  4. Using object ACLs for ad hoc sharing in production. Setting an ACL to share a file with a colleague or external partner seems quick, but it creates an access grant with no expiry, no audit trail, and no automatic cleanup. Use signed URLs for temporary sharing instead: they expire on their own and leave no permanent grants in your access model.

  5. Granting project-level storage roles to application service accounts. roles/storage.objectViewer at the project level gives read access to every bucket in the project. Grant roles at the bucket level so a compromised service account can only reach the buckets it legitimately needs.

Safer alternatives to object ACLs

If you find yourself reaching for an object ACL, there is almost always a better pattern:

  • Signed URLs for temporary access. To share a file with someone who has no GCP account, generate a signed URL instead. It expires automatically, is scoped to one object, and the recipient needs no IAM permissions. See Signed URLs Explained for how to generate and use them safely.

  • Separate public and private buckets. If some content must be public and other content must not, use separate buckets. Grant roles/storage.objectViewer to allUsers via IAM on the public bucket with uniform access enabled. Keep sensitive content in a separate bucket with no public access at all.

  • IAM roles on service accounts for application access. Applications should access Cloud Storage through service accounts with bucket-level IAM roles. This is auditable, consistent, and does not create object-level ACL grants that accumulate silently over time.

  • Public access prevention as a backstop. Enable public access prevention on buckets that should never be public. This blocks any attempt to add allUsers or allAuthenticatedUsers at either the IAM or ACL level. Enforce it organisation-wide using Organisation Policy for blanket coverage. Full configuration details are in Cloud Storage Security.

Quick rule of thumb

If you need to share access with someone outside your GCP project, the answer is almost always a signed URL, not an ACL. Signed URLs have an expiry. ACLs do not.

Real-world examples

Internal application uploading to a private bucket

A data pipeline writes processed files to a private Cloud Storage bucket. A separate reporting service reads from the same bucket. Neither should be able to do anything else.

Recommended model: Enable uniform bucket-level access on the bucket at creation. Grant roles/storage.objectCreator to the pipeline service account and roles/storage.objectViewer to the reporting service account, both at the bucket level. No ACLs, no project-level grants, no over-permissioning.

Serving static website assets publicly

A marketing site’s images, CSS, and JavaScript files live in Cloud Storage and must be publicly readable.

Recommended model: Create a dedicated public bucket with uniform bucket-level access enabled. Grant roles/storage.objectViewer to allUsers via IAM on that bucket only. Keep all other content in a separate private bucket. Do not mix public and private objects in the same bucket, and do not use object ACLs to make individual files public.

Sharing a download link with an external user

A user requests a specific file from a private bucket. Your application needs to give them a one-time download link without adding them to your IAM policy.

Recommended model: Generate a signed URL with a short expiry (15 minutes to 1 hour). The recipient needs no Google account. The URL expires automatically. Do not use an object ACL for this: ACLs have no expiry and leave permanent grants in your access model.

Migrating a legacy bucket to uniform access

If you have an existing bucket with fine-grained access, migrating to uniform bucket-level access is the right move:

  1. Audit all existing ACLs. Use gsutil ls -L gs://bucket-name/ to inspect object ACLs across the bucket and save the output.
  2. Identify identities that have access via ACLs but not via IAM. These are the grants that will break when uniform access is enabled.
  3. Add equivalent IAM bindings for all legitimate access. If an object was allUsers: READER for public hosting, decide whether the bucket should be public, then add an IAM binding at the bucket level if appropriate.
  4. Enable uniform bucket-level access and verify that all applications and services still function correctly.
  5. Monitor access for a few days. After 90 days, the change is permanent.
# Audit ACLs on a legacy bucket
gsutil ls -L gs://my-legacy-bucket/ > bucket-acl-audit.txt

# Check the default ACL applied automatically to new objects
gsutil defacl get gs://my-legacy-bucket

# Enable uniform bucket-level access
gcloud storage buckets update gs://my-legacy-bucket \
  --uniform-bucket-level-access

# Confirm it is active
gcloud storage buckets describe gs://my-legacy-bucket \
  --format="value(iamConfiguration.uniformBucketLevelAccess.enabled)"

Frequently asked questions

What is the difference between IAM and ACLs in Cloud Storage?

IAM controls access at the bucket level using roles. You grant a role to an identity and it applies to all objects in the bucket. ACLs are a legacy mechanism that can be set per object, with only three fixed access levels: READER, WRITER, and OWNER. IAM integrates with audit logs, organisation policies, and all GCP tooling. ACLs do not. For any new bucket, IAM with uniform bucket-level access is the correct choice.

Should I still use ACLs in Google Cloud Storage?

Almost certainly not for new work. ACLs are a legacy mechanism and Google recommends IAM with uniform bucket-level access for all new buckets. You will encounter ACLs when working with older buckets or third-party tools that set them automatically. In those cases, your goal should be to audit the existing ACLs and migrate to IAM.

Does uniform bucket-level access disable object ACLs?

Yes. When uniform bucket-level access is enabled, Cloud Storage stops evaluating object ACLs. Existing ACLs are preserved in storage for 90 days in case you need to revert, but they have no effect on access. After 90 days, the change becomes permanent and irreversible. IAM becomes the only access control mechanism on the bucket.

Can an object become public even if the bucket IAM policy is restrictive?

Yes, if the bucket does not have uniform bucket-level access enabled. In the legacy fine-grained model, Cloud Storage evaluates both IAM and ACLs. If an object ACL grants access to allUsers, that object is publicly readable regardless of how restrictive the bucket IAM policy is. This is the core risk of the dual-system model. Enable uniform bucket-level access to prevent it.

What should I use instead of object ACLs for temporary access?

Signed URLs. A signed URL grants time-limited access to a specific object with no Google account required. The recipient does not need to be in your IAM policy at all. Signed URLs expire automatically, are scoped to one object, and can be traced back to the service account that signed them. They are a much cleaner pattern than object ACLs for external or temporary sharing.

Last verified: 23 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.