Cloud Storage Security: Secure GCP Buckets with IAM, Encryption, and Best Practices
Cloud Storage buckets hold some of the most sensitive data in a GCP environment: database backups, user uploads, compliance records, build artefacts, and internal reports. A new bucket is private and encrypted by default, but that is the starting point, not the destination. Securing a bucket properly means configuring access control, preventing accidental public exposure, choosing the right encryption model, and setting up logging. This page explains how all of those controls fit together.
What Cloud Storage security actually means
A bucket is not secure just because it exists. When you create a new Cloud Storage bucket, it starts out private and encrypted. Those two facts give many beginners the impression that the job is done. It is not.
The storage unit analogy
Think of a bucket like a self-storage unit. When you rent it, the door locks automatically and only you have a key. That is “private by default.” But locking the door is the starting point, not the finished product. You still need to decide who else gets a key and what they can do, whether to add a second lock for the most sensitive boxes inside, whether to install a security camera, and whether to put a sign on the gate saying no outside visitors without prior approval. Uniform access, least-privilege IAM, public access prevention, and audit logging are those decisions. The automatic lock solves one thing; everything else requires your deliberate choices.
Real security comes from four things working together:
Access control that is easy to reason about. If you do not enable uniform bucket-level access, individual objects can have their own ACLs that grant access completely independently of your IAM policy. Your bucket can look locked down in the IAM console while specific objects are readable by anyone.
Guardrails against human error. The single most common cause of a storage breach is not a sophisticated attack. It is someone accidentally making a bucket public, or granting a service account more access than it needs. Controls like public access prevention and Organisation Policies exist precisely to prevent this class of mistake.
Deliberate encryption choices. The default encryption is good. For regulated data or situations where you need to control the key lifecycle, you need to understand CMEK and decide whether it applies to your situation.
Logging where it matters. Admin Activity logs are on by default and free. Data Access logs are off by default and expensive. You need to understand which buckets require object-level audit trails and enable logging deliberately.
Understood together, Cloud Storage security is about removing ambiguity from access control, adding guardrails against misconfiguration, and building an audit trail proportionate to the sensitivity of the data.
How Cloud Storage security works
Cloud Storage security is a set of layered controls. Each layer addresses a different threat. Understanding each one helps you decide which to apply and in what order.
Private by default
Every new bucket is private. No one outside your project can access it without a valid GCP identity and an IAM binding granting them a role on the bucket or the project. There is no extra step required to keep a bucket private. The risk comes when you actively grant access, either intentionally or by mistake.
IAM and identities
Access to Cloud Storage is controlled through Cloud IAM. You grant a role to an identity (a service account, a user account, or a group) either at the bucket level or at the project level. Project-level grants apply to every bucket in the project, which is why they require careful thought. Bucket-level grants are narrower and almost always the right choice for application service accounts.
The key IAM principle to follow is least privilege: grant the minimum role on the most specific resource needed for the task.
Legacy ACLs vs modern IAM-only access
Cloud Storage supports two access control models. The legacy model (called fine-grained access) evaluates both IAM and per-object ACLs in parallel. The modern model, enabled by turning on uniform bucket-level access, disables ACLs entirely and makes IAM the only control.
On a bucket without uniform bucket-level access, an individual object can
be publicly readable even when the bucket IAM policy is entirely private.
A developer runs a script that uploads a file and sets its ACL to
allUsers. The IAM policy still says private. The object is
now public. You would not see this by looking at the bucket in the
console. You would only find it by inspecting that specific object’s
ACL directly. Uniform bucket-level access removes this entire class of
problem.
The legacy model exists for backwards compatibility. If you are building anything new, use uniform bucket-level access from the start. See the Cloud Storage IAM vs ACLs page for a detailed comparison of both models and how to migrate existing buckets.
Public access prevention
Public access prevention is a bucket setting that blocks any IAM binding
granting access to allUsers or allAuthenticatedUsers.
It is a hard guardrail. Even if someone with the right permissions tries to
make the bucket public, the setting prevents it. For buckets that should
never be public, this is a simple and important control.
Encryption at rest and in transit
All data stored in Cloud Storage is encrypted at rest using AES-256. All data in transit is encrypted using TLS. Both of these are on by default and cannot be disabled. You do not need to configure anything to get encryption; it happens automatically.
Customer-managed encryption keys (CMEK)
By default, the encryption keys are managed by Google. If you need to control the key lifecycle for regulatory reasons, contractual requirements, or because you want the ability to cut off access by revoking the key, you can configure a bucket to use a key from Cloud KMS that you manage. This is called CMEK. It adds operational complexity and should be applied deliberately, not by default.
Logging and monitoring
Cloud Storage integrates with Cloud Audit Logs to record two categories of events. Admin Activity logs capture bucket-level changes (creating a bucket, modifying IAM policy, changing lifecycle configuration). These are always on and always free. Data Access logs capture individual object reads and writes. These are off by default and can generate very high volumes of data.
VPC Service Controls
VPC Service Controls lets you define a security perimeter around GCP services including Cloud Storage. Access to buckets inside the perimeter is blocked for requests originating outside it, regardless of IAM. This is primarily a data exfiltration control: it prevents a compromised identity from copying data to a bucket in an external project or organisation.
These controls are not all-or-nothing. Knowing when each one applies and which to prioritise for your situation is what the rest of this page covers.
Uniform bucket-level access
Uniform bucket-level access is the single most important configuration change you can make to a new bucket. It disables legacy ACLs so that IAM is the only way to grant or deny access. Without it, individual objects can have ACLs that override the bucket IAM policy in ways that are difficult to audit.
Enable it at bucket creation. It becomes permanent after 90 days, which is a feature: it prevents temporary disables that could expose objects through ACLs.
# Enable uniform bucket-level access at bucket creation
gcloud storage buckets create gs://my-app-secure-data \
--location=europe-west2 \
--uniform-bucket-level-access
# Enable it on an existing bucket
gcloud storage buckets update gs://my-app-existing-bucket \
--uniform-bucket-level-access
# Verify the setting
gcloud storage buckets describe gs://my-app-secure-data \
--format="value(iamConfiguration.uniformBucketLevelAccess.enabled)"Once uniform bucket-level access has been enabled for 90 days, it cannot be disabled. This is intentional. The 90-day window gives you time to catch problems and revert; after that, the simpler access model is locked in. If you are enabling it on an existing bucket, audit the existing ACLs first and add equivalent IAM bindings before enabling. See Cloud Storage IAM vs ACLs for the migration process.
Least-privilege IAM for Cloud Storage
The principle of least privilege means granting the minimum role on the most specific resource needed. For Cloud Storage, that means:
- Grant roles at the bucket level, not the project level. A project-level grant applies to every bucket in the project.
- Choose the narrowest role that covers the actual need.
- Separate read and write identities where practical.
The standard roles and when to use each:
roles/storage.objectViewer— read-only access to objects, no bucket configuration accessroles/storage.objectCreator— upload access only; cannot read existing objects or deleteroles/storage.objectAdmin— full object management; cannot change bucket configuration or IAM policyroles/storage.admin— full control including bucket configuration; for infrastructure automation only, never for application service accounts
A service account that uploads log files to a bucket should have
roles/storage.objectCreator on that specific bucket, not
roles/storage.admin on the project. A compromised upload worker
should not be able to read the data it just wrote, let alone access other
buckets. For the broader principles behind this, see
Principle of Least Privilege.
# Grant objectViewer on a specific 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"
# Grant objectCreator on a specific bucket (upload-only)
gcloud storage buckets add-iam-policy-binding gs://my-app-uploads \
--member="serviceAccount:uploader@my-project.iam.gserviceaccount.com" \
--role="roles/storage.objectCreator"
# View all IAM bindings on a bucket
gcloud storage buckets get-iam-policy gs://my-app-dataPublic access prevention
Public access prevention blocks any IAM binding that would grant access to
allUsers or allAuthenticatedUsers. It applies at
the bucket level and acts as a hard guardrail. Even an identity with full
admin on the bucket cannot make it public while this setting is active.
Enable it on every bucket that should never serve public content. For
organisation-wide coverage, enforce the Organisation Policy
constraints/storage.publicAccessPrevention. This applies
public access prevention to all buckets across your GCP organisation,
regardless of individual bucket configuration, and means it cannot be
overridden by anyone working within the organisation.
# Enable public access prevention on a specific bucket
gcloud storage buckets update gs://my-app-sensitive-data \
--public-access-prevention
# Check the current setting
gcloud storage buckets describe gs://my-app-sensitive-data \
--format="value(iamConfiguration.publicAccessPrevention)"Enforcing constraints/storage.publicAccessPrevention at the
organisation level is the strongest protection. It means that even if a
bucket is created without the setting enabled, any attempt to make it
public is blocked at the Organisation Policy level. Apply it as a default
across your organisation and create exceptions only for buckets that
genuinely need to serve public content.
VPC Service Controls
VPC Service Controls addresses a different threat than IAM and public access prevention. IAM controls who can access a bucket. VPC Service Controls controls where requests can come from, regardless of the caller’s identity.
A Service Controls perimeter wraps one or more GCP projects and services. Cloud Storage requests that originate from outside the perimeter are blocked, even if the caller holds a valid IAM binding. This prevents a scenario where a compromised credential is used from outside the perimeter to exfiltrate data to a bucket in another project or organisation.
VPC Service Controls is appropriate for environments handling sensitive or regulated data where you need both identity-based and network-based access restrictions. For most applications, correct IAM and public access prevention are sufficient. VPC Service Controls is the additional layer for high-security or compliance-driven environments.
Customer-managed encryption keys
By default, Cloud Storage encrypts your data with Google-managed AES-256 keys. You do not configure this and cannot disable it. For most workloads, this is entirely sufficient.
Customer-managed encryption keys (CMEK) let you supply a key from Cloud KMS instead. When you configure a CMEK on a bucket, all objects written to it are encrypted using your key. The advantage is control: you can audit key use in Cloud KMS logs, rotate the key, and revoke access to it. If you revoke the key, Cloud Storage can no longer decrypt those objects.
Some regulatory frameworks, including certain HIPAA and financial compliance requirements, mandate that you control the encryption key lifecycle. If that applies to your situation, CMEK is the right choice. If it does not, the operational complexity is not worth the addition. See the comparison table below.
# Configure a bucket to use a CMEK key for all new objects
gcloud storage buckets update gs://my-app-regulated-data \
--default-encryption-key=projects/my-project/locations/europe-west2/keyRings/my-ring/cryptoKeys/my-key
# Verify the CMEK configuration
gcloud storage buckets describe gs://my-app-regulated-data \
--format="value(encryption)"The Cloud Storage service agent for your project must have
roles/cloudkms.cryptoKeyEncrypterDecrypter on the KMS key
before you configure the bucket to use it. If you skip this step, every
object write to that bucket will fail with a permission error. Grant the
role, verify it, then update the bucket. See
Cloud KMS Overview
for how to set up a key ring and key.
Google-managed encryption vs CMEK
| Aspect | Google-managed encryption | CMEK |
|---|---|---|
| Setup required | None — automatic | Yes — Cloud KMS key, service agent permissions |
| Key rotation | Managed by Google | You control rotation schedule |
| Key audit logs | Not available to you | Every key use logged in Cloud KMS |
| Ability to revoke access | No | Yes — revoke key to make data unreadable |
| Operational risk | Low | Higher — misconfigured key permissions break writes |
| Compliance use cases | Most workloads | Regulated data, contractual key control requirements |
Audit logging
Cloud Storage integrates with Cloud Audit Logs and emits two categories of log:
Admin Activity logs. Record bucket-level operations: creating a bucket, changing an IAM policy, modifying lifecycle configuration, enabling or disabling uniform bucket-level access. These are always enabled, free, and cannot be turned off. They cover the configuration plane.
Data Access logs. Record individual object reads, writes, and deletions. These are off by default. When enabled, they generate very high log volumes for active buckets and incur cost. Enable them only for buckets where object-level auditing is required by policy or regulation.
For full details on how Cloud Audit Logs works across GCP services, see Cloud Audit Logs.
# View recent Admin Activity logs for Cloud Storage in a project
gcloud logging read \
'protoPayload.serviceName="storage.googleapis.com" AND protoPayload.methodName=~"buckets\."' \
--project=my-project \
--limit=20 \
--format=jsonEnabling Data Access logs for Cloud Storage on a busy bucket can produce millions of log entries per day. These are ingested into Cloud Logging and billed at the standard log ingestion rate. Scope carefully: enable them for specific high-sensitivity buckets, not project-wide by default.
Cloud Storage IAM vs ACLs
Cloud Storage originally used Access Control Lists (ACLs) to control access to individual objects. Each object could have its own ACL granting different levels of access to different identities. Cloud IAM was added later, and for a time both systems ran in parallel.
ACLs are now considered legacy. The problems with them are practical: in a bucket without uniform bucket-level access, you must inspect both the IAM policy and every object’s ACL to understand who can access what. An object can be publicly readable via its ACL even if the bucket IAM policy is entirely private. This is the core reason uniform bucket-level access was introduced: it disables ACLs so IAM is the only access control mechanism, and the full picture is visible in one place.
Picture a library where the head librarian controls who can enter the building. That is IAM. Now imagine that individual donors have also left spare keys taped to specific bookshelves over the years. Those are ACLs. The building access list looks fine when you check it, but there are hidden keys you do not know about. Uniform bucket-level access is the decision to collect all those hidden keys and declare: only the building access system counts.
If you are working with older buckets or inherited environments, you may encounter ACLs in practice. The Cloud Storage IAM vs ACLs page covers the full comparison, including how to audit ACLs on legacy buckets and how to migrate safely to uniform bucket-level access.
When to use each control
Not every bucket needs every control. Here is a practical breakdown by scenario:
Internal application uploads
A bucket used by an internal service to store application data (logs, job outputs, user files). Enable uniform bucket-level access and grant the specific service account the narrowest role it needs on that bucket. Enable public access prevention. Admin Activity logs are sufficient unless your internal policy requires object-level audit trails.
Public file delivery
A bucket serving genuinely public content such as static assets, marketing
images, or open documentation. This is the one case where granting
allUsers the roles/storage.objectViewer role is
correct. Do not enable public access prevention here. Do enable
uniform bucket-level access. Keep only public content in this bucket;
do not mix public and private objects in the same bucket.
allAuthenticatedUsers means any Google account anywhere in
the world, authenticated or not to your application. It does not mean
members of your organisation or users who have logged into your product.
If you want genuinely public content, use allUsers. If you
want access restricted to your own users, use IAM roles on specific
identities, not allAuthenticatedUsers.
Backups and archives
Buckets storing database backups, snapshots, or long-term archives. Enable uniform bucket-level access, public access prevention, and restrict write access to the backup service account only. Consider whether the backup destination should be in a separate project to limit the blast radius if your application project is compromised. Object versioning and retention locks can also be relevant here; see Versioning in Cloud Storage.
Regulated or sensitive data
Buckets holding personal data, financial records, healthcare data, or anything subject to compliance requirements. All of the above, plus: decide whether CMEK is required by your compliance framework. Enable Data Access logs for object-level audit trails. Consider VPC Service Controls if the data must not leave a defined network perimeter. Keep the list of identities with access short and reviewed regularly.
Cross-project or multi-team environments
Buckets shared across teams or accessed from multiple GCP projects. Be especially careful about project-level IAM grants here: they can inadvertently give access to far more than intended. Grant access at the bucket level to specific, named service accounts. Consider using VPC Service Controls to control which projects can access the bucket, not just which identities.
External file sharing
Situations where external users or systems need temporary access to specific objects without a GCP identity. Use signed URLs to grant time-limited, scoped access to individual objects. Keep expiry times short. Do not use public access for this purpose. For common questions about troubleshooting access, see Storage Access Denied Errors.
Common mistakes
Not enabling uniform bucket-level access. Without it, individual objects can have ACLs that grant access independently of the bucket IAM policy. Your IAM policy can look entirely correct while specific objects are exposed through ACLs set by an old script, a third-party integration, or a manual operation someone ran months ago. Enable uniform bucket-level access on every new bucket, from day one.
Granting
roles/storage.adminat the project level to application service accounts. This gives full control over every bucket in the project, including the ability to change IAM policies and delete everything. Application service accounts should have the minimum role on the specific buckets they need. Reserve project-level admin for infrastructure automation with strict monitoring, not for application workloads.Skipping public access prevention on private buckets. Without it, anyone with sufficient IAM permissions on the bucket can accidentally or deliberately make it public. Enable public access prevention as a guardrail on every bucket that should never serve public content. Enforce it at the organisation level for blanket coverage.
Misunderstanding signed URLs as a safe alternative to public access. Signed URLs are better than public objects because they expire and are scoped to one object, but they are still credentials. Anyone who has the URL can use it. Keep expiry times short (minutes for uploads, an hour or less for downloads). Never log full signed URLs: the signature and all parameters are in the query string. For a full treatment, see Signed URLs Explained.
Enabling CMEK without planning key permissions first. The Cloud Storage service agent must have
roles/cloudkms.cryptoKeyEncrypterDecrypteron the KMS key before you configure the bucket to use it. If you enable CMEK without granting this permission, all object writes to that bucket will fail. Grant it, verify it, then update the bucket.Turning on Data Access logs without considering cost and noise. Data Access logs for Cloud Storage can generate millions of entries per day on active buckets. If you enable them project-wide by default, you will quickly accumulate significant log ingestion costs and make it harder to find signal in the noise. Enable them only for buckets where object-level auditing is actually required.
Assuming all public buckets are wrong, or that all public buckets are acceptable. Public buckets are correct in one specific situation: serving genuinely public static content. In every other situation, they are a misconfiguration. The mistake is not in having a public bucket at all; it is in not being deliberate about which buckets are public and why. If you do not have a clear reason for a bucket to be public, it should not be.
Relying on defaults without reviewing them. The defaults are a reasonable starting point, not a secure baseline for production. Treating “private by default” as sufficient without configuring uniform access, least-privilege IAM, and public access prevention is a common gap between what people think they have and what is actually configured.
Simple hardening checklist
This covers the standard steps for a new bucket that should never be public and will hold internal or sensitive data:
Create the bucket in the correct region. Data residency requirements often dictate this. For general guidance on how buckets work, see Storage Buckets Explained.
Enable uniform bucket-level access. Do this at creation time, not as a retrofit. Use the
—uniform-bucket-level-accessflag.Enable public access prevention. Use
—public-access-preventionat creation or update. For organisation-wide coverage, enforce the Organisation Policy.Grant bucket-level IAM roles, not project-level. Identify each service account or identity that needs access. Grant the narrowest role on this specific bucket.
Decide on signed URLs. If external users or systems need temporary access, plan the signed URL workflow. Review the security considerations in Signed URLs Explained before implementing.
Decide on CMEK. If your compliance framework requires key control, configure a Cloud KMS key, grant the service agent permission on it, then update the bucket.
Enable Data Access logging where required. If this bucket holds data that requires an object-level audit trail, enable Data Access logs for
storage.googleapis.comin the project audit log configuration.Review access regularly. IAM bindings accumulate over time. Schedule periodic reviews to remove stale grants and verify the access model still matches what is actually needed.
Public bucket vs private bucket
| Aspect | Private bucket | Public bucket |
|---|---|---|
| Who can access | Only identities with IAM bindings | Anyone with the object URL |
| Authentication required | Yes | No |
| Suitable for | Almost everything | Only genuinely public static content |
| Public access prevention | Enable it | Must be disabled |
| Signed URLs | Use these for external sharing | Not needed — objects are already public |
| CMEK applicability | Relevant for regulated data | Rarely relevant (data is already public) |
Related security topics
Cloud Storage security does not exist in isolation. The controls on this page connect to several broader GCP security topics that are worth understanding as you build out your security posture:
IAM structure and roles. Understanding how IAM policy structure works across all GCP resources, not just Cloud Storage, and gives you the mental model needed to reason about access control across your whole project.
Cloud KMS for CMEK. If CMEK applies to your situation, read Cloud KMS Overview and Customer-Managed Encryption Keys before configuring it. Getting the service agent permissions wrong is a common operational issue.
Audit logging across services. Cloud Audit Logs covers how audit logging works across all GCP services, not just Cloud Storage, including how to query and export logs.
VPC Service Controls for perimeter security. VPC Service Controls explains how to define a network perimeter around GCP services and when it is the right tool to reach for.
Securing production systems broadly. For the wider picture of how Cloud Storage security fits into a secure production GCP architecture, see Securing Production Systems.
Summary
- Cloud Storage is private by default with AES-256 encryption at rest and TLS in transit, but defaults alone are not a production security posture
- Enable uniform bucket-level access on every new bucket to make IAM the sole access control mechanism and eliminate the risk of hidden per-object ACLs
- Apply least privilege: grant roles at the bucket level, not the project level, using the narrowest role that covers the actual need
- Enable public access prevention on every bucket that should never serve public content; enforce it at the organisation level for blanket coverage
- VPC Service Controls adds a network perimeter to prevent data exfiltration; use it for regulated environments, not as a substitute for correct IAM
- CMEK is for workloads where you must control the encryption key lifecycle; it is not required for most buckets and adds meaningful operational complexity
- Admin Activity logs are always on and free; Data Access logs must be enabled explicitly, generate high volume, and should be scoped to buckets that require object-level audit trails
- Signed URLs are the right pattern for giving external users temporary access to specific private objects; treat them as credentials and keep expiry times short
Frequently asked questions
Is Cloud Storage secure by default?
Mostly, yes. A new bucket is private, requires valid GCP credentials to access, and all data is encrypted at rest with AES-256 and in transit with TLS. However, defaults alone are not enough for production. You still need to enable uniform bucket-level access, configure least-privilege IAM, enable public access prevention on private buckets, and set up audit logging. The defaults protect you from accidental public exposure, but they do not configure granular access control or compliance controls for you.
Should I always enable uniform bucket-level access?
Yes, on all new buckets. Uniform bucket-level access disables legacy ACLs so that IAM is the sole access control mechanism. Without it, individual objects can have their own ACLs that grant access independently of the bucket IAM policy, making it genuinely difficult to audit who can read what. Enable it from day one. Once enabled for 90 days, it becomes permanent — which is intentional, not a limitation.
What is the difference between IAM and ACLs in Cloud Storage?
IAM controls access at the bucket level: you grant a role to an identity (a service account, a user, a group) on a specific bucket, and that role applies to every object in the bucket. ACLs are a legacy mechanism that can grant access on individual objects, independently of the bucket IAM policy. On a bucket without uniform access, both are evaluated and access is granted if either permits it. Uniform bucket-level access disables ACLs entirely so only IAM matters. For a full comparison, see the Cloud Storage IAM vs ACLs page.
Do I need CMEK to have a secure bucket?
No. Google-managed encryption (the default) uses AES-256 and is secure for the vast majority of workloads. CMEK is an additional control for situations where you need to manage the encryption key lifecycle yourself — typically for regulatory compliance (HIPAA, certain financial frameworks), contractual requirements, or environments where you need the ability to revoke Google's access to your data by revoking the key. Do not add CMEK purely because it sounds more secure. Understand the operational burden: the Cloud Storage service agent must have the correct KMS permissions, and if you ever revoke the key your data becomes inaccessible.
How do I stop a bucket from becoming public?
Enable public access prevention on the bucket. This is a hard guardrail that blocks any IAM binding granting access to allUsers or allAuthenticatedUsers, even if someone deliberately attempts to add one. For stronger protection across your whole organisation, enforce the constraints/storage.publicAccessPrevention Organisation Policy, which applies the same block to all buckets in the organisation regardless of individual bucket settings.