emulatorauth

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 6, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

Local IAM Control Plane — Enforcement Proxy

Blackwell Systems CI Coverage Go Reference Go Version License

Enforce real GCP IAM policies before requests reach emulators — make local environments fail exactly like production.

This is the enforcement proxy component of the Blackwell Local IAM Control Plane. It sits between your application and service emulators (Secret Manager, KMS, etc.), checking permissions before allowing data access.

What This Is

Unlike mocks (which allow everything) or observers (which record after the fact), this library actively denies unauthorized requests using real IAM policy evaluation.

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.

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."

Standard GCP emulators give you a false sense of security. Your integration tests pass with flying colors locally, but the moment you deploy to production, you hit PermissionDenied errors because:

  • You forgot to grant the service account the right role
  • Your conditional policy doesn't match what you thought
  • A permission was removed in a recent GCP update

This library solves that:

By enforcing IAM policies before data access, your tests fail during development the exact same way they would fail in production. This means:

  • No more "Friday night IAM outages"
  • Security reviews happen before merge, not after deploy
  • Permission bugs are caught in CI, not in incident postmortems

This is the enforcement proxy that closes the hermetic seal on GCP testing.

Architecture

┌─────────────────────────────────────────┐
│  Your Application Code                  │
│  (GCP client libraries)                 │
└────────────────┬────────────────────────┘
                 │
                 ▼
┌─────────────────────────────────────────┐
│  DATA PLANE                             │
│  Service Emulator (Secret Manager, KMS) │
│                                         │
│  Uses THIS LIBRARY to:                  │
│  1. Extract principal from request      │
│  2. Check permission                    │
│  3. Deny if unauthorized                │
└────────────────┬────────────────────────┘
                 │
                 │ CheckPermission(principal, resource, permission)
                 ▼
┌─────────────────────────────────────────┐
│  CONTROL PLANE                          │
│  IAM Emulator (Policy Engine)           │
│                                         │
│  - Role bindings                        │
│  - Group memberships                    │
│  - Policy inheritance                   │
└─────────────────────────────────────────┘

This library is used by all Blackwell service emulators to provide consistent IAM enforcement.

Who Uses This

Service emulator developers: This library enforces IAM policies in your emulator.

End users: Use the control plane CLI instead — it manages the entire stack for you.

Currently integrated:

Building a new emulator? See the Integration Contract.

Installation

go get github.com/blackwell-systems/gcp-emulator-auth

Usage

Basic Setup
import (
    emulatorauth "github.com/blackwell-systems/gcp-emulator-auth"
)

func main() {
    // Load config from environment
    config := emulatorauth.LoadFromEnv()
    
    if config.Mode.IsEnabled() {
        // Connect to IAM emulator
        iamClient, err := emulatorauth.NewClient(config.Host, config.Mode)
        if err != nil {
            log.Fatal(err)
        }
        defer iamClient.Close()
        
        // Use in handlers...
    }
}
In gRPC Handler
func (s *Server) GetSecret(ctx context.Context, req *pb.GetSecretRequest) (*pb.Secret, error) {
    // Extract principal from incoming request
    principal := emulatorauth.ExtractPrincipalFromContext(ctx)
    
    // Check permission if IAM is enabled
    if s.iamClient != nil {
        allowed, err := s.iamClient.CheckPermission(
            ctx,
            principal,
            req.Name, // resource
            "secretmanager.secrets.get", // permission
        )
        if err != nil {
            return nil, status.Error(codes.Internal, "IAM check failed")
        }
        if !allowed {
            return nil, status.Error(codes.PermissionDenied, "Permission denied")
        }
    }
    
    // Proceed with operation
    return s.storage.GetSecret(req.Name)
}
In HTTP Handler
func (s *Server) handleGetSecret(w http.ResponseWriter, r *http.Request) {
    // Extract principal from HTTP header
    principal := emulatorauth.ExtractPrincipalFromRequest(r)
    
    // Check permission if IAM is enabled
    if s.iamClient != nil {
        allowed, err := s.iamClient.CheckPermission(
            r.Context(),
            principal,
            resourceName,
            "secretmanager.secrets.get",
        )
        if err != nil {
            http.Error(w, "IAM check failed", http.StatusInternalServerError)
            return
        }
        if !allowed {
            http.Error(w, "Permission denied", http.StatusForbidden)
            return
        }
    }
    
    // Proceed with operation
    // ...
}

Environment Variables

Variable Purpose Default Values
IAM_MODE Authorization mode off off, permissive, strict
IAM_EMULATOR_HOST IAM emulator gRPC endpoint localhost:8080 host:port
IAM_TRACE Enable IAM decision logging false true, false

Auth Modes

Off (default)

No IAM checks, all requests allowed (legacy behavior).

# Default - no environment variables needed
./server
Permissive

IAM checks enabled with fail-open behavior:

  • IAM reachable → enforce permissions
  • IAM unreachable → allow (fail-open)
  • Config errors → deny
IAM_MODE=permissive IAM_EMULATOR_HOST=localhost:8080 ./server

Use for: Development environments where IAM might not always be running.

Strict

IAM checks enabled with fail-closed behavior:

  • IAM reachable → enforce permissions
  • IAM unreachable → deny (fail-closed)
  • Config errors → deny
IAM_MODE=strict IAM_EMULATOR_HOST=localhost:8080 ./server

Use for: CI/CD to catch permission issues before production.

Authorization Tracing

Structured logging of authorization decisions for debugging and audit trails.

Enable Tracing
# Emit traces to file
IAM_TRACE_OUTPUT=./authz-trace.jsonl IAM_MODE=strict ./your-service

# Or to stdout
IAM_TRACE_OUTPUT=stdout IAM_MODE=strict ./your-service
What Gets Traced

Every CheckPermission call emits:

  • authz_check event - permission check outcome (ALLOW/DENY)
  • authz_error event - IAM failures (unreachable, timeout, config errors)
Use Cases

Debug Why Access Failed:

# See denied permissions
cat authz-trace.jsonl | jq 'select(.decision.outcome=="DENY")'

Audit What Was Accessed:

# List all resources accessed during test run
cat authz-trace.jsonl | \
  jq -r 'select(.decision.outcome=="ALLOW") | .target.resource' | sort -u

Performance Analysis:

# Check IAM call latency
cat authz-trace.jsonl | jq '.decision.latency_ms' | \
  awk '{sum+=$1; n++} END {print "Avg:", sum/n, "ms"}'

CI/CD Audit Trail:

# Archive traces for compliance
IAM_TRACE_OUTPUT=$CI_ARTIFACTS_DIR/authz-$(date +%Y%m%d).jsonl go test ./...
Trace Schema

Events follow schema v1.0 from pkg/trace:

  • JSONL format (one event per line)
  • Schema-versioned for compatibility
  • Includes principal, resource, permission, outcome, timing

See pkg/trace/types.go for complete schema definition.

Error Handling

The package classifies errors into three categories:

1. Permission Denied (Normal)

IAM evaluated and denied permission. Always deny.

2. Connectivity Errors

IAM unreachable, timeout, or cancelled:

  • Permissive mode: Allow (fail-open)
  • Strict mode: Deny (fail-closed)
3. Configuration Errors

Invalid resource, bad permission format, internal errors:

  • Both modes: Deny (indicates bug/misconfiguration)

API Reference

Functions
ParseAuthMode(s string) AuthMode

Parse auth mode from string (case-insensitive).

LoadFromEnv() Config

Load configuration from environment variables.

ExtractPrincipalFromContext(ctx context.Context) string

Extract principal from gRPC incoming metadata.

ExtractPrincipalFromRequest(r *http.Request) string

Extract principal from HTTP request header.

InjectPrincipalToContext(ctx context.Context, principal string) context.Context

Add principal to outgoing gRPC metadata.

IsConnectivityError(err error) bool

Check if error is due to connectivity issues.

IsConfigError(err error) bool

Check if error indicates configuration problem.

Types
type AuthMode string

Authorization mode: off, permissive, or strict.

type Config struct

IAM emulator configuration.

type Client struct

IAM emulator client for permission checks.

Maintained By

Maintained by Dayna Blackwell — founder of Blackwell Systems, building reference infrastructure for cloud-native development.

GitHub · LinkedIn · Blog

Trademarks

Blackwell Systems™ and the Blackwell Systems logo are trademarks of Dayna Blackwell. You may use the name "Blackwell Systems" to refer to this project, but you may not use the name or logo in a way that suggests endorsement or official affiliation without prior written permission. See BRAND.md for usage guidelines.


Who's Using This?

If you're integrating this library into your own emulator or service — I'd love to hear how it's working.

  • What service are you building? (custom GCP emulator, internal IAM-aware service, testing framework)
  • How smooth was the proxy integration? (wrapped existing gRPC in hours, or hit sharp edges)
  • Which features do you rely on? (basic ALLOW/DENY, conditional policies, trace generation, group expansion)
  • What's still friction? (principal extraction patterns, error handling, performance overhead)

Open an issue, start a discussion, or reach out directly:

📬 dayna@blackwell-systems.com

This helps shape the roadmap and ensures the library stays aligned with emulator developer needs.


License

Apache License 2.0 - See LICENSE for details.

Documentation

Index

Constants

View Source
const (
	// PrincipalMetadataKey is the gRPC metadata key for principal identity
	PrincipalMetadataKey = "x-emulator-principal"

	// PrincipalHeaderKey is the HTTP header key for principal identity
	PrincipalHeaderKey = "X-Emulator-Principal"
)

Variables

This section is empty.

Functions

func ExtractPrincipalFromContext

func ExtractPrincipalFromContext(ctx context.Context) string

ExtractPrincipalFromContext extracts the principal from gRPC incoming metadata

func ExtractPrincipalFromRequest

func ExtractPrincipalFromRequest(r *http.Request) string

ExtractPrincipalFromRequest extracts the principal from HTTP request header

func InjectPrincipalToContext

func InjectPrincipalToContext(ctx context.Context, principal string) context.Context

InjectPrincipalToContext adds the principal to outgoing gRPC metadata

func IsConfigError

func IsConfigError(err error) bool

IsConfigError returns true if the error indicates a configuration problem that should always deny (in both permissive and strict modes)

func IsConnectivityError

func IsConnectivityError(err error) bool

IsConnectivityError returns true if the error is due to connectivity issues (IAM emulator unreachable, timeout, or cancelled context)

Types

type AuthMode

type AuthMode string

AuthMode defines how IAM authorization is enforced

const (
	// AuthModeOff disables IAM checks entirely (legacy behavior, default)
	AuthModeOff AuthMode = "off"

	// AuthModePermissive enables IAM checks with fail-open behavior:
	// - IAM reachable: enforce permissions
	// - IAM unreachable: allow (fail-open)
	// - Config errors: deny
	// Use for development where IAM might not always be running
	AuthModePermissive AuthMode = "permissive"

	// AuthModeStrict enables IAM checks with fail-closed behavior:
	// - IAM reachable: enforce permissions
	// - IAM unreachable: deny (fail-closed)
	// - Config errors: deny
	// Recommended for CI/CD to catch permission issues
	AuthModeStrict AuthMode = "strict"
)

func ParseAuthMode

func ParseAuthMode(s string) AuthMode

ParseAuthMode parses an auth mode from string (case-insensitive)

func (AuthMode) IsEnabled

func (m AuthMode) IsEnabled() bool

IsEnabled returns true if IAM checks are enabled (not off)

func (AuthMode) String

func (m AuthMode) String() string

String returns the string representation of the auth mode

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a lightweight IAM emulator client for permission checks

func NewClient

func NewClient(host string, mode AuthMode) (*Client, error)

NewClient creates a new IAM emulator client

func (*Client) CheckPermission

func (c *Client) CheckPermission(
	ctx context.Context,
	principal string,
	resource string,
	permission string,
) (bool, error)

CheckPermission checks if the principal has the given permission on the resource

func (*Client) Close

func (c *Client) Close() error

Close closes the IAM client connection

type Config

type Config struct {
	// Mode is the authorization mode (off, permissive, strict)
	Mode AuthMode

	// Host is the IAM emulator gRPC endpoint (host:port)
	Host string

	// Trace enables IAM decision logging
	Trace bool
}

Config holds IAM emulator configuration

func LoadFromEnv

func LoadFromEnv() Config

LoadFromEnv loads configuration from environment variables

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL