IAM & Service Accounts
1. Overview
This document specifies the GCP IAM configuration for the Pinpoint Payment Gateway. We follow the principle of least privilege, each service gets only the permissions it needs.
2. Service Accounts
| Service Account | Used By | Purpose |
|---|---|---|
gateway-processing@{project}.iam | Processing Microservice | TransIT API calls, DB access, Pub/Sub publish, Secret Manager |
gateway-management@{project}.iam | Management Microservice | DB access, Firebase Admin, Identity Platform, FCM terminal commands, Secret Manager |
gateway-online-txn@{project}.iam | Online Txn Microservice | DB access, Pub/Sub, App Check, Secret Manager |
gateway-merchant-onboarding@{project}.iam | Merchant Onboarding Microservice | Merchant activation, XTMS API, App Check, Secret Manager |
gateway-auth@{project}.iam | Gateway Auth Service | OAuth token issuance, App Check, Secret Manager |
gateway-status@{project}.iam | Status Microservice | Health checks, LB/Run viewer |
gateway-github-actions@{project}.iam | GitHub Actions CI/CD | Deploy to Cloud Run, push images |
3. IAM Role Bindings
Microservice Common Roles
All microservices (except status) get these base roles:
resource "google_project_iam_member" "spanner_database_user" {
for_each = local.db_services # processing, management, online-txn, merchant-onboarding, auth
project = var.project_id
role = "roles/spanner.databaseUser"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
resource "google_project_iam_member" "cloud_trace_agent" {
for_each = local.trace_agent_services # all services
project = var.project_id
role = "roles/cloudtrace.agent"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
Identity & App Check Roles
resource "google_project_iam_member" "identity_toolkit_viewer" {
for_each = local.identity_toolkit_viewers # management, auth, online-txn
project = var.project_id
role = "roles/identitytoolkit.viewer"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
resource "google_project_iam_member" "identity_toolkit_admin_management" {
project = var.project_id
role = "roles/identitytoolkit.admin"
member = "serviceAccount:${google_service_account.management.email}"
}
resource "google_project_iam_member" "firebase_app_check_token_verifier" {
for_each = local.app_check_token_verifiers # auth, management, online-txn, merchant-onboarding
project = var.project_id
role = "roles/firebaseappcheck.tokenVerifier"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
Firebase Cloud Messaging for Terminal Commands
The management service sends Gateway runtime commands to terminals through Firebase Cloud Messaging (FCM). Terraform provisions the Pub/Sub command-dispatch resources and the gateway-fcm-service-account-json Secret Manager secret in infra/tf/gcp/modules/device-runtime/main.tf.
The secret value is not managed by Terraform. Populate it with the Firebase Admin SDK service-account JSON before enabling live terminal-command dispatch:
gcloud secrets versions add gateway-fcm-service-account-json \
--data-file=/path/to/firebase-adminsdk.json
The management Cloud Run service account must also be allowed to send FCM messages on the Firebase project. Grant either roles/firebase.sdkAdminServiceAgent or a custom role that includes firebase.messaging.send:
gcloud projects add-iam-policy-binding <FIREBASE_PROJECT_ID> \
--member="serviceAccount:<MANAGEMENT_SA_EMAIL>" \
--role="roles/firebase.sdkAdminServiceAgent"
Keep this Firebase-project-level binding aligned with the device-runtime Terraform module note. It is separate from the GCP project Secret Manager accessor binding that Terraform manages for the FCM JSON secret.
Auth Service Token Signing
resource "google_service_account_iam_member" "auth_custom_token_signer" {
service_account_id = google_service_account.auth.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${google_service_account.auth.email}"
}
Status Service Roles
resource "google_project_iam_member" "status_lb_viewer" {
project = var.project_id
role = google_project_iam_custom_role.status_lb_viewer.name
member = "serviceAccount:${google_service_account.status.email}"
}
resource "google_project_iam_member" "status_run_viewer" {
project = var.project_id
role = "roles/run.viewer"
member = "serviceAccount:${google_service_account.status.email}"
}
GitHub Actions Deploy Service Account
resource "google_project_iam_member" "github_actions_roles" {
for_each = toset([
"roles/artifactregistry.writer",
"roles/run.developer",
])
project = var.project_id
role = each.value
member = "serviceAccount:${google_service_account.github_actions.email}"
}
resource "google_service_account_iam_member" "github_actions_sa_user_microservice" {
for_each = local.service_accounts
service_account_id = each.value.name
role = "roles/iam.serviceAccountUser"
member = "serviceAccount:${google_service_account.github_actions.email}"
}
4. Secret Manager Access
Secrets Inventory
| Secret Name | Accessible By | Description |
|---|---|---|
gateway-database-url | All except status | Spanner JDBC connection URL |
gateway-internal-api-shared-secret | management, online-txn, processing | Inter-service authentication secret |
gateway-fcm-service-account-json | management | Firebase Admin SDK service-account JSON for terminal-command FCM dispatch |
transit-developer-id | processing | TransIT API developer identifier |
transit-developer-key | processing | TransIT API authentication key |
oauth-signing-key | auth | Key for signing OAuth JWTs |
Per-Secret IAM (Fine-Grained)
resource "google_secret_manager_secret_iam_member" "database_url" {
for_each = local.db_services
secret_id = var.database_url_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
resource "google_secret_manager_secret_iam_member" "internal_api_shared_secret" {
for_each = local.internal_api_secret_callers
secret_id = var.internal_api_shared_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${local.service_accounts[each.key].email}"
}
resource "google_secret_manager_secret_iam_member" "transit_developer_id_processing" {
secret_id = var.transit_developer_id_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.processing.email}"
}
resource "google_secret_manager_secret_iam_member" "oauth_signing_key_auth" {
secret_id = var.oauth_signing_key_secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.auth.email}"
}
5. Service-to-Service Authentication
Cloud Run services authenticate to each other using GCP IAM identity:
# Allow management service to invoke processing service
resource "google_cloud_run_v2_service_iam_member" "management_to_processing" {
project = var.project_id
location = var.region
name = "gateway-processing"
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.management.email}"
}
# Allow management service to invoke auth service
resource "google_cloud_run_v2_service_iam_member" "management_to_auth" {
project = var.project_id
location = var.region
name = "gateway-auth"
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.management.email}"
}
No public access to the Processing Microservice, it is only callable by other internal services.
6. Human Access
| Role | GCP IAM Role | Scope |
|---|---|---|
| Platform Engineer | roles/editor | Full project access |
| Developer | roles/viewer + roles/run.developer | View + deploy |
| On-call | roles/viewer + roles/logging.viewer | View + logs |
| Security Auditor | roles/iam.securityReviewer + roles/logging.viewer | Audit |
Workload Identity Federation (for GitHub Actions)
resource "google_iam_workload_identity_pool" "github" {
project = var.project_id
workload_identity_pool_id = "gateway-github-actions"
display_name = "Gateway GitHub Actions"
}
resource "google_iam_workload_identity_pool_provider" "github" {
project = var.project_id
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "gateway-github-provider"
display_name = "Gateway GitHub OIDC"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
"attribute.ref" = "assertion.ref"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
attribute_condition = "assertion.repository == '${var.github_repository}'"
}
resource "google_service_account_iam_member" "github_wif_impersonation" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${var.github_repository}"
condition {
title = "main-branch-only"
description = "Allow WIF impersonation from main branch"
expression = "attribute.repository == '${var.github_repository}' && attribute.ref == 'refs/heads/main'"
}
}