Ensure that BigQuery datasets are not anonymously or publicly accessible

GCP
GCP

A BigQuery dataset's access list can name allUsers or allAuthenticatedUsers as a principal, and either entry hands the dataset's tables and views to people outside the project. The control requires that no role on any dataset is bound to those two principals.

What the benchmark asks for

Every dataset in every project is checked for the two public principals. allUsers means any caller, signed in or not. allAuthenticatedUsers means any holder of a Google account or service account, which is a population the project owner has no relationship with and no list of.

The check reads the dataset's own access list, not the project's IAM policy. A resource carries one allow policy of its own, so the dataset's list and the project's policy are separate things to read and separate things to edit.

BigQuery does not create public datasets. A dataset that fails this control was changed by someone, through the console, the bq tool, Terraform, or a SQL statement.

How it works

A dataset carries an access list of entries, each pairing a role with a principal. Most entries name a user, a group, or a service account. Some name a special group instead, and allAuthenticatedUsers is one of the special groups a dataset's access list accepts. In the list it appears as a role set against that special group, READER alongside allAuthenticatedUsers, sitting in the same array as every other entry. That entry is the mechanism behind this control. There is no separate "make public" setting to audit, only a principal in a list that otherwise holds colleagues and service accounts.

Four paths write to that list, and a reader auditing only one of them will miss grants made through the others:

  • The console, under the dataset's Sharing > Permissions panel.
  • bq update --source with an edited JSON access array.
  • Terraform, through google_bigquery_dataset or google_bigquery_dataset_iam_policy.
  • SQL, through the GRANT statement, written as GRANT role ON SCHEMA dataset TO principal.

Two behaviors around that list explain most real findings.

The first is inheritance. A project is the parent resource of a dataset, so a role granted at the project level is inherited by every dataset in that project and by their tables and views. A project-level binding to a broad principal reaches the data without ever appearing in a dataset's access list.

The second is dataset creation. A dataset created by Terraform's google_bigquery_dataset resource with no access block is not private. The provider's default grants access to all users with project-level roles Owner, Editor, and Viewer. Where a project has wide membership, that default converts project membership into dataset read access at creation time.

Ownership follows the same quiet pattern. The user who creates a dataset is granted the BigQuery Data Owner role on it. Nobody has to assign that role for it to exist, and it carries the permission to add a public principal later. A service account that creates datasets in a pipeline holds Data Owner on each one.

What the control does not cover

Inherited project roles sit outside it, as described above. An organization that grants roles/bigquery.dataViewer broadly at the project level passes this control on every dataset.

External and linked datasets are a harder limit. Tables inside those datasets do not support access controls at all, so there is no object-level permission to set and no dataset-level fix that reaches down into them. Control over that data lives at the source.

The check is also per dataset, not per project. bq ls lists the datasets in one project and each one is inspected separately, so a project added later, or a dataset created after the audit ran, is outside the result until somebody lists it again. Nothing in the control watches for new datasets, which makes it a point-in-time answer rather than a standing guarantee.

What it costs to comply

Removing a public principal is a small change with a sharp failure mode, and the failure mode depends on which tool makes the change.

bq update --source applies the whole file. The access array in that file becomes the dataset's access list, replacing what was there. A principal dropped from the file during editing loses access the moment the update runs, with no warning and no diff. The same holds for Terraform's google_bigquery_dataset_iam_policy, which replaces the dataset's existing policy wholesale rather than adding to it. Importing a dataset into a google_bigquery_dataset_iam_policy resource without first reading its current access list removes every grant the configuration does not list.

For some datasets the public grant is deliberate. Where it is, the right response is to record the dataset, the reason, and the owner, then suppress the finding. The question to settle is whether anyone in the current team made the decision or whether it survives only because nobody has reviewed it.

There is no ongoing cost. Dataset access controls carry no charge, and removing a principal does not change query cost, storage cost, or performance.

How to check your environment

List the datasets in a project:

bq ls

Then read each one's access list. Exporting the dataset metadata to JSON gives the full access array, which is what the audit needs:

bq show --format=prettyjson PROJECT_ID:DATASET_NAME

Each entry in the access array names its principal through one of a small set of fields: specialGroup, userByEmail, groupByEmail, or domain. allAuthenticatedUsers is one of the values specialGroup takes, so it is a specialGroup entry you are looking for. allUsers is not a specialGroup value. It appears as an iamMember entry instead, so an audit that reads only specialGroup finds one of the two public principals and misses the broader one. While the array is open, read the domain and groupByEmail entries as well. Both are entry types the access list accepts, and each names a set of people rather than a single account.

In the console, the dataset's principals are reviewed by expanding the project in the Explorer pane, selecting the dataset, then opening Sharing > Permissions. This is a per-dataset panel.

Scale the check by scripting bq ls and bq show across every project in the organization.

How to implement it

To remove a public principal with the bq tool, write the metadata to a file, edit it, and apply it back:

bq show --format=prettyjson PROJECT_ID:DATASET_NAME > PATH_TO_FILE

Delete the entries naming allUsers or allAuthenticatedUsers from the access array, leave every other entry in place, then apply the file:

bq update --source PATH_TO_FILE PROJECT_ID:DATASET_NAME

Read the edited file once more before applying it. Because the update replaces the entire access list, an entry deleted by accident is a principal that loses access, and the only record of what was there is the file you just overwrote. Keep an unedited copy of the export until the change is confirmed.

In the console, open the dataset's Sharing > Permissions panel, expand the role holding the public principal, and remove it.

To stop new public grants, use the domain restriction organization policy, constraints/iam.allowedPolicyMemberDomains. Test it on a folder before applying it more widely. It also blocks grants to external contractors, partner service accounts, and Google-managed service agents from other organizations, and those break loudly.

For datasets managed in Terraform, pick the resource type deliberately. google_bigquery_dataset_iam_policy replaces the dataset's policy wholesale, so the next apply removes a public grant the configuration does not list, along with every other grant it does not list. google_bigquery_dataset_iam_member writes one binding at a time and leaves the other grants in place. Whichever you choose, set an explicit access block on google_bigquery_dataset rather than relying on the default, because the default grants the dataset to everyone holding a basic project role.

Further reading

  • Controlling access to datasets. The access list model, the special groups a dataset accepts, the four tools that write to it, and the inheritance rules between project and dataset.
  • Creating datasets. Dataset creation across the console, bq, the API, and Terraform, including the default access a new dataset receives and the role its creator is given.

Found it. Fixed it. Done.

Start free. Understand your cloud in under an hour.