BigQueryデータセットのパブリックアクセス制限の設定について

このブログシリーズ 「クラウドセキュリティ 実践集」 では、一般的なセキュリティ課題を取り上げ、「なぜ危険なのか?」 というリスクの解説から、 「どうやって直すのか?」 という具体的な修復手順(コンソール、gcloud CLI、Terraformなど)まで、分かりやすく解説します。
この記事では、BigQueryデータセットがパブリックアクセス可能な権限設定で構成されている問題について、リスクと対策を解説します。

ポリシーの説明
BigQueryデータセットへのパブリックアクセス(allUsersまたはallAuthenticatedUsers)を制限し、機密データの露出を防止します。BigQueryデータセットに「allUsers」や「allAuthenticatedUsers」といったパブリックアクセスを許可するIAMメンバーが設定されていると、インターネット上の誰でもデータセット内のビジネスデータにアクセスできる状態となります。BigQueryには通常、企業の重要な分析データや機密情報が格納されているため、この設定は深刻なセキュリティリスクとなります。
修復方法
コンソールでの修復手順
Google Cloud コンソールを使用して、BigQueryデータセットのパブリックアクセスを制限します。
前提条件
- データセットの権限を表示するには
bigquery.datasets.get
権限が必要です - データセットの権限を更新するには
bigquery.datasets.update
権限が必要です - カスタムロールを使用している場合は、必要な権限が含まれていることを確認してください
- Google Cloud BigQueryコンソールにアクセス
- Google Cloud Console(https://console.cloud.google.com/bigquery)にログイン
- 対象のGCPプロジェクトを選択
- 対象データセットの選択
- 左側のエクスプローラーペインでプロジェクトを展開
- パブリックアクセスが設定されているデータセットをクリックして選択
- 権限設定画面へのアクセス
- データセット情報パネルで「共有」ボタンをクリック
- または、3点メニュー(⋮)から「権限を開く」を選択
- パブリックアクセス権限の特定
- 権限パネルで以下のプリンシパルを探します:
allUsers
: 認証済み・未認証を問わず、インターネット上のすべてのユーザーallAuthenticatedUsers
: Googleアカウントでサインインできるすべてのユーザー(組織外も含む)
- これらのプリンシパルに付与されているロールを確認(通常は「BigQuery データ閲覧者」など)
- 権限パネルで以下のプリンシパルを探します:
- パブリックアクセス権限の削除
allUsers
またはallAuthenticatedUsers
の行にカーソルを合わせる- 右側に表示される削除アイコン(ゴミ箱)をクリック
- 確認ダイアログで「削除」をクリック
- すべてのパブリックアクセス権限を削除するまで繰り返す
- 適切なアクセス権限の設定
- 「プリンシパルを追加」をクリック
- 新しいプリンシパルに適切なメンバーを入力:
- 組織ドメイン:
@yourdomain.com
- 特定のユーザー:
user@yourdomain.com
- サービスアカウント:
service-account@project-id.iam.gserviceaccount.com
- Google グループ:
group@yourdomain.com
- 組織ドメイン:
- ロールを選択:
BigQuery データ閲覧者
: 読み取り専用BigQuery データ編集者
: 読み書き可能BigQuery データオーナー
: 完全な管理権限
- 「保存」をクリック
- 変更の確認と監査
- 権限パネルで、パブリックアクセスが完全に削除されたことを確認
- Cloud Loggingで権限変更の監査ログを確認
- 必要に応じて、他のデータセットも同様に確認・修正
Terraformでの修復手順
BigQueryデータセットのパブリックアクセスを防止するTerraformコードと、主要な修正ポイントを説明します。
# プロバイダー設定
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
# セキュアなBigQueryデータセットの作成
resource "google_bigquery_dataset" "secure_dataset" {
dataset_id = "${var.environment}_secure_analytics"
friendly_name = "Secure Analytics Dataset - ${var.environment}"
description = "機密データを含む分析用データセット(アクセス制限あり)"
location = var.location
delete_contents_on_destroy = false # 誤削除防止
# データ保持期間の設定(90日後に自動削除)
default_table_expiration_ms = var.table_expiration_days * 24 * 60 * 60 * 1000
# パーティション有効期限(7日)
default_partition_expiration_ms = 7 * 24 * 60 * 60 * 1000
# デフォルトの暗号化設定(CMEK)
default_encryption_configuration {
kms_key_name = google_kms_crypto_key.bigquery_key.id
}
# アクセス制御の明示的な設定(パブリックアクセスなし)
# 重要: allUsersやallAuthenticatedUsersは絶対に使用しない
# データセットオーナー(管理者)
access {
role = "OWNER"
user_by_email = google_service_account.dataset_owner.email
}
# データエンジニア(書き込み権限)
access {
role = "WRITER"
user_by_email = google_service_account.data_engineer.email
}
# 組織ドメインユーザー(読み取り専用)
access {
role = "READER"
domain = var.organization_domain # 例: "example.com"
}
# 特定のGoogleグループ(アナリストチーム)
access {
role = "READER"
group_by_email = "data-analysts@${var.organization_domain}"
}
# ラベル(データガバナンス)
labels = {
environment = var.environment
data_classification = "confidential"
compliance = "gdpr-pci"
cost_center = var.cost_center
managed_by = "terraform"
}
lifecycle {
prevent_destroy = true # 本番環境では削除防止
}
}
# IAMバインディング(より細かい権限制御)
# データ閲覧者ロール(特定ユーザー)
resource "google_bigquery_dataset_iam_member" "data_viewer" {
dataset_id = google_bigquery_dataset.secure_dataset.dataset_id
role = "roles/bigquery.dataViewer"
member = "user:analyst@${var.organization_domain}"
condition {
title = "weekday_business_hours"
description = "平日の営業時間内のみアクセス可能"
expression = <<-EOT
request.time.getDayOfWeek("Asia/Tokyo") >= 1 &&
request.time.getDayOfWeek("Asia/Tokyo") <= 5 &&
request.time.getHours("Asia/Tokyo") >= 9 &&
request.time.getHours("Asia/Tokyo") <= 18
EOT
}
}
# ETLサービスアカウント(書き込み権限)
resource "google_bigquery_dataset_iam_member" "etl_writer" {
dataset_id = google_bigquery_dataset.secure_dataset.dataset_id
role = "roles/bigquery.dataEditor"
member = "serviceAccount:${google_service_account.etl_service.email}"
}
# ジョブユーザーロール(クエリ実行権限)
resource "google_bigquery_dataset_iam_member" "job_user" {
dataset_id = google_bigquery_dataset.secure_dataset.dataset_id
role = "roles/bigquery.jobUser"
member = "group:data-scientists@${var.organization_domain}"
}
# 認可ビューのための別データセット
resource "google_bigquery_dataset" "authorized_views" {
dataset_id = "${var.environment}_authorized_views"
friendly_name = "Authorized Views - ${var.environment}"
description = "制限されたビューを提供するデータセット"
location = var.location
# ビューデータセットへのアクセス(より広範囲)
access {
role = "READER"
domain = var.organization_domain
}
}
# 認可ビューの設定
resource "google_bigquery_dataset_access" "authorized_view" {
dataset_id = google_bigquery_dataset.secure_dataset.dataset_id
view {
project_id = var.project_id
dataset_id = google_bigquery_dataset.authorized_views.dataset_id
table_id = google_bigquery_table.filtered_view.table_id
}
}
# フィルタリングされたビューの作成
resource "google_bigquery_table" "filtered_view" {
dataset_id = google_bigquery_dataset.authorized_views.dataset_id
table_id = "customer_summary_view"
view {
query = <<-EOT
SELECT
customer_id,
country,
total_purchases,
last_purchase_date
FROM
`${var.project_id}.${google_bigquery_dataset.secure_dataset.dataset_id}.customers`
WHERE
-- 個人情報を除外し、集計データのみ公開
deletion_requested = FALSE
AND country IN ('JP', 'US', 'EU')
EOT
use_legacy_sql = false
}
}
# VPCサービスコントロールによる追加保護
resource "google_access_context_manager_service_perimeter" "bigquery_perimeter" {
parent = "accessPolicies/${var.access_policy_id}"
name = "accessPolicies/${var.access_policy_id}/servicePerimeters/bigquery_${var.environment}"
title = "BigQuery Security Perimeter - ${var.environment}"
status {
restricted_services = [
"bigquery.googleapis.com",
"bigquerystorage.googleapis.com",
"bigqueryreservation.googleapis.com"
]
resources = [
"projects/${data.google_project.current.number}",
]
# アクセスレベル(企業ネットワークからのみ)
access_levels = [
google_access_context_manager_access_level.corp_network.name,
]
# VPCアクセシブルサービス
vpc_accessible_services {
enable_restriction = true
allowed_services = ["bigquery.googleapis.com"]
}
}
}
# アクセスレベルの定義
resource "google_access_context_manager_access_level" "corp_network" {
parent = "accessPolicies/${var.access_policy_id}"
name = "accessPolicies/${var.access_policy_id}/accessLevels/corp_network_${var.environment}"
title = "Corporate Network Access"
basic {
conditions {
ip_subnetworks = var.corporate_ip_ranges
# 追加のデバイスポリシー
device_policy {
require_screen_lock = true
require_admin_approval = true
allowed_encryption_statuses = ["ENCRYPTED"]
os_constraints {
os_type = "DESKTOP_WINDOWS"
minimum_version = "10.0.0"
}
os_constraints {
os_type = "DESKTOP_MAC"
minimum_version = "12.0.0"
}
}
}
}
}
# データポリシーによる列レベルセキュリティ
resource "google_bigquery_datapolicy_data_policy" "pii_masking" {
location = var.location
data_policy_id = "pii_masking_policy"
policy_tag = google_data_catalog_policy_tag.pii.name
data_policy_type = "DATA_MASKING_POLICY"
data_masking_policy {
predefined_expression = "SHA256"
}
}
# ポリシータグの作成
resource "google_data_catalog_policy_tag" "pii" {
taxonomy = google_data_catalog_taxonomy.data_classification.id
display_name = "PII"
description = "個人識別情報"
}
# 監査ログシンク(BigQuery専用)
resource "google_logging_project_sink" "bigquery_audit" {
name = "bigquery-audit-sink"
destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.audit_logs.dataset_id}"
project = var.project_id
filter = <<-EOT
protoPayload.serviceName="bigquery.googleapis.com"
AND (
protoPayload.methodName="datasetservice.update"
OR protoPayload.methodName="tableservice.update"
OR protoPayload.authorizationInfo.permission="bigquery.datasets.setIamPolicy"
)
EOT
unique_writer_identity = true
bigquery_options {
use_partitioned_tables = true
}
}
# 組織ポリシー(ドメイン制限)
resource "google_organization_policy" "domain_restricted_sharing" {
count = var.organization_id != "" ? 1 : 0
org_id = var.organization_id
constraint = "iam.allowedPolicyMemberDomains"
list_policy {
allow {
values = [var.customer_id] # C0xxxxxxx形式のカスタマーID
}
}
}
# カスタムクォータ(コスト管理)
resource "google_bigquery_reservation" "slot_commitment" {
count = var.enable_reservations ? 1 : 0
name = "${var.environment}-reservation"
location = var.location
slot_capacity = var.slot_capacity
commitment_plan = "FLEX" # FLEX, MONTHLY, ANNUAL
edition = "STANDARD" # STANDARD, ENTERPRISE, ENTERPRISE_PLUS
autoscale {
max_slots = var.max_slots
}
}
# 変数定義
variable "environment" {
description = "環境名"
type = string
validation {
condition = contains(["dev", "stg", "prd"], var.environment)
error_message = "環境は dev, stg, prd のいずれかである必要があります。"
}
}
variable "location" {
description = "BigQueryデータセットのロケーション"
type = string
default = "asia-northeast1"
}
variable "organization_domain" {
description = "組織のドメイン名"
type = string
}
variable "corporate_ip_ranges" {
description = "企業ネットワークのIPレンジ"
type = list(string)
}
variable "table_expiration_days" {
description = "テーブルの自動削除までの日数"
type = number
default = 90
}
variable "customer_id" {
description = "Google Cloud カスタマーID (C0xxxxxxx)"
type = string
}
# 出力
output "dataset_id" {
description = "作成されたデータセットID"
value = google_bigquery_dataset.secure_dataset.dataset_id
}
output "dataset_url" {
description = "BigQueryコンソールでのデータセットURL"
value = "<https://console.cloud.google.com/bigquery?project=${var.project_id}&ws=!1m4!1m3!3m2!1s${var.project_id}!2s${google_bigquery_dataset.secure_dataset.dataset_id}>"
}
まとめ
BigQueryデータセットのパブリックアクセスは、組織にとって最も危険なセキュリティリスクの一つです。
特に重要なポイント:
allUsers
とallAuthenticatedUsers
は絶対に使用しない- VPCサービスコントロールでネットワークレベルの保護を追加
- 認可ビューを活用してデータの直接公開を避ける
- Cloud Loggingですべてのアクセスを記録・監視
情報漏洩のみならず予期しない高額なクエリコストの発生や、競合他社への情報流出は組織に致命的な影響を与えます。定期的なアクセス権限の監査と、ゼロトラストセキュリティモデルの実装により、安全なデータ分析環境を維持しましょう。
この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。 運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。