通常、Cloud Functions を内部トラフィックからの呼び出しに限定したい場合は、Cloud Functions と呼び出し元(例えば GCE インスタンス)のプロジェクトが同一であることが必要です。
しかし、VPC Service Controls を使って Cloud Functions を境界の保護対象にすることで、Cloud Functions とその呼び出し元のプロジェクトが異なる場合でも、内部トラフィック扱いで実行することが可能になります。
Cloud Functions のアクセス制限
Cloud Functions のアクセス制限には大別してふたつの方法があります。
- Identity-based
- Network-based
詳しい説明は上記の公式ドキュメントを参照頂くと良いかと思いますが、簡単にまとめると以下のようになります。
関数の呼び出し元のネットワークが自身の組織やプロジェクトに限定される場合は内部トラフィックからの呼び出しのみを許可することで、関数のエンドポイントへの外部からのアクセスを防止することができます。
また、今回は触れませんが、関数から VPC への下り (Egress) の通信を行いたい場合は Serverless VPC Access Connector を利用します。
実装
Cloud Functions とその呼び出し元のプロジェクトが異なる環境を作り、VPC Service Controls を使ってプロジェクトをまたいで関数を内部トラフィックとして実行するまでの流れを示します。
1. プロジェクトの作成
まずは 2 つプロジェクトを作成します。
これらは同一の組織内に作成します。
$ gcloud projects list PROJECT_ID: bar-127298 NAME: bar-127298 PROJECT_NUMBER: 785238314710 PROJECT_ID: foo-192847 NAME: foo-192847 PROJECT_NUMBER: 865726560248
2. GCE インスタンスの作成
関数の呼び出し元となる GCE インスタンスを各プロジェクトに作成します。
cloudshell:~$ gcloud compute instances list --project foo-192847 NAME: instance-foo ZONE: us-central1-a MACHINE_TYPE: e2-medium PREEMPTIBLE: INTERNAL_IP: 10.128.0.3 EXTERNAL_IP: 35.239.219.39 STATUS: RUNNING cloudshell:~$ gcloud compute instances describe instance-foo --project foo-192847 --zone us-central1-a ... serviceAccounts: - email: sa-instance-foo@foo-192847.iam.gserviceaccount.com ... cloudshell:~$ gcloud compute instances list --project bar-127298 NAME: instance-bar ZONE: us-central1-a MACHINE_TYPE: e2-medium PREEMPTIBLE: INTERNAL_IP: 10.128.0.3 EXTERNAL_IP: 34.69.40.102 STATUS: RUNNING $ gcloud compute instances describe instance-bar --project bar-127298 --zone us-central1-a ... serviceAccounts: - email: sa-instance-bar@bar-127298.iam.gserviceaccount.com ...
3. Cloud Functions の作成
Cloud Functions の関数を一方のプロジェクト (foo-192847) に作成します。
関数の呼び出し元は 内部トラフィックのみ (ALLOW_INTERNAL_ONLY) に制限します。
cloudshell:~ (foo-192847)$ gcloud functions describe function-1 availableMemoryMb: 256 buildId: fc93c012-9859-4fc2-aa85-099012dc32c7 buildName: projects/865726560248/locations/us-central1/builds/fc93c012-9859-4fc2-aa85-099012dc32c7 dockerRegistry: CONTAINER_REGISTRY entryPoint: helloWorld httpsTrigger: securityLevel: SECURE_ALWAYS url: https://us-central1-foo-192847.cloudfunctions.net/function-1 ingressSettings: ALLOW_INTERNAL_ONLY labels: deployment-tool: console-cloud maxInstances: 3000 name: projects/foo-192847/locations/us-central1/functions/function-1 runtime: nodejs16 serviceAccountEmail: foo-192847@appspot.gserviceaccount.com sourceUploadUrl: https://storage.googleapis.com/uploads-620971246542.us-central1.cloudfunctions.appspot.com/0be327c3-5853-40ae-96f1-766c3fccc3d7.zip status: ACTIVE timeout: 60s updateTime: '2022-04-05T13:04:10.169Z' versionId: '1'
各 GCE のサービスアカウントに関数の呼び出し権限を付与します。
$ gcloud functions get-iam-policy function-1 bindings: - members: - serviceAccount:sa-instance-bar@bar-127298.iam.gserviceaccount.com - serviceAccount:sa-instance-foo@foo-192847.iam.gserviceaccount.com role: roles/cloudfunctions.invoker etag: BwXb6E6wnRg= version: 1
4. ネットワークによるアクセス制限の確認
この時点では、同じプロジェクト (foo-192847) に含まれる instance-foo からの HTTP トリガのみが内部トラフィックと判定されます。
HTTP トリガで関数を呼び出す際、呼び出し元は認証情報を明示的に指定する必要があるため、Authorization ヘッダに ID トークンを指定して関数の実行をリクエストしています。
参考:https://cloud.google.com/functions/docs/securing/authenticating
instance-foo:~$ gcloud config list account = sa-instance-foo@foo-192847.iam.gserviceaccount.com disable_usage_reporting = True project = foo-192847 instance-foo:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1 Hello World!
Cloud Functions のデプロイされているプロジェクトとは別のプロジェクト (bar-127298) のインスタンス instance-bar は、IAM ポリシによる権限付与がされているものの、ネットワークによるアクセス制限を受けているため実行できません。
参考:https://cloud.google.com/functions/docs/troubleshooting#internal-traffic
instance-bar:~$ gcloud config list account = sa-instance-bar@bar-127298.iam.gserviceaccount.com disable_usage_reporting = True project = bar-127298 instance-bar:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1 <html><head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>403 Forbidden</title> </head> <body text=#000000 bgcolor=#ffffff> <h1>Error: Forbidden</h1> <h2>Access is forbidden.</h2> <h2></h2> </body></html>
5. VPC Service Controls のサービス境界を作成
以下のように、2つのプロジェクトと Cloud Functions を指定して境界を作成します。
cloudshell:~$ gcloud access-context-manager perimeters describe dev_perimeter name: accessPolicies/973808997062/servicePerimeters/dev_perimeter status: resources: - projects/865726560248 - projects/785238314710 restrictedServices: - cloudfunctions.googleapis.com vpcAccessibleServices: {} title: dev_perimeter
境界が有効になったことを確認してみます。
例えば、Cloud Functions のユーザアカウントによるテスト実行は境界外からのアクセスとなるため、VPC Service Controls により実行不可になっていることがわかります。
なお、実行したい場合は境界への Ingress ルールでユーザアカウントからの API アクセスを許可する必要があります。
境界が有効になったので bar-127298 プロジェクトのインスタンス instance-bar からも内部トラフィック扱いで foo-192847 プロジェクトの関数を呼び出すことができるようになりました。
関数の実行をリクエストして確認します。
instance-bar:~$ curl -H "Authorization: bearer $(gcloud auth print-identity-token)" https://us-central1-foo-192847.cloudfunctions.net/function-1 Hello World!
おわりに
VPC Service Controls を使って Cloud Functions を境界の保護対象にすることで、Cloud Functions とその呼び出し元のプロジェクトが異なる場合でも、内部トラフィック扱いで実行する方法をご紹介しました。
理由は様々ですが Cloud Functions と呼び出し元を同一のプロジェクトに配置できないケースはそれなりにあると思いますので、そうした場合でもネットワークベースのアクセス制限を行えることがわかりました。