Skip to main content

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 AccountUsed ByPurpose
gateway-processing@{project}.iamProcessing MicroserviceTransIT API calls, DB access, Pub/Sub publish, Secret Manager
gateway-management@{project}.iamManagement MicroserviceDB access, Firebase Admin, Identity Platform, Secret Manager
gateway-online-txn@{project}.iamOnline Txn MicroserviceDB access, Pub/Sub, App Check, Secret Manager
gateway-merchant-onboarding@{project}.iamMerchant Onboarding MicroserviceMerchant activation, XTMS API, App Check, Secret Manager
gateway-auth@{project}.iamGateway Auth ServiceOAuth token issuance, App Check, Secret Manager
gateway-status@{project}.iamStatus MicroserviceHealth checks, LB/Run viewer
gateway-github-actions@{project}.iamGitHub Actions CI/CDDeploy 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 NameAccessible ByDescription
gateway-database-urlAll except statusSpanner JDBC connection URL
gateway-internal-api-shared-secretmanagement, online-txn, processingInter-service authentication secret
transit-developer-idprocessingTransIT API developer identifier
transit-developer-keyprocessingTransIT API authentication key
oauth-signing-keyauthKey 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

RoleGCP IAM RoleScope
Platform Engineerroles/editorFull project access
Developerroles/viewer + roles/run.developerView + deploy
On-callroles/viewer + roles/logging.viewerView + logs
Security Auditorroles/iam.securityReviewer + roles/logging.viewerAudit

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