From 7623f834b78f50361f7ea7de11be8c6fed2e9e63 Mon Sep 17 00:00:00 2001 From: James Scott Date: Tue, 24 Sep 2024 19:50:53 +0000 Subject: [PATCH 1/2] Enable Google Cloud Identity Platform for staging and production This change configures Google Cloud Identity Platform (GCIP) for staging and production environments, enabling developers to deploy full-stack applications with GCIP to GCP. Key changes include: - Setting up a new GCIP tenant with a GitHub Provider for authentication. - Modifying the frontend infrastructure module to utilize the new tenant and enable GitHub login. - Updating frontend code to handle optional tenant IDs, allowing seamless transition between local and cloud environments. This addresses the previous limitation of having GCIP configured only locally with the emulator. --- DEPLOYMENT.md | 13 +++++- frontend/src/common/app-settings.ts | 1 + frontend/src/static/index.html | 3 +- .../js/components/test/webstatus-app.test.ts | 1 + .../test/webstatus-services-container.test.ts | 1 + .../webstatus-app-settings-service.test.ts | 1 + .../webstatus-firebase-auth-service.test.ts | 11 +++++ .../webstatus-firebase-auth-service.ts | 5 +++ infra/.envs/prod.tfvars | 7 +++ infra/.envs/staging.tfvars | 7 +++ infra/auth/main.tf | 44 +++++++++++++++++++ infra/auth/outputs.tf | 18 ++++++++ infra/auth/providers.tf | 24 ++++++++++ infra/auth/variables.tf | 25 +++++++++++ infra/frontend/service.tf | 12 +++++ infra/frontend/variables.tf | 8 ++++ infra/main.tf | 23 ++++++++++ infra/providers.tf | 5 ++- infra/variables.tf | 14 ++++++ 19 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 infra/auth/main.tf create mode 100644 infra/auth/outputs.tf create mode 100644 infra/auth/providers.tf create mode 100644 infra/auth/variables.tf diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 54f130f8..ec68b51b 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -27,10 +27,15 @@ instructions assume you have access to the following projects: - webstatus-dev-internal-staging - webstatus-dev-public-staging - production - - web-compass-staging + - web-compass-prod - webstatus-dev-internal-prod - webstatus-dev-public-prod +Google Cloud Identity Platform: + +- [Enable](https://console.cloud.google.com/marketplace/details/google-cloud-platform/customer-identity) Cloud Identity Platform for the internal project. +- [Enable](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart) multi-tenancy in the Google Cloud Console. + ## Deploying your own copy ```sh @@ -83,6 +88,10 @@ Or you could populate with fake data by running. go run ./util/cmd/load_fake_data/main.go -spanner_project=${SPANNER_PROJECT_ID} -spanner_instance=${SPANNER_INSTANCE_ID} -spanner_database=${SPANNER_DATABASE_ID} -datastore_project=${DATASTORE_PROJECT_ID} -datastore_database=${DATASTORE_DATABASE} ``` +Setup auth: + +Add your domain to the allow-list of domains in the [console](https://pantheon.corp.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). + When you are done with your own copy ```sh @@ -96,6 +105,8 @@ terraform workspace select default terraform workspace delete $ENV_ID ``` +Also, remove your domain from the allow-list of domains in the [console](https://pantheon.corp.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). + ## Deploy Staging ```sh diff --git a/frontend/src/common/app-settings.ts b/frontend/src/common/app-settings.ts index 56f1d4db..4d62a934 100644 --- a/frontend/src/common/app-settings.ts +++ b/frontend/src/common/app-settings.ts @@ -27,6 +27,7 @@ interface FirebaseAppSettings { interface FirebaseAuthSettings { emulatorURL: string; + tenantID: string; } interface FirebaseSettings { diff --git a/frontend/src/static/index.html b/frontend/src/static/index.html index 201bee23..9747c263 100644 --- a/frontend/src/static/index.html +++ b/frontend/src/static/index.html @@ -81,7 +81,8 @@ "authDomain": "$FIREBASE_APP_AUTH_DOMAIN" }, "auth": { - "emulatorURL": "$FIREBASE_AUTH_EMULATOR_URL" + "emulatorURL": "$FIREBASE_AUTH_EMULATOR_URL", + "tenantID": "$FIREBASE_AUTH_TENANT_ID" } } }' diff --git a/frontend/src/static/js/components/test/webstatus-app.test.ts b/frontend/src/static/js/components/test/webstatus-app.test.ts index f7a594fe..7f1c27e5 100644 --- a/frontend/src/static/js/components/test/webstatus-app.test.ts +++ b/frontend/src/static/js/components/test/webstatus-app.test.ts @@ -30,6 +30,7 @@ describe('webstatus-app', () => { }, auth: { emulatorURL: 'http://localhost:9099', + tenantID: 'tenantID', }, }, }; diff --git a/frontend/src/static/js/components/test/webstatus-services-container.test.ts b/frontend/src/static/js/components/test/webstatus-services-container.test.ts index 57e0e8e3..bd8dbaf7 100644 --- a/frontend/src/static/js/components/test/webstatus-services-container.test.ts +++ b/frontend/src/static/js/components/test/webstatus-services-container.test.ts @@ -31,6 +31,7 @@ describe('WebstatusServiceContainer', () => { }, auth: { emulatorURL: 'http://localhost:9099', + tenantID: 'tenantID', }, }, }; diff --git a/frontend/src/static/js/services/test/webstatus-app-settings-service.test.ts b/frontend/src/static/js/services/test/webstatus-app-settings-service.test.ts index a59f95ee..63a552eb 100644 --- a/frontend/src/static/js/services/test/webstatus-app-settings-service.test.ts +++ b/frontend/src/static/js/services/test/webstatus-app-settings-service.test.ts @@ -36,6 +36,7 @@ describe('webstatus-app-settings-service', () => { }, auth: { emulatorURL: 'http://localhost:9099', + tenantID: 'tenantID', }, }, }; diff --git a/frontend/src/static/js/services/test/webstatus-firebase-auth-service.test.ts b/frontend/src/static/js/services/test/webstatus-firebase-auth-service.test.ts index 0f609799..351d1a90 100644 --- a/frontend/src/static/js/services/test/webstatus-firebase-auth-service.test.ts +++ b/frontend/src/static/js/services/test/webstatus-firebase-auth-service.test.ts @@ -53,6 +53,7 @@ class FakeParentElement extends LitElement { describe('webstatus-firebase-auth-service', () => { const settings = { emulatorURL: '', + tenantID: 'tenantID', }; it('can be added to the page with the settings', async () => { const component = await fixture( @@ -144,6 +145,11 @@ describe('webstatus-firebase-auth-service', () => { 'github', 'icon should be github' ); + assert.equal( + component.firebaseAuthConfig?.auth.tenantId, + 'tenantID', + 'unexpected tenantID' + ); // Ensure it gets it via context. assert.equal( component.firebaseAuthConfig, @@ -193,6 +199,7 @@ describe('webstatus-firebase-auth-service', () => { const testSettings = { // Set emulator URL emulatorURL: 'http://localhost:9099', + tenantID: '', }; const root = document.createElement('div'); document.body.appendChild(root); @@ -228,6 +235,10 @@ describe('webstatus-firebase-auth-service', () => { await component.updateComplete; assert.isTrue(emulatorConnectorStub.calledOnce); + assert.notExists( + component.firebaseAuthConfig?.auth.tenantId, + 'unexpected tenantID' + ); expect(emulatorConnectorStub).to.have.been.calledWith( authStub, 'http://localhost:9099' diff --git a/frontend/src/static/js/services/webstatus-firebase-auth-service.ts b/frontend/src/static/js/services/webstatus-firebase-auth-service.ts index 8befc2db..a0d223e8 100644 --- a/frontend/src/static/js/services/webstatus-firebase-auth-service.ts +++ b/frontend/src/static/js/services/webstatus-firebase-auth-service.ts @@ -36,6 +36,7 @@ import {ServiceElement} from './service-element.js'; interface FirebaseAuthSettings { emulatorURL: string; + tenantID: string; } @customElement('webstatus-firebase-auth-service') @@ -62,6 +63,10 @@ export class WebstatusFirebaseAuthService extends ServiceElement { initFirebaseAuth() { if (this.firebaseApp) { const auth = this.authInitializer(this.firebaseApp); + // Local environment will not have a tenantID. + if (this.settings.tenantID !== '') { + auth.tenantId = this.settings.tenantID; + } const provider = new GithubAuthProvider(); this.firebaseAuthConfig = { auth: auth, diff --git a/infra/.envs/prod.tfvars b/infra/.envs/prod.tfvars index 07b32384..98b23f27 100644 --- a/infra/.envs/prod.tfvars +++ b/infra/.envs/prod.tfvars @@ -72,3 +72,10 @@ wpt_region_schedules = { "us-central1" = "0 21 * * *" # Daily at 9:00 PM "europe-west1" = "0 9 * * *" # Daily at 9:00 AM } + +firebase_api_key_location = "prod-firebase-app-api-key" + +auth_github_config_locations = { + client_id = "prod-github-client-id" + client_secret = "prod-github-client-secret" +} diff --git a/infra/.envs/staging.tfvars b/infra/.envs/staging.tfvars index 983c4446..32e85c78 100644 --- a/infra/.envs/staging.tfvars +++ b/infra/.envs/staging.tfvars @@ -73,3 +73,10 @@ wpt_region_schedules = { "us-central1" = "0 21 * * *" # Daily at 9:00 PM "europe-west1" = "0 9 * * *" # Daily at 9:00 AM } + +firebase_api_key_location = "staging-firebase-app-api-key" + +auth_github_config_locations = { + client_id = "staging-github-client-id" + client_secret = "staging-github-client-secret" +} diff --git a/infra/auth/main.tf b/infra/auth/main.tf new file mode 100644 index 00000000..c56d383a --- /dev/null +++ b/infra/auth/main.tf @@ -0,0 +1,44 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +locals { + gh_client_id = sensitive(data.google_secret_manager_secret_version_access.gh_client_id.secret_data) + gh_client_secret = sensitive(data.google_secret_manager_secret_version_access.gh_client_secret.secret_data) +} + +data "google_secret_manager_secret_version_access" "gh_client_id" { + provider = google.internal_project + secret = var.github_config_locations.client_id +} + +data "google_secret_manager_secret_version_access" "gh_client_secret" { + provider = google.internal_project + secret = var.github_config_locations.client_secret +} + +resource "google_identity_platform_tenant" "tenant" { + provider = google.internal_project + display_name = var.env_id + allow_password_signup = false + enable_email_link_signin = false +} + +resource "google_identity_platform_tenant_default_supported_idp_config" "github_idp_config" { + provider = google.internal_project + enabled = true + tenant = google_identity_platform_tenant.tenant.name + idp_id = "github.com" + client_id = local.gh_client_id + client_secret = local.gh_client_secret +} diff --git a/infra/auth/outputs.tf b/infra/auth/outputs.tf new file mode 100644 index 00000000..e8cebf8b --- /dev/null +++ b/infra/auth/outputs.tf @@ -0,0 +1,18 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +output "tenant_id" { + value = google_identity_platform_tenant.tenant.name +} diff --git a/infra/auth/providers.tf b/infra/auth/providers.tf new file mode 100644 index 00000000..bf027b46 --- /dev/null +++ b/infra/auth/providers.tf @@ -0,0 +1,24 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + google = { + source = "hashicorp/google" + configuration_aliases = [ + google.internal_project, + ] + } + } +} diff --git a/infra/auth/variables.tf b/infra/auth/variables.tf new file mode 100644 index 00000000..27393797 --- /dev/null +++ b/infra/auth/variables.tf @@ -0,0 +1,25 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "env_id" { + type = string +} + +variable "github_config_locations" { + description = "Location of the github configuration in secret manager" + type = object({ + client_id = string + client_secret = string + }) +} diff --git a/infra/frontend/service.tf b/infra/frontend/service.tf index f1294b64..de5cc876 100644 --- a/infra/frontend/service.tf +++ b/infra/frontend/service.tf @@ -79,6 +79,18 @@ resource "google_cloud_run_v2_service" "service" { name = "PROJECT_ID" value = data.google_project.datastore_project.number } + env { + name = "FIREBASE_APP_AUTH_DOMAIN" + value = var.firebase_settings.auth_domain + } + env { + name = "FIREBASE_APP_API_KEY" + value = var.firebase_settings.api_key + } + env { + name = "FIREBASE_AUTH_TENANT_ID" + value = var.firebase_settings.tenant_id + } } vpc_access { network_interfaces { diff --git a/infra/frontend/variables.tf b/infra/frontend/variables.tf index b861f7ed..1fc26997 100644 --- a/infra/frontend/variables.tf +++ b/infra/frontend/variables.tf @@ -67,3 +67,11 @@ variable "projects" { variable "deletion_protection" { type = bool } + +variable "firebase_settings" { + type = object({ + auth_domain = string + api_key = string + tenant_id = string + }) +} diff --git a/infra/main.tf b/infra/main.tf index 3aaa0451..0d0f004e 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -12,6 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. +locals { + firebase_api_key = sensitive(data.google_secret_manager_secret_version_access.firebase_api_key.secret_data) +} + +data "google_secret_manager_secret_version_access" "firebase_api_key" { + provider = google.internal_project + secret = var.firebase_api_key_location +} + +module "auth" { + source = "./auth" + providers = { + google.internal_project = google.internal_project + } + env_id = var.env_id + github_config_locations = var.auth_github_config_locations +} + module "services" { source = "./services" providers = { @@ -118,4 +136,9 @@ module "frontend" { ssl_certificates = var.ssl_certificates domains_for_gcp_managed_certificates = var.frontend_domains_for_gcp_managed_certificates projects = var.projects + firebase_settings = { + api_key = local.firebase_api_key + auth_domain = "${var.projects.internal}.firebaseapp.com" + tenant_id = module.auth.tenant_id + } } diff --git a/infra/providers.tf b/infra/providers.tf index 42f03738..ccf6215b 100644 --- a/infra/providers.tf +++ b/infra/providers.tf @@ -33,6 +33,9 @@ provider "google" { provider "google" { alias = "internal_project" project = var.projects.internal + # Need user_project_override=true for identity platform + # https://stackoverflow.com/a/78203631 + user_project_override = true } provider "google" { @@ -46,4 +49,4 @@ provider "docker" { address = module.storage.docker_repository_details.hostname config_file = pathexpand("~/.docker/config.json") } -} \ No newline at end of file +} diff --git a/infra/variables.tf b/infra/variables.tf index f51bd3c5..66b4f575 100644 --- a/infra/variables.tf +++ b/infra/variables.tf @@ -141,3 +141,17 @@ variable "chromium_region_schedules" { variable "web_features_region_schedules" { type = map(string) } + + +variable "firebase_api_key_location" { + description = "Location of the firebase api key in secret manager" + type = string +} + +variable "auth_github_config_locations" { + description = "Location of the github configuration in secret manager" + type = object({ + client_id = string + client_secret = string + }) +} From 5b6d57a7ed3540640af8400559f6de6c70a80c7d Mon Sep 17 00:00:00 2001 From: James Scott Date: Thu, 26 Sep 2024 16:59:55 +0000 Subject: [PATCH 2/2] use console.cloud.google.com for links --- DEPLOYMENT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index ec68b51b..f3999a3e 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -90,7 +90,7 @@ go run ./util/cmd/load_fake_data/main.go -spanner_project=${SPANNER_PROJECT_ID} Setup auth: -Add your domain to the allow-list of domains in the [console](https://pantheon.corp.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). +Add your domain to the allow-list of domains in the [console](https://console.cloud.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). When you are done with your own copy @@ -105,7 +105,7 @@ terraform workspace select default terraform workspace delete $ENV_ID ``` -Also, remove your domain from the allow-list of domains in the [console](https://pantheon.corp.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). +Also, remove your domain from the allow-list of domains in the [console](https://console.cloud.google.com/customer-identity/settings?project=webstatus-dev-internal-staging). ## Deploy Staging