Skip to content

Instantly share code, notes, and snippets.

@zebreus
Last active March 27, 2024 11:00
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save zebreus/906b8870e49586adfe8bd7bbff43f0a8 to your computer and use it in GitHub Desktop.
Save zebreus/906b8870e49586adfe8bd7bbff43f0a8 to your computer and use it in GitHub Desktop.
Terraform configuration for creating a firebase project with firestore, functions and storage
# firebase.tf https://gist.githubusercontent.com/Zebreus/906b8870e49586adfe8bd7bbff43f0a8/raw/firebase.tf
# Terraform configuration for creating a firebase project with firestore, functions and storage
# Unfinished
terraform {
required_providers {
google-beta = {
source = "hashicorp/google-beta"
version = "4.11.0"
}
null = {
version = "~> 3.1.0"
}
time = {
source = "hashicorp/time"
version = "0.7.2"
}
}
}
variable "billing_account_id" {
type = string
description = "The id of the associated billing account"
nullable = false
}
variable "project_id" {
type = string
description = "The id of the created project"
nullable = false
}
variable "project_name" {
type = string
description = "The name of the created project"
nullable = false
}
variable "region" {
type = string
description = "The region to create the project in"
default = "europe-west1"
nullable = false
}
variable "zone" {
type = string
description = "The zone to create the project in"
default = "europe-west1-b"
nullable = false
}
variable "location" {
type = string
description = "The location to create the project in"
default = "europe-west"
nullable = false
}
locals {
bucket_location = "EUROPE-WEST1"
}
# Basic provider
provider "google-beta" {
alias = "gcloud-user"
region = var.region
zone = var.zone
}
data "google_billing_account" "account" {
provider = google-beta.gcloud-user
billing_account = var.billing_account_id
}
data "google_client_config" "gcloud-user" {
provider = google-beta.gcloud-user
# depends_on = [
# google_service_account.service_account
# ]
}
data "google_client_openid_userinfo" "gcloud-user" {
provider = google-beta.gcloud-user
}
// Create new google cloud project with service account
resource "google_project" "default" {
provider = google-beta.gcloud-user
project_id = var.project_id
name = var.project_name
billing_account = data.google_billing_account.account.id
}
resource "google_service_account" "service_account" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
account_id = "terraform"
display_name = "Terraform"
}
# Allow your user to create a access token
resource "google_service_account_iam_member" "grant-token-iam" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
role = "roles/iam.serviceAccountTokenCreator"
member = "user:${data.google_client_openid_userinfo.gcloud-user.email}"
}
resource "time_sleep" "delay_token_creation" {
depends_on = [
google_service_account_iam_member.grant-token-iam,
google_service_account.service_account,
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_iam_member.editor-iam
]
create_duration = "30s"
}
# Create access token
data "google_service_account_access_token" "default" {
provider = google-beta.gcloud-user
# project = google_project.default.project_id
target_service_account = google_service_account.service_account.email
scopes = ["userinfo-email", "cloud-platform"]
lifetime = "300s"
depends_on = [
google_service_account_iam_member.grant-token-iam,
google_service_account.service_account,
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_iam_member.editor-iam,
time_sleep.delay_token_creation
]
}
# Give some roles to the service account
resource "google_project_iam_member" "firebase-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/firebase.admin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "service-usage-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/serviceusage.serviceUsageAdmin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "appengine-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/appengine.appAdmin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "appengine-creator-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/appengine.appCreator"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "editor-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/editor"
member = "serviceAccount:${google_service_account.service_account.email}"
}
# Create provider with service account
resource "google_service_account_key" "mykey" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
# Wait for the account being added to roles
depends_on = [
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
]
}
provider "google-beta" {
alias = "service-account"
project = google_project.default.project_id
region = var.region
zone = var.zone
# impersonate_service_account = google_service_account.service_account.email
# credentials = base64decode(google_service_account_key.mykey.private_key)
access_token = data.google_service_account_access_token.default.access_token
}
# Activate all required apis
resource "google_project_service" "serviceusage" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
service = "serviceusage.googleapis.com"
disable_dependent_services = true
depends_on = [
]
}
resource "google_project_service" "firebase" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firebase.googleapis.com"
disable_dependent_services = true
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "firestore" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firestore.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "firebasestorage" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firebasestorage.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloudresourcemanager" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "cloudresourcemanager.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "identitytoolkit" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "identitytoolkit.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "compute" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "compute.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "container_registry" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "containerregistry.googleapis.com"
disable_dependent_services = true
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloud_run" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "run.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloud_build" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "cloudbuild.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
# Create firebase project
resource "google_firebase_project" "default" {
provider = google-beta.service-account
project = google_project.default.project_id
depends_on = [
google_project_service.firebase
]
}
# Create firebase web app
resource "google_firebase_web_app" "basic" {
provider = google-beta.service-account
project = google_project.default.project_id
display_name = "${var.project_name} App"
depends_on = [
google_firebase_project.default
]
}
data "google_firebase_web_app_config" "basic" {
provider = google-beta.service-account
web_app_id = google_firebase_web_app.basic.app_id
}
# Create firestore database
resource "google_app_engine_application" "app" {
provider = google-beta.service-account
# provider = google-beta.gcloud-user
project = google_project.default.project_id
location_id = var.location
database_type = "CLOUD_FIRESTORE"
depends_on = [
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_service.firestore
]
}
# Create a bucket for backups
resource "google_storage_bucket" "backup" {
provider = google-beta.service-account
project = google_project.default.project_id
name = "${google_project.default.project_id}-backup"
location = local.bucket_location
}
# Create admin-sdk service account
resource "google_service_account" "admin_sdk" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
account_id = "firebase-adminsdk-ouwu6"
display_name = "firebase-adminsdk"
}
resource "google_project_iam_member" "admin-sdk-token-creator" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${google_service_account.admin_sdk.email}"
}
resource "google_project_iam_member" "admin-sdk-agent" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/firebase.sdkAdminServiceAgent"
member = "serviceAccount:${google_service_account.admin_sdk.email}"
}
resource "google_service_account_key" "admin_sdk" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
# Wait for the account being added to roles
depends_on = [
google_project_iam_member.admin-sdk-token-creator,
google_project_iam_member.admin-sdk-agent,
]
}
# Create firebase storage
resource "null_resource" "activate_storage" {
triggers = {
bucket = data.google_firebase_web_app_config.basic.storage_bucket
}
provisioner "local-exec" {
command = "curl -X POST -H 'Authorization: Bearer ${nonsensitive(data.google_service_account_access_token.default.access_token)}' -H 'Content-Type: application/json' 'https://firebasestorage.googleapis.com/v1beta/projects/${google_project.default.project_id}/buckets/${data.google_firebase_web_app_config.basic.storage_bucket}:addFirebase'"
interpreter = ["sh", "-c"]
}
depends_on = [
google_firebase_web_app.basic,
google_project_service.firebasestorage
]
}
# Enable authentication service
resource "google_identity_platform_config" "identity_platform_config" {
provider = google-beta.service-account
project = google_project.default.project_id
autodelete_anonymous_users = true
depends_on = [
google_firebase_web_app.basic,
google_project_service.identitytoolkit
]
}
resource "google_identity_platform_project_default_config" "identity_project_config" {
provider = google-beta.service-account
project = google_project.default.project_id
sign_in {
allow_duplicate_emails = false
email {
enabled = true
password_required = true
}
}
depends_on =[google_identity_platform_config.identity_platform_config]
}
# Write secrets to local file
resource "local_file" "firebase_config" {
content = jsonencode({
firebase = {
appId = google_firebase_web_app.basic.app_id
apiKey = data.google_firebase_web_app_config.basic.api_key
authDomain = data.google_firebase_web_app_config.basic.auth_domain
databaseURL = lookup(data.google_firebase_web_app_config.basic, "database_url", "")
storageBucket = lookup(data.google_firebase_web_app_config.basic, "storage_bucket", "")
messagingSenderId = lookup(data.google_firebase_web_app_config.basic, "messaging_sender_id", "")
measurementId = lookup(data.google_firebase_web_app_config.basic, "measurement_id", "")
}
})
filename = "${path.module}/firebase-config.json"
depends_on = [
google_firebase_web_app.basic
]
}
resource "local_file" "secrets_file" {
content = jsonencode({
private = {
serviceAccount = jsondecode(base64decode(google_service_account_key.admin_sdk.private_key))
firebase = {
backupBucket = google_storage_bucket.backup.name
}
}
public = {
firebase = {
projectId = google_project.default.project_id
appId = google_firebase_web_app.basic.app_id
apiKey = data.google_firebase_web_app_config.basic.api_key
authDomain = data.google_firebase_web_app_config.basic.auth_domain
databaseURL = lookup(data.google_firebase_web_app_config.basic, "database_url", "")
storageBucket = lookup(data.google_firebase_web_app_config.basic, "storage_bucket", "")
messagingSenderId = lookup(data.google_firebase_web_app_config.basic, "messaging_sender_id", "")
measurementId = lookup(data.google_firebase_web_app_config.basic, "measurement_id", "")
}
}
})
filename = "${path.module}/secrets.json"
depends_on = [
google_firebase_web_app.basic
]
}
resource "local_file" "firebaserc" {
content = jsonencode({
projects = {
development = google_project.default.project_id
production = google_project.default.project_id
}
})
filename = "${path.module}/.firebaserc"
depends_on = [
google_project.default
]
}
resource "local_file" "admin_config" {
content = base64decode(google_service_account_key.mykey.private_key)
filename = "${path.module}/admin-config.json"
depends_on = [
google_service_account_key.mykey,
google_firebase_web_app.basic
]
}
@sheikhaafaq
Copy link

where you have created the firebase project if you are not using it anywhere.

@zebreus
Copy link
Author

zebreus commented Sep 13, 2022

@sheikhaafaq I don't really understand your question, can you maybe explain in more detail what you want to know?

@s-kravtsov
Copy link

Hey, @zebreus , good job ! I tried to use a similar setup, but I can't seem to figure out how to activate the authentication service. The google_project_service.identitytoolkit does not seem to do it. Did you have this issue ? Thanks !

@zebreus
Copy link
Author

zebreus commented Feb 20, 2023

@s-kravtsov Yes, I also had that issue. I did not find a good solution except manually enabling the authentication service in the firebase cloud console.

It has been a while since I looked into this, but I think it uses an internal google API to activate that service. If I remember correctly that API requires authentication with a real user account, so no service account. I created a few new firebase projects and tried to reverse engineer the API calls by looking at the network requests made when enabling authentication, but was not able to reproduce it if I was not on the website.

I would be happy to hear if you find a solution.

@s-kravtsov
Copy link

@zebreus After some trial and error, I did find a way to do this. Basically, it is not enough to just activate the identitytoolkit api, you also need to create a default config and then activate the sign in methods you want to enable. Here is the complete code :

resource "google_project_service" "identitytoolkit" {
  provider = google-beta
  project  = google_project.default.project_id
  service  = "identitytoolkit.googleapis.com"

  depends_on = [google_project.default]
}

resource "google_identity_platform_config" "identity_platform_config" {
  project = google_project.default.project_id
  autodelete_anonymous_users = true
}

resource "google_identity_platform_project_default_config" "identity_project_config" {

    project = google_project.default.project_id 

    sign_in {
        allow_duplicate_emails = false

        email {
            enabled = true
            password_required = true
        }
    }

    depends_on =[google_identity_platform_config.identity_platform_config]
}

Here I am enabling the email/password login.

This removed the need to manually enable the api. However, there are still some manual steps to do which are not available in terraform (at least I did not find a way) :

  • Register the authorized domains (Authentication -> Settings -> Authorized domains)
  • Set the default action link (Authentication -> Templates)

So, not perfect, but the provider is still in beta, so I guess we'll get there.

@zebreus
Copy link
Author

zebreus commented Feb 21, 2023

Neat solution! I added it to the gist.

I am curious to know how you figured out that the config was missing?

@s-kravtsov
Copy link

@zebreus I figured Identity Platform was one of the APIs independent from firebase, so you can open it in the GCP console and see what is missing. And you can find what can be configured in the terraform doc.

@afgallo
Copy link

afgallo commented May 15, 2023

Just wondering if anyone has run into this issue with the latest version of this gist:

╷
│ Error: Invalid resource type
│
│   on main.tf line 363, in resource "google_identity_platform_config" "identity_platform_config":
│  363: resource "google_identity_platform_config" "identity_platform_config" {
│
│ The provider hashicorp/google-beta does not support resource type "google_identity_platform_config".

@myktra
Copy link

myktra commented May 30, 2023

@afgallo

I pulled the google_identity_platform_config.identity_platform_config resource into my own script, dropped the provider = google-beta instruction (as of v4.66.0, you don't need the beta provider), and it worked fine for me.

@schowdhuri
Copy link

This gist is super helpful, thank you! FYI, setting authorized domains is now supported: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/identity_platform_config#authorized_domains

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment