DEPRECATED — This repository has been superseded by gcp-emulator, which composes all Blackwell GCP emulators (IAM, Secret Manager, KMS, Eventarc) into a single process on one gRPC port. The unified emulator provides the same IAM enforcement, policy management, and CLI features — plus in-process composition (no docker-compose), a unified grpc-gateway REST layer, and a single Docker image.
Migration: Replace
go install github.com/blackwell-systems/gcp-iam-control-plane/cmd/gcp-emulator@latestwithgo install github.com/blackwell-systems/gcp-emulator/cmd/gcp-emulator@latest. See the gcp-emulator README for updated CLI flags and configuration.This repository is archived and will receive no further updates.
Enforce IAM authorization policies in local development and CI — Make your emulators fail for the same authorization reasons production would.
Orchestrates the Local IAM Control Plane — a CLI (gcp-emulator) that manages GCP service emulators with pre-flight IAM enforcement. Start/stop services, manage policies, and test authorization without cloud credentials or docker-compose knowledge.
Unlike mocks (which allow everything) or observers (which record after the fact), the Blackwell IAM Control Plane actively denies unauthorized requests before they reach emulators.
| Approach | Example | When | Behavior |
|---|---|---|---|
| Mock | Standard emulators | Never | Always allows |
| Observer | Post-execution analysis | After | Records what you used |
| Control Plane | Blackwell IAM | Before | Denies unauthorized |
Pre-flight enforcement catches permission bugs in development and CI, not production.
Before Blackwell, "GCP Hermetic Testing" was essentially impossible.
While Google has long provided emulators for individual data plane services (Pub/Sub, Spanner, BigTable), they intentionally left a massive hole where the Identity Layer should be.
The two bad options you had:
- Fake Auth - Official emulators ignore permissions (tests pass locally, fail in production)
- Staging Leak - Call real GCP IAM API (hermetic seal broken, tests become flaky with 1-60s propagation delays)
Blackwell closes the hermetic seal:
This control plane provides what was missing:
- Deterministic IAM - Strongly consistent (0ms delay vs 1-60s in real GCP)
- Offline Authorization - Services check permissions locally (no network required)
- Zero-Credential Loop - Simulate identities in-memory (no service account keys)
The result: Tests fail for the same authorization reasons production would, run completely offline, and execute deterministically.
The Security Paradox:
"A test that cannot fail due to a permission error is a test that has not fully validated the code's production readiness."
This is not an incremental improvement. This fills the missing identity-layer gap in local hermetic emulator workflows.
┌─────────────────────────────────────────┐
│ Your Application Code │
│ (GCP client libraries) │
└────────────────┬────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ DATA PLANES (Started by this CLI) │
│ • Secret Manager Emulator │
│ • KMS Emulator │
│ • (Future: Tasks, Pub/Sub, Storage) │
│ │
│ Each checks IAM before data access │
└────────────────┬────────────────────────┘
│
│ CheckPermission(principal, resource, permission)
▼
┌─────────────────────────────────────────┐
│ CONTROL PLANE (Policy Engine) │
│ IAM Emulator │
│ │
│ - Role bindings │
│ - Group memberships │
│ - Resource-level policy evaluation │
│ - Deterministic evaluation │
└─────────────────────────────────────────┘
This CLI orchestrates the IAM control plane and all data plane services.
Most GCP emulators skip authorization entirely. Without IAM testing, you discover permission bugs after deployment:
- Incorrect role assignments
- Missing permissions
- Wrong principal identity
- Policies that work in dev but fail in prod
This control plane makes IAM enforcement testable and deterministic, locally and in CI.
- Unified CLI - Single command to start/stop/restart the entire stack, no docker-compose knowledge required
- Policy import - Load IAM policies from JSON/YAML (including exports from tooling like
gcloud get-iam-policy) - One policy file - Define authorization once in
policy.yamlorpolicy.json, enforced consistently across all emulators - Principal injection - Consistent identity channel (gRPC
x-emulator-principal, HTTPX-Emulator-Principal) - Hermetic testing - No network calls, no cloud credentials, deterministic CI pipelines
- Multiple IAM modes - Off/permissive/strict for different testing scenarios
go install github.com/blackwell-systems/gcp-iam-control-plane/cmd/gcp-emulator@latestgcp-emulator startThis starts three services:
- IAM Emulator (control plane):
localhost:8080(gRPC) - Secret Manager Emulator (data plane):
localhost:9090(gRPC),localhost:8081(HTTP) - KMS Emulator (data plane):
localhost:9091(gRPC),localhost:8082(HTTP)
Edit policy.yaml (YAML or JSON supported):
roles:
roles/custom.developer:
permissions:
- secretmanager.secrets.get
- secretmanager.versions.access
groups:
developers:
members:
- user:alice@example.com
projects:
test-project:
bindings:
- role: roles/custom.developer
members:
- group:developersSee Policy Reference for complete policy syntax.
# Secret Manager HTTP API
curl -X POST http://localhost:8081/v1/projects/test-project/secrets \
-H "X-Emulator-Principal: user:alice@example.com" \
-H "Content-Type: application/json" \
-d '{"secretId":"db-password","payload":{"data":"c2VjcmV0"}}'
# Check authorization logs
gcp-emulator logs iamStop hand-writing test policies that drift from production.
Export your real GCP IAM policy and test against it:
# Export production policy
gcloud projects get-iam-policy my-prod-project --format=json > prod-policy.json
# Test locally with the exported policy
gcp-emulator start --policy-file=prod-policy.json --mode=strict
go test ./...
# Catch permission issues before deploying
gcp-emulator logs iam | grep DENYNote: Exporting policies with gcloud requires GCP credentials. Running the emulators and tests does not.
Why this matters:
- Test with exact production policy
- Catch permission issues in CI, not production
- No policy drift between environments
- Works with Terraform, CDK, or Console exports
Both YAML and JSON formats supported. JSON matches GCP's native IAM policy structure.
This control plane orchestrates multiple emulators:
| Component | Role | Status |
|---|---|---|
| gcp-iam-emulator | Authorization engine (control plane) | ✓ Stable |
| gcp-secret-manager-emulator | Secret Manager API (data plane) | ✓ Stable |
| gcp-kms-emulator | KMS API (data plane) | ✓ Stable |
All components can run standalone or orchestrated together. New emulators follow the Integration Contract.
# Stack management
gcp-emulator start [--mode=permissive|strict|off]
gcp-emulator stop
gcp-emulator status
gcp-emulator logs [service] [--follow]
# Policy management
gcp-emulator policy validate [file]
gcp-emulator policy init [--template=basic|advanced|ci] [--output=policy.yaml]
# Configuration
gcp-emulator config get
gcp-emulator config set <key> <value>See CLI Design for complete command reference.
- name: Install CLI
run: go install github.com/blackwell-systems/gcp-iam-control-plane/cmd/gcp-emulator@latest
- name: Start emulators (strict mode)
run: gcp-emulator start --mode=strict
- name: Run tests
run: go test ./...
- name: Check IAM logs for denials
if: failure()
run: gcp-emulator logs iam | grep DENY
- name: Stop emulators
if: always()
run: gcp-emulator stopIAM Modes:
off- No IAM enforcement (fast iteration)permissive- IAM enabled, fail-open on errors (development)strict- IAM enabled, fail-closed (CI-ready, recommended for CI)
See CI Integration for GitLab, CircleCI, Jenkins examples.
Structured logging of IAM authorization decisions across the entire emulator stack.
# Start stack with tracing enabled
IAM_TRACE_OUTPUT=./authz-trace.jsonl gcp-emulator start --mode=strict
# Or export environment variable
export IAM_TRACE_OUTPUT=./authz-trace.jsonl
gcp-emulator start --mode=strictEvery authorization check across all services emits structured events:
- IAM Emulator (control plane) - Policy engine decisions (authoritative)
- Secret Manager - Secret access checks (via enforcement proxy)
- KMS - Key operation checks (via enforcement proxy)
Each service emits JSONL events with principal, resource, permission, outcome, and timing.
Debug Cross-Service Authorization:
# See all denied requests across the stack
cat authz-trace.jsonl | jq 'select(.decision.outcome=="DENY")'
# Filter by service
cat authz-trace.jsonl | jq 'select(.environment.component=="gcp-iam-emulator")'Audit CI Access Patterns:
# List all resources accessed during test run
cat authz-trace.jsonl | \
jq -r 'select(.decision.outcome=="ALLOW") | .target.resource' | sort -u
# See which principals were tested
cat authz-trace.jsonl | jq -r '.actor.principal' | sort -uValidate Policy Changes:
# Before policy change
IAM_TRACE_OUTPUT=./before.jsonl gcp-emulator start --mode=strict
go test ./...
gcp-emulator stop
# After policy change
IAM_TRACE_OUTPUT=./after.jsonl gcp-emulator start --mode=strict
go test ./...
# Compare outcomes (detect regressions)
diff <(jq -r '.decision.outcome' before.jsonl | sort) \
<(jq -r '.decision.outcome' after.jsonl | sort)CI/CD Compliance:
# GitHub Actions - archive traces as artifacts
- name: Run tests with tracing
env:
IAM_TRACE_OUTPUT: ${{ runner.temp }}/authz-trace.jsonl
run: |
gcp-emulator start --mode=strict
go test ./...
- name: Upload authorization audit trail
uses: actions/upload-artifact@v3
with:
name: authorization-traces
path: ${{ runner.temp }}/authz-trace.jsonlEvents follow schema v1.0:
- JSONL format (one event per line)
- Schema-versioned for compatibility
- Includes actor, resource, permission, decision, timing, component
Example event:
{"schema_version":"1.0","event_type":"authz_check","timestamp":"2026-01-28T10:15:23.483Z","actor":{"principal":"user:alice@example.com"},"target":{"resource":"projects/test/secrets/db-password"},"action":{"permission":"secretmanager.secrets.get"},"decision":{"outcome":"ALLOW","reason":"binding_match","evaluated_by":"gcp-iam-emulator","latency_ms":3}}See component READMEs for detailed schema documentation:
Control Plane (IAM Emulator):
- Evaluates authorization policy from
policy.yaml - Expands roles, resolves groups, evaluates conditions
- Returns allow/deny decisions
- Stateless - doesn't know about secrets or keys
Data Plane (Secret Manager, KMS):
- Implements CRUD operations
- Checks permissions by calling IAM emulator
- Stores resources in-memory
- Enforces decisions from control plane
This architecture matches real GCP. Secrets/keys live in the data plane. Authorization logic lives in the control plane. Testing this separation catches production bugs.
See Architecture for detailed system design.
Mocks don't test:
- Policy inheritance - Project-level bindings affecting resources
- Conditional access - CEL expressions restricting by resource name
- Cross-service consistency - Same policy engine for all services
- Permission drift - Real GCP permission names
This stack tests actual control plane behavior.
If you prefer direct orchestration:
docker compose up -d # Start stack
docker compose logs # View logs
docker compose down # Stop stackThe CLI wraps these commands with policy validation, status checks, and unified logging.
Ready-made role definitions in packs/:
# Copy Secret Manager roles
cat packs/secretmanager.yaml >> policy.yaml
# Copy KMS roles
cat packs/kms.yaml >> policy.yaml
# Copy CI patterns
cat packs/ci.yaml >> policy.yamlSee Policy Reference for available packs and examples.
- Policy Reference - Complete policy syntax, conditions, permissions, examples
- CI Integration - GitHub Actions, GitLab, CircleCI, Jenkins examples
- Architecture - Control plane design, request flow, authorization model
- Integration Contract - Contract for building new emulators
- CLI Design - CLI implementation and Viper pattern
- End-to-End Tutorial - Complete usage walkthrough
- Troubleshooting - Common issues and solutions
- Migration Guide - Migrating from standalone emulators
Restrict CI to production secrets only:
roles:
roles/custom.ciRunner:
permissions:
- secretmanager.secrets.get
- secretmanager.versions.access
projects:
test-project:
bindings:
- role: roles/custom.ciRunner
members:
- serviceAccount:ci@test-project.iam.gserviceaccount.com
condition:
expression: 'resource.name.startsWith("projects/test-project/secrets/prod-")'
title: "CI limited to production secrets"Now CI can only access secrets starting with prod-:
- ✓
projects/test-project/secrets/prod-db-password- Allowed - ✗
projects/test-project/secrets/dev-api-key- Denied (403)
See Policy Reference for CEL condition syntax.
The IAM control plane is deliberately scoped for authorization testing, not comprehensive IAM replication. The underlying IAM emulator models a small set of built-in roles (primitives + Secret Manager + KMS) plus unlimited custom role definitions to catch the bugs that actually break production: missing permissions, wrong role assignments, and misconfigured principals. This curated-first approach catches 95% of real-world authorization bugs while maintaining hermetic execution (no GCP credentials required), deterministic behavior (0ms propagation delay vs 1-60s in real GCP), and zero maintenance burden from tracking GCP's evolving role catalog. If you need to test additional GCP services or permissions, define them explicitly in policy.yaml as custom roles — this explicit approach is simpler, more reliable, and avoids the catalog staleness problem that plagues comprehensive IAM emulation. We optimize for authorization failures that matter, not theoretical IAM completeness.
No. Define only the permissions your tests actually exercise. Start with the built-in roles (primitives, Secret Manager, KMS), then add custom roles as needed. This keeps your test policies explicit and maintainable.
Yes! Export your production policy with gcloud projects get-iam-policy and use it directly:
gcloud projects get-iam-policy my-prod-project --format=json > prod-policy.json
gcp-emulator start --policy-file=prod-policy.json --mode=strictThis tests your code against real production permissions without network calls or credentials.
IAM enforcement is opt-in. Default behavior matches standalone emulators:
IAM_MODE=off- All requests succeed (legacy behavior)IAM_MODE=permissive- IAM enabled, fail-open on errorsIAM_MODE=strict- IAM enabled, fail-closed
Non-breaking by design.
If you're using the GCP emulator control plane — in CI, locally, or in a test harness — I'd love to hear how you're using it.
- How do you orchestrate the emulators? (via this CLI, docker-compose directly, custom scripts)
- What's your policy.yaml approach? (single file for all services, multiple files, generated dynamically)
- Which emulators are you running? (IAM + Secret Manager, IAM + KMS, full stack)
- What's still friction? (multi-service coordination, log aggregation, IAM mode transitions)
Open an issue, start a discussion, or reach out directly:
This helps shape the roadmap and ensures the project stays aligned with real-world needs.
This project is not affiliated with, endorsed by, or sponsored by Google LLC. "Google Cloud", "GCP", "IAM", and related trademarks are property of Google LLC.
Dayna Blackwell — Founder, Blackwell Systems