Skip to content

IAM

floci-gcp emulates Google Cloud IAM over REST JSON and the shared IAM policy gRPC mixin.

Configuration

Variable Default Description
FLOCI_GCP_SERVICES_IAM_ENABLED true Enable/disable IAM
FLOCI_GCP_SERVICES_IAM_AUTHORIZATION_MODE disabled Set to enforce to evaluate allow policies for the supported services below
FLOCI_GCP_SERVICES_IAM_BOOTSTRAP_ADMIN_MEMBER unset Optional IAM member granted roles/storage.admin on each newly created bucket

Opt-in enforcement

Set FLOCI_GCP_SERVICES_IAM_AUTHORIZATION_MODE=enforce, or floci-gcp.services.iam.authorization-mode: enforce in YAML. The default is disabled: stored policies do not restrict requests. Startup logs always report which mode is active and the supported service list.

Surface IAM enforcement in enforce mode
Resource Manager v1 project metadata and project policies REST JSON; project policies also use the shared IAM gRPC mixin
GCS buckets Supported REST bucket metadata, IAM-policy, retention-lock, storage-layout, and notification operations; bucket testIamPermissions filters its response
GCS objects Supported REST JSON and XML reads, writes, updates, deletes, listing, compose, copy, rewrite, move, restore, resumable uploads, and XML multipart uploads
Pub/Sub Not IAM-enforced. Service enforcement is deferred
Secret Manager Not IAM-enforced. Service enforcement is deferred
GCS ACLs and GCS gRPC Not IAM-enforced. Existing downscoped-token CAB checks still apply
IAM service-account/key management, IAM Credentials, Cloud Run, all other services Not IAM-enforced, including any policies those services store

Valid Floci-issued service-account tokens activate IAM evaluation on every supported surface. Mint one through the existing IAM Credentials generateAccessToken endpoint and pass it as Authorization: Bearer TOKEN or through your SDK credentials provider. For plaintext gRPC, Google credentials may refuse an insecure channel before the request reaches the emulator; use a fixed authorization header with the SDK's no-credentials provider, or use the emulator's TLS transport. Anonymous requests and external credentials bypass Resource Manager evaluation. GCS evaluation treats those callers as anonymous so allUsers policies work. In enforce mode, an unknown or expired Floci-issued token receives UNAUTHENTICATED; an evaluated caller without a matching grant receives 403 PERMISSION_DENIED over REST or PERMISSION_DENIED over gRPC. GCS applies the Credential Access Boundary before IAM and retains the source principal for IAM evaluation of valid downscoped credentials. Downscoped tokens cannot expand their boundary into project or bucket management permissions.

This is a local permission-testing tool, not an authentication/security boundary. Token minting and impersonation remain unrestricted. A test using anonymous or external credentials can still pass without exercising IAM.

The framework supports child-to-project inheritance and multiple required permission checks. Bucket evaluation includes the owning project's policy; the project cannot be inferred from the projects/_/buckets/... resource name and is read from the stored bucket metadata. Project bindings do not authorize or restrict Pub/Sub, Secret Manager, or other services without a registered adapter. testIamPermissions evaluates each requested permission for the same caller and resource; missing resources keep the existing empty-result behavior. Disabled mode and bypass callers retain the existing permission echo.

Bucket policies inherit to objects. Restore requires storage.objects.restore and storage.objects.create, plus storage.objects.delete when it replaces a live object. Move accepts storage.objects.move on the source, or both storage.objects.get and storage.objects.delete, and requires storage.objects.create on the destination. Resumable-upload permissions are established when the session is opened; the session URI then acts as the authorization token for status queries and chunks, including requests without an Authorization header.

XML multipart upload IDs are not authentication tokens. Every initiate, part upload, completion, list, list-parts, and abort request evaluates its documented storage.multipartUploads.* permission. Uploading parts and completing an upload also require storage.objects.create; completion requires storage.objects.delete when it replaces a live object.

For a downscoped token derived from a Floci-issued IAM Credentials impersonated token, Floci preserves the source service-account identity. Object requests first satisfy the token's Credential Access Boundary (CAB), then satisfy the bucket policy for that service account. Consequently, a bucket policy cannot extend a CAB grant, and a CAB cannot extend a bucket-policy grant. A downscoped token from an external source credential has no named IAM identity and, in enforce mode, can match only an allUsers binding.

Bucket enforcement bootstrap

When Floci can resolve a new bucket's caller from a valid Floci-issued impersonated token, it persists a bucket-level roles/storage.admin binding for that service-account member. This lets the creator manage the bucket after the emulator is started with authorization-mode: enforce.

For callers without a resolvable identity, set FLOCI_GCP_SERVICES_IAM_BOOTSTRAP_ADMIN_MEMBER to an IAM member such as serviceAccount:admin@example.iam.gserviceaccount.com. That member receives roles/storage.admin on every subsequently created bucket. Treat this setting as an administrator credential: it is deliberately powerful, and an allUsers value makes every new bucket publicly manageable. The setting accepts only serviceAccount: members, allAuthenticatedUsers, or allUsers.

Supported roles and conditions

The finite catalog includes the implemented-operation permissions from:

  • roles/browser and roles/resourcemanager.projectIamAdmin.
  • roles/owner, roles/editor, and roles/viewer, restricted to the declared project permissions. Editor/viewer permit project metadata and policy reads, but not project policy writes.

In enforce mode, project policies cannot contain allUsers or allAuthenticatedUsers. Such bindings produce INVALID_ARGUMENT on policy writes and when evaluating previously stored project policies. Disabled mode retains permissive policy storage.

The finite catalog also contains roles/storage.objectViewer, roles/storage.objectCreator, roles/storage.objectAdmin, and roles/storage.admin for the explicitly enforced bucket and object permissions. Uncatalogued roles grant no permissions and do not block known bindings, including when they apply to the caller. Missing resource/operation mappings on an enforcing adapter produce a named FAILED_PRECONDITION. The shared google.iam.v1.IAMPolicy gRPC mixin enforces project policy resources and rejects resource kinds without a registered mapping for recognized callers in enforce mode, including Pub/Sub policy calls routed through that mixin. Those policy APIs support anonymous setup calls. Unsupported CEL expressions produce INVALID_ARGUMENT; they are not silently treated as a policy denial.

Version 3 bindings use the existing restricted IAM Conditions profile, including resource service/type/name, string comparisons and prefixes, logical operators, and request-time comparison. The CEL standard library is not enabled. The catalog and condition profile are subsets of Google Cloud IAM.

Remaining limitations

  • Object-list conditions authorize the bucket-level storage.objects.list permission but do not filter returned objects.
  • GCS gRPC and ACL operations are not restricted by IAM allow policies. Signed-URL identity is also outside the evaluator: because the emulator does not cryptographically verify V4 signatures, signed URLs are treated as anonymous and can access only resources granted to allUsers in enforce mode.
  • Conditional GCS bindings require UBLA. A bucket update cannot disable, remove, or partially clear UBLA while conditional bindings remain configured. The full UBLA lifecycle is not implemented.
  • Organization/folder inheritance, deny policies, principal access boundaries, custom roles, group/domain membership expansion, workforce/workload identities, and organization policy constraints are not implemented.
  • Resource names are evaluated as requested. Project ID/number aliases are not canonicalized to equivalent numeric resource names.
  • Only declared operation permissions are checked. Dependent service-agent, actAs, push-authentication, BigQuery/Storage export, CMEK, and other cross-service grants are not validated. This emulator cannot prove those configurations work in GCP.
  • Policy reads and business mutations are separate operations. There is no atomic policy-change/resource-mutation transaction; this is not a revocation-timing test.
  • The emulator does not validate that a role is grantable on the policy resource. Unsupported permissions in a known role are absent from the catalog.

See Adding an enforcing service for the shared extension contract, test requirements, and upstream evidence.

Quick Start

gcloud config set project floci-local

# Create a service account
gcloud iam service-accounts create my-sa \
    --display-name="My Service Account"

# List service accounts
gcloud iam service-accounts list

# Create a key
gcloud iam service-accounts keys create key.json \
    --iam-account=my-sa@floci-local.iam.gserviceaccount.com

# Delete a key
gcloud iam service-accounts keys delete KEY_ID \
    --iam-account=my-sa@floci-local.iam.gserviceaccount.com
# Create service account
curl -X POST http://localhost:4588/v1/projects/floci-local/serviceAccounts \
  -H "Content-Type: application/json" \
  -d '{"accountId":"my-sa","serviceAccount":{"displayName":"My SA"}}'

# List service accounts
curl http://localhost:4588/v1/projects/floci-local/serviceAccounts

# Get service account
curl http://localhost:4588/v1/projects/floci-local/serviceAccounts/my-sa@floci-local.iam.gserviceaccount.com

# Delete service account
curl -X DELETE http://localhost:4588/v1/projects/floci-local/serviceAccounts/my-sa@floci-local.iam.gserviceaccount.com

Service Accounts

Service accounts follow the GCP naming convention:

projects/{project}/serviceAccounts/{account}@{project}.iam.gserviceaccount.com

The - wildcard can replace the project ID in any service account or key name (projects/-/serviceAccounts/{EMAIL_ADDRESS|UNIQUE_ID}), which is what Terraform sends. The owning project is resolved from the account, and resource names in responses carry the real project. As on GCP, a missing account addressed through - returns 403 PERMISSION_DENIED instead of 404 NOT_FOUND.

Service Account Keys

# Create key
curl -X POST \
  http://localhost:4588/v1/projects/floci-local/serviceAccounts/my-sa@floci-local.iam.gserviceaccount.com/keys \
  -H "Content-Type: application/json" \
  -d '{}'

# List keys
curl http://localhost:4588/v1/projects/floci-local/serviceAccounts/my-sa@floci-local.iam.gserviceaccount.com/keys

IAM Policy Bindings

# Grant Secret Manager access to a service account
gcloud secrets add-iam-policy-binding my-secret \
    --member="serviceAccount:my-sa@floci-local.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

Sign Blob (V4 Signed URLs)

The IAM SignBlob endpoint is used by the GCS SDK to generate V4 pre-signed URLs:

URL signedUrl = storage.signUrl(
    BlobInfo.newBuilder("my-bucket", "hello.txt").build(),
    15, TimeUnit.MINUTES,
    Storage.SignUrlOption.withV4Signature());

SignBlob accepts the bytes to sign and returns a stub signature, which is sufficient for local development.

Supported Operations

  • CreateServiceAccount
  • GetServiceAccount
  • ListServiceAccounts
  • DeleteServiceAccount
  • CreateServiceAccountKey (real RSA-2048 key pair; returns JSON key file)
  • GetServiceAccountKey
  • ListServiceAccountKeys
  • DeleteServiceAccountKey
  • GetIamPolicy
  • SetIamPolicy
  • TestIamPermissions
  • SignBlob

generateAccessToken (the iamcredentials.googleapis.com API used by ImpersonatedCredentials) is provided by the separate IAM Credentials service, toggled with FLOCI_GCP_SERVICES_IAMCREDENTIALS_ENABLED.