IP 制限付きで AWS EC2 から BigQuery にアクセスしてみた
こんにちは、みかみです。
GCP & BigQuery 勉強中です。
実際に業務で使用するとなると、セキュリティの考慮は必須です。
普段業務で使っている AWS 環境では、セキュリティグループで IP 制限をかけ、許可されていない IP からのアクセスはできないようにしています。
GCP でも IP 制限かけられるの? どうやって? どの範囲で?
ということで。
やりたいこと
- GCP ではどうやって IP 制限するのか学びたい
- EC2 から BigQuery に IP 制限付きでアクセスしたい
GCP の IP 制限
AWS 同様 GCP でも VPC(VPC Service Controls)があり、Access Context Manager でアクセスレベルを設定できるそうです。
Access Context Manager では、IP アドレスやリージョン、デバイス単位でアクセスを制限できるとのこと。
こちらの記事が、非常に分かりやすかったです。
やってみた
GCP アカウントの権限設定
操作するアカウントに、以下2つの権限を付与してもらいました。
- Access Context Manager 管理者(roles/accesscontextmanager.policyAdmin)
- Resource Manager 組織閲覧者(roles/resourcemanager.organizationViewer)
VPC Service Controls も Access Context Manager も、プロジェクトより上位の「組織」に紐づくため、この2つの権限がないと、プロジェクトオーナーであってもアクセス制御を設定できません。
サービス境界の作成
ナビゲーションメニューの「セキュリティ」から「VPC Service Controls」をクリックし、サービス境界を作成します。
「新しい境界」リンクをクリックして、サービス境界を作成します。
「境界名」に任意の名前を入力し、「保護するプロジェクト」に自分のプロジェクトを追加。「保護するサービス」に「BigQuery API」と「Google Cloud Storage API」を追加して、「保存」をクリック。
無事サービス境界が作成できました。
ちなみに、Resource Manager 組織閲覧者(resourcemanager.organizationViewer) 権限がないと、「保存」ボタンクリックで下記エラーが表示されます。。(初め何のエラーか分かりませんでした。。。
サービス境界で保護設定した自分のプロジェクトを選択して、コンソールから BigQuery と GCS を参照しようとすると・・・
コンソールからのアクセスは、結局のところサービス境界外の local PC からのアクセスになるので、設定通り、参照できなくなりました。
同じプロジェクト内にあらかじめ作成しておいた GCE から BigQuery にアクセスしてみると・・・
mikami.yuki@test-cm-da-mikami:~$ bq ls datasetId ----------- test_s3 mikami.yuki@test-cm-da-mikami:~$ bq query --use_legacy_sql=false \ > 'SELECT > name > FROM > `cm-da-mikami-yuki-258308`.test_s3.pref > WHERE > code = 1' Waiting on bqjob_r5f3f0be91ace4063_0000016f12dca461_1 ... (0s) Current status: DONE +------+ | name | +------+ | 北海道 | +------+
この GCE は サービス境界内のため、ちゃんとデータが参照できました。「保護するサービス」に GCE を設定しなくても、同じプロジェクト内ならサービス境界内に入るようです。
さらに、サービス境界外の EC2 からアクセスしようとすると・・・
[ec2-user@ip-172-31-31-170 ~]$ bq ls BigQuery error in ls operation: VPC Service Controls: Request is prohibited by organization's policy. vpcServiceControlsUniqueIdentifier: b9f2dd46d888e3ef.
ちゃんとアクセス拒否されています。
アクセスレベルを作成してサービス境界にアタッチ
続いてサービス境界に IP 制限を付与し、先ほどアクセスできなかった EC2 からのアクセスを許可してみます。
組織を選択した状態でナビゲーションメニュー「セキュリティ」の「Access Context Manager」画面で「新規」リンクをクリック。
「アクセスレベルのタイトル」に任意の名前を入力し、「条件」欄「属性を追加」から「IP サブネットワーク」を選択。許可する EC2 インスタンスのパブリック IP を入力したら「保存」をクリック。
アクセスレベルが作成できました。
作成したアクセスレベルを、サービス境界にアタッチします。
「VPC Service Controls」コンソールで初めに作成したサービス境界を選択し、編集画面でアクセスレベルを追加して「保存」。
先ほどアクセスエラーになった EC2 から、再度アクセスしてみます。
[ec2-user@ip-172-31-31-170 ~]$ bq ls datasetId ----------- test_s3 [ec2-user@ip-172-31-31-170 ~]$ bq ls test_s3 tableId Type Labels Time Partitioning Clustered Fields --------- ------- -------- ------------------- ------------------ pref TABLE [ec2-user@ip-172-31-31-170 ~]$ bq query --use_legacy_sql=false \ > 'SELECT > name > FROM > `cm-da-mikami-yuki-258308`.test_s3.pref > WHERE > code = 1' Waiting on bqjob_r4385587201489758_0000016f1336af95_1 ... (0s) Current status: DONE +------+ | name | +------+ | 北海道 | +------+
無事、BigQueryが参照できるようになりました!
なお、初めは一部 bq コマンドでアクセスエラーが出たのですが、しばらく経ってからもう一度実行したら、無事データ参照できました。(設定後、少し待つ必要があるのかな?
アクセス許可していない IPからのアクセス(ローカルPCからコンソールでアクセス)は、拒否されたままです。
続いて EC2 で Python を実行して、GCS に格納済みのデータをロードしてみます。
ロードするデータはこちらからいただいてきた、アメリカの赤ちゃんの名前ファイルを.csv に変更したものです。
from google.cloud import bigquery # データセット作成 client = bigquery.Client() dataset_id = "{}.test_dataset".format(client.project) dataset = bigquery.Dataset(dataset_id) dataset.location = "asia-northeast1" dataset = client.create_dataset(dataset) print("Created dataset {}.{}".format(client.project, dataset.dataset_id)) # テーブル作成 table_id = "{}.{}.names_1880".format(client.project, dataset.dataset_id) schema = [ bigquery.SchemaField("name", "STRING", mode="REQUIRED"), bigquery.SchemaField("gender", "STRING", mode="REQUIRED"), bigquery.SchemaField("count", "INTEGER", mode="REQUIRED"), ] table = bigquery.Table(table_id, schema=schema) table = client.create_table(table) print( "Created table {}.{}.{}".format(table.project, table.dataset_id, table.table_id) ) # データロード dataset_id = dataset_id.split(".")[-1] table_id = table_id.split(".")[-1] dataset_ref = client.dataset(dataset_id) job_config = bigquery.LoadJobConfig() job_config.schema = [ bigquery.SchemaField("name", "STRING", mode="REQUIRED"), bigquery.SchemaField("gender", "STRING", mode="REQUIRED"), bigquery.SchemaField("count", "INTEGER", mode="REQUIRED"), ] # The source format defaults to CSV, so the line below is optional. job_config.source_format = bigquery.SourceFormat.CSV uri = "gs://test-cm-mikami/yob1880.csv" load_job = client.load_table_from_uri( uri, dataset_ref.table(table_id), job_config=job_config ) # API request print("Starting job {}".format(load_job.job_id)) load_job.result() # Waits for table load to complete. print("Job finished.") destination_table = client.get_table(dataset_ref.table(table_id)) print("Loaded {} rows.".format(destination_table.num_rows))
実行してみると・・・
(test_bq) [ec2-user@ip-172-31-31-170 ~]$ python test_load.py Created dataset cm-da-mikami-yuki-258308.test_dataset Created table cm-da-mikami-yuki-258308.test_dataset.names_1880 Starting job d9d8fd70-e0ff-4fbd-a1fd-0534dc45950d Job finished. Loaded 2000 rows.
ロード完了。
確認してみます。
[ec2-user@ip-172-31-31-170 ~]$ bq ls datasetId -------------- test_dataset test_s3 [ec2-user@ip-172-31-31-170 ~]$ bq ls test_dataset tableId Type Labels Time Partitioning Clustered Fields ------------ ------- -------- ------------------- ------------------ names_1880 TABLE [ec2-user@ip-172-31-31-170 ~]$ bq query --use_legacy_sql=false \ > 'SELECT > name, > gender > FROM > `cm-da-mikami-yuki-258308`.test_dataset.names_1880 > WHERE > count = (SELECT max(count) FROM `cm-da-mikami-yuki-258308`.test_dataset.names_1880)' Waiting on bqjob_r6573da5ad95dd040_0000016f15098a30_1 ... (0s) Current status: DONE +------+--------+ | name | gender | +------+--------+ | John | M | +------+--------+
無事データがロードできています。
BigQuery へのアクセスに IP 制限をつけることができました。
まとめ(所感)
GCP では、以下の 2step で IP 制限が設定できます。
※組織やプロジェクト、制限するサービスは構築済みで、アカウントへの権限付与は実施済みの前提です。
- アクセスレベルの作成(Access Context Manager)
- サービス境界の作成(VPC Service Controls)
コンソールからプルダウン選択などで操作していくだけで完了なので、思っていたより簡単に設定できました。
また、サービス境界を設定すれば、外からのアクセスだけではなく、サービス境界外のリソースへのデータ出力もできなくなります。
特に個人情報など大事なデータを格納しているリソースには、サービス境界を設定しておけば、コンソールからのアクセスもできなくなるため、流出のリスクがかなり軽減されますね。