BigQuery makes it easy to put an organisation's most sensitive data in front of thousands of users and service accounts, and just as easy to copy it out again with one query or one export job. There is no cluster to harden: security in BigQuery is almost entirely identity and policy, spread across Google Cloud IAM, dataset access, policy tags, row access policies, VPC Service Controls and organisation policies. This guide explains how those layers combine, which predefined roles quietly include export rights, how to apply row and column security correctly, how to stop data leaving the perimeter, and what to log and measure. It is for cloud security engineers, data platform owners and auditors working with BigQuery in production.
Key takeaways
- Reading data and running queries are separate rights. bigquery.jobs.create (BigQuery Job User) is granted on a project; table data rights are granted on datasets or tables. Design both deliberately.
- Data Viewer can export. roles/bigquery.dataViewer includes bigquery.tables.export, so "read only" access is also an exfiltration path unless a perimeter constrains it.
- Row access policies deny by default. Once a table has any row access policy, principals not in a grantee list see no rows, so plan a TRUE filter for jobs that need full access.
- Column security needs two permissions. Policy tags with Fine-Grained Reader are enforced on top of dataset access, and SELECT * fails for users without it.
- VPC Service Controls is the exfiltration control. IAM decides who can read; a service perimeter around bigquery.googleapis.com decides where the data can go.
How BigQuery access works
BigQuery has no database users of its own. Every request is made by a Google Cloud principal (user, group, service account or federated workload identity) and evaluated against IAM allow policies on the organisation, folder, project, dataset, table, view or routine, plus fine-grained policies on columns and rows.
| Layer | Question it answers | Controls |
|---|---|---|
| Resource hierarchy IAM | Who can run jobs and administer BigQuery in this project? | roles/bigquery.jobUser, roles/bigquery.admin at project or folder |
| Dataset and table IAM | Who can read or change this data? | dataViewer, dataEditor, dataOwner, metadataViewer on datasets, tables and views |
| Fine-grained policies | Which columns and rows can this principal see, clear or masked? | Policy tags, data policies and masking, row access policies |
| Sharing patterns | How do consumers see a subset without source access? | Authorized views, authorized datasets, authorized routines |
| Perimeter and organisation policy | Where can requests come from and where can data go? | VPC Service Controls, domain-restricted sharing, CMEK constraints |
Because jobs run in one project and read data in another, a common pattern is a "compute" project where analysts hold Job User and "data" projects where they hold dataset-level read roles. Cost and quotas sit with the compute project, data ownership with the data project. The wider IAM model is covered in the Google Cloud IAM and Service Accounts Guide.
Choosing IAM roles
| Predefined role | Key permissions | Grant where | Security note |
|---|---|---|---|
| BigQuery Job User (roles/bigquery.jobUser) | bigquery.jobs.create | Project, folder or organisation only | Lets the principal run queries billed to the project; combine with data roles granted elsewhere |
| BigQuery Metadata Viewer (roles/bigquery.metadataViewer) | tables.get, tables.list, datasets.get, routines.get; no data read | Dataset, table or higher | Good for catalogue and discovery users |
| BigQuery Data Viewer (roles/bigquery.dataViewer) | tables.getData, tables.export, tables.replicateData, tables.createSnapshot | Dataset, table, view or higher | Includes export; grant on datasets, not projects |
| BigQuery Data Editor (roles/bigquery.dataEditor) | getData, updateData, export, tables.create | Dataset, table or higher | Can overwrite tables, which removes row access policies (see below) |
| BigQuery Data Owner (roles/bigquery.dataOwner) | Full dataset and table rights including setIamPolicy | Dataset or higher | Can grant access to others; treat as privileged |
| BigQuery Admin (roles/bigquery.admin) | All bigquery.* permissions | Project, folder, organisation | Break-glass and platform team only |
| BigQuery Connection User (roles/bigquery.connectionUser) | connections.use | Connection or higher | External connections reach Cloud SQL, Spanner or other clouds with the connection's own service account |
- Grant to groups, not individuals: dataset ACLs that list individual users drift quickly and are hard to review.
- Avoid project-level data roles: dataViewer at project level silently covers every future dataset in the project.
- Use custom roles where export is not needed: a custom role without bigquery.tables.export removes the extract job path for that population. Queries can still return results, so this is a complement to, not a replacement for, a perimeter.
- Block public sharing: the organisation policy constraint iam.managed.allowedPolicyMembers (or the legacy iam.allowedPolicyMemberDomains) restricts IAM grants to principals from your organisation and blocks allUsers and allAuthenticatedUsers unless you build a tagged exception with a custom constraint. See the Google Cloud Organization Policy Guide.
Service accounts and workload access
Most BigQuery traffic in a mature estate comes from machines: scheduled queries, Dataflow and Composer pipelines, BI connectors, notebooks and, increasingly, AI agents querying data on a user's behalf. Each of these is a non-human identity with data access of its own.
- One service account per workload: a shared "bigquery-etl" account with dataEditor on every dataset makes it impossible to scope or attribute access.
- No downloadable keys: use attached service accounts, Workload Identity Federation for workloads outside Google Cloud, and service account impersonation for people. Leaked service account keys are the most common way BigQuery data is reached from outside. See the Google Cloud Service Account Keys Security Guide and the Workload Identity Federation Guide.
- BI and SaaS connectors: OAuth grants and connector service accounts often hold broad read. Give each connector a dedicated identity with dataset-level access to curated datasets only.
- The encryption service account: with CMEK, BigQuery uses a Google-managed service account ([email protected]) that needs roles/cloudkms.cryptoKeyEncrypterDecrypter on the key. That grant is legitimate; monitor for others.
Column-level security and data masking
Column-level access control uses policy tags organised in taxonomies in Data Catalog (Dataplex). You tag a column with a policy tag, enforce access control on the taxonomy, and grant the Fine-Grained Reader role (datacatalog.categoryFineGrainedReader) on the policy tag to those allowed to read clear values.
- Two checks: users need dataset access and policy tag access. Without Fine-Grained Reader, SELECT * fails and names the restricted columns; users can still query the other columns with SELECT * EXCEPT.
- Limits to plan around: one policy tag per column, at most 1,000 unique policy tags per table, hierarchies up to five levels, and the taxonomy must be in the same location as the table. Taxonomies cannot be referenced across organisations.
- Writes: inserts do not need fine-grained read, but updates, deletes and merges need it on the columns they read.
- Authorized views: policy tags on the underlying table columns are still checked, so an authorized view does not bypass column security.
- Dynamic data masking: data policies on a policy tag let a masked-reader population see masked values (for example nullified, hashed or default values) instead of being refused. Masking keeps queries and dashboards working for analysts who do not need raw values.
- Separation of duties: Data Catalog Policy Tag Admin (datacatalog.categoryAdmin) manages taxonomies; BigQuery Data Policy Admin, BigQuery Admin or Data Owner manage data policies. Keep taxonomy administration with the data governance function, not the pipeline team.
Row-level security
A row access policy is created with CREATE ROW ACCESS POLICY on a table, naming a GRANT TO list of principals and a FILTER USING predicate that works like a WHERE clause. BigQuery Admin and Data Owner can create them.
- Deny by default: as soon as one policy exists, principals not in any grantee list see no rows. Anyone who had full access before needs a policy with FILTER USING (TRUE), including service accounts that run copies and other non-query operations.
- Overwrites remove policies: CREATE OR REPLACE TABLE and WRITE_TRUNCATE loads remove existing row access policies; TRUNCATE TABLE does not. A pipeline that rebuilds a table nightly will silently strip row security unless it recreates the policies. Test for this explicitly.
- Limitations: GoogleSQL only (legacy SQL is rejected); no policies on JSON columns or temporary tables; wildcard table queries, table sampling and previews are not supported; partition and cluster pruning does not apply to the filter; subquery policies have a 100 MB limit on the top-level subquery result, and data masking works only with non-subquery policies.
- Region copies: tables with row-level or column-level security cannot be copied across regions.
Choosing the pattern
| Need | Pattern | Trade-off |
|---|---|---|
| Hide a sensitive column from most users | Policy tag plus Fine-Grained Reader | SELECT * breaks for non-readers, so expect query changes |
| Show a sensitive column masked | Policy tag with a data masking policy | Masked values must still be safe if combined with other columns |
| Restrict rows by region, tenant or business unit | Row access policies | Pipelines that replace tables must recreate policies |
| Give a team a curated subset without source access | Authorized view or authorized dataset | Most flexible and best performance; up to 2,500 authorised resources per dataset |
| Strongest isolation | Separate tables or datasets | Most secure, least flexible, duplicated data |
Preventing exfiltration
An identity that can read data can usually also copy it: query results to a table in another project, extract jobs to Cloud Storage, table copies and snapshots, or BI tool downloads. The relevant ATT&CK techniques are T1530 Data from Cloud Storage, T1537 Transfer Data to Cloud Account and T1567 Exfiltration Over Web Service.
- Service perimeter: put data projects in a VPC Service Controls perimeter with bigquery.googleapis.com (and storage.googleapis.com if exports go to Cloud Storage) as restricted services. Requests that would read data into or write data out to a project outside the perimeter are blocked unless an ingress or egress rule allows them.
- Access levels for ingress: define access levels (for example corporate IP ranges or managed devices) and ingress rules so that a stolen credential used from elsewhere is refused even when IAM allows the call.
- Narrow egress rules: where a project must query data in another perimeter, write an egress rule for that project, identity and method, not for all BigQuery methods.
- Dry run first: run perimeter changes in dry-run mode, review the violation logs for a few weeks, then enforce.
- Remove export from roles that do not need it and keep public sharing blocked with domain-restricted sharing.
Authorized views across perimeters need ingress and egress rules covering both the view's project and the source project, and revoking authorisation can surface as perimeter violations. For the wider control set see the Data Exfiltration Guide.
Encryption and keys
BigQuery encrypts data at rest by default using envelope encryption managed by Google. Customer-managed encryption keys (CMEK) put the key encryption key in Cloud KMS under your control, so disabling the key makes the data unreadable and every use is visible in Cloud KMS audit logs.
- Location: key rings must match the dataset location (multi-region keys for multi-region datasets).
- Setting keys: set a dataset default key or specify kms_key_name in CREATE TABLE, or use the bq --destination_kms_key flag. BigQuery always uses the primary key version.
- Query results: results go to a temporary table encrypted with a Google-managed key unless a project default key or an explicit CMEK applies. Set the project default where results are themselves sensitive.
- Cost and dependency: each query on a CMEK table makes a Cloud KMS decrypt call, and key availability becomes data availability. Protect key administration rights at least as tightly as data rights.
- Field-level protection: AEAD encryption functions allow encrypting individual values with keysets, useful for crypto-shredding per customer. See the Cryptographic Key Management Guide.
Logging, detection and metrics
| Source | What it gives you | Use |
|---|---|---|
| Cloud Audit Logs, Admin Activity | Dataset, table, IAM policy and policy changes | setIamPolicy on datasets, row access policy deletion, perimeter changes |
| Cloud Audit Logs, Data Access | Data reads and job execution; BigQuery Data Access logs cannot be disabled | Who queried what; prefer the BigQueryAuditMetadata format |
| INFORMATION_SCHEMA.JOBS views | Per-job detail including bytes and slots used and referenced tables | Volume anomalies, unusual tables per principal |
| VPC Service Controls violation logs | Blocked or dry-run requests crossing the perimeter | Exfiltration attempts and policy tuning |
| Cloud KMS audit logs | Key use, disable and destroy | CMEK tampering |
Audit logs contain SQL text and schema details, so restrict who can read them and route them to a central log project with a sink. See the Cloud Logging and Audit Trail Guide.
Detections worth building: extract jobs or table copies to projects outside the organisation; a principal querying far more bytes or tables than its baseline; dataset IAM grants to external domains; row access policy drops; service account key creation for identities with BigQuery data roles; perimeter violations from an identity with valid IAM.
Metrics:
- Percentage of sensitive datasets inside an enforced service perimeter.
- Number of principals holding dataViewer or higher at project level (target zero outside the platform team).
- Percentage of regulated columns carrying an enforced policy tag.
- Service accounts with BigQuery data roles that have user-managed keys (target zero).
- Row access policy coverage on multi-tenant tables, checked after each pipeline run.
Implementation roadmap
- Inventory: list datasets with their IAM bindings, project-level BigQuery roles, service accounts and keys, connections, authorized views and external shares. Classify datasets by sensitivity.
- Guardrails: apply domain-restricted sharing and disable service account key creation at organisation level, with exceptions documented. Test on a pilot folder first; roll back by removing the policy from that folder.
- Re-grant by dataset: move data roles from projects to datasets and groups, split compute and data projects, create custom roles without export where it is not needed.
- Fine-grained security: build the policy tag taxonomy with the data governance team, tag regulated columns, enforce in a test project, then production. Add row access policies to multi-tenant tables and update pipelines that use WRITE_TRUNCATE or CREATE OR REPLACE TABLE to recreate them.
- Perimeter: create VPC Service Controls perimeters in dry-run mode, review violations, add precise ingress and egress rules, then enforce. Rolling back means returning the perimeter to dry run.
- Keys and logging: CMEK for regulated datasets, project default keys for results, log sinks to a central project, detections and metrics live.
Small versus large organisations: a small team can get most of the value from dataset-level group grants, a single policy tag taxonomy and domain-restricted sharing. A large organisation with many data products needs separate compute and data projects, perimeters per data domain, a governed taxonomy owner, automated row policy checks in CI for pipelines, and access reviews tied to the Access Reviews Guide process.
Framework mapping
| Control area | NIST SP 800-53 Rev 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A | ATT&CK threat addressed |
|---|---|---|---|---|
| IAM roles, service accounts, no keys | AC-6, IA-2, IA-5 | PR.AA-05 | A.5.15, A.5.18, A.8.2 | T1078.004 Cloud Accounts, T1552.001 Credentials In Files |
| Policy tags, masking, row access policies | AC-3 | PR.AA-05 | A.8.3, A.8.11 | T1213 Data from Information Repositories |
| VPC Service Controls, domain-restricted sharing | AC-4, SC-7 | PR.DS-01 | A.8.12, A.8.22 | T1537, T1567, T1530 |
| CMEK and in-transit protection | SC-12, SC-28, SC-8 | PR.DS-01, PR.DS-02 | A.8.24 | Unauthorised storage access |
| Audit logs and job monitoring | AU-2, AU-12 | DE.CM-03, DE.CM-09 | A.8.15, A.8.16 | All of the above |
In CIS Controls v8.1 the work maps mainly to Control 3 (Data Protection), Control 6 (Access Control Management) and Control 8 (Audit Log Management).
Common failure patterns
- Read-only that is not read-only: Data Viewer granted at project level to a large group, with no perimeter, gives everyone the ability to export every current and future table.
- Row security stripped by pipelines: a nightly CREATE OR REPLACE TABLE removes the row access policies and every analyst sees every tenant's rows until someone notices.
- Service account keys in notebooks and repos: a key for a pipeline account with dataEditor gives an outsider full access from anywhere when no perimeter or access level applies. The Okta support system breach shows how one service account credential stored in the wrong place exposes many customers.
- Credentials without strong authentication: the Snowflake breach showed infostealer credentials without MFA or network restrictions unlocking 165 data warehouses; the lesson for BigQuery is MFA for people, no keys for machines, and access levels on the perimeter.
- Perimeter left in dry run: a VPC Service Controls perimeter tested in dry-run mode and never enforced logs exfiltration rather than stopping it.
- Public datasets by accident: allAuthenticatedUsers on a dataset, often added for a demo, exposes data to any Google account. Domain-restricted sharing prevents it.
Practitioner checklist
- Separate compute (Job User) and data (dataset roles) projects, and grant data roles to groups at dataset level.
- Remove project-level dataViewer, dataEditor and dataOwner outside the platform team.
- Enforce domain-restricted sharing and block service account key creation at organisation level.
- Give each pipeline, BI connector and agent its own service account with no user-managed keys.
- Tag regulated columns with enforced policy tags; use masking where analysts need the column shape.
- Add row access policies to multi-tenant tables, with a TRUE filter for named service accounts, and test that pipelines recreate them.
- Place sensitive data projects in an enforced VPC Service Controls perimeter with narrow ingress and egress rules.
- Use CMEK for regulated datasets and query results, and alert on Cloud KMS key disable and destroy.
- Sink audit logs centrally, restrict who can read them, and build detections on exports, copies and IAM changes.
- Review dataset access and authorized views quarterly.
Standards and references
- Google Cloud: BigQuery IAM roles and permissions
- Google Cloud: Introduction to column-level access control
- Google Cloud: Introduction to BigQuery row-level security
- Google Cloud: VPC Service Controls for BigQuery
- Google Cloud: Authorized views
- Google Cloud: Customer-managed Cloud KMS keys
- Google Cloud: Introduction to audit logs in BigQuery
- Google Cloud: Restricting identities by domain
Related NHI Mgmt Group resources: Google Cloud Security Guide · Data Security Guide · Amazon Redshift Security Guide · Snowflake Security Guide · Data Loss Prevention (DLP) Guide