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, 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}"
}
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 |
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'"
}
}