Repository navigation
Conversation
This was referenced Oct 6, 2026
Draft
This was referenced Nov 10, 2026
…ting job ordering
rkoster
force-pushed
the
service-accounts-poc
branch
from
November 11, 2026 10:44
c303938 to
f9d5880
Compare
rkoster
marked this pull request as ready for review
November 11, 2026 12:44
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Critical ownership and concurrency flaws can reuse or delete unrelated OAuth principals and permit service-account assignment after entitlement revocation.
10 open issues
Read entitlement membership freshly after locking the account · New Verify principal ownership before reuse or deletion · New Allow reservation cleanup without provisioning configuration · New Filter accounts by qualified identity before loading · New Eager-load account associations in the list · New Require writable space before entitlement revocation · New Index account GUIDs for reference checks · New Capture account identity only for managed service instances · New Capture account identity before binding creation · New Make partial migration recovery idempotent · New
What changed in this PR
Adds experimental service-account lifecycle and workload identity support to Cloud Controller. The implementation uses organization-owned accounts with explicit space entitlements, integrating account identities with UAA provisioning, Diego workloads, service brokers and route policies.
Changes:
- Adds account APIs, organization namespaces, entitlements, quotas, creation budgets and audit events.
- Adds asynchronous provisioning and cleanup, launch identity snapshots, broker context and account-based route sources.
- Adds database migrations, rollout documentation and request/unit coverage.
| File | Description |
|---|---|
spec/unit/service_account_migration_spec.rb |
Tests MySQL partial-migration collation recovery. |
spec/unit/models/runtime/service_account_ownership_spec.rb |
Tests ownership, entitlements and reservation statistics. |
spec/unit/models/runtime/service_account_model_spec.rb |
Tests identity validation and name reservations. |
spec/unit/models/runtime/service_account_assignment_spec.rb |
Tests assignment and ownership constraints. |
spec/unit/lib/services/service_brokers/v2/client_spec.rb |
Tests broker identity context and missing-account rejection. |
spec/unit/lib/cloud_controller/service_account_provisioner_configuration_spec.rb |
Tests provisioning configuration and token refresh. |
spec/unit/lib/cloud_controller/diego/task_recipe_builder_spec.rb |
Tests task certificate identity propagation. |
spec/unit/lib/cloud_controller/diego/task_environment_spec.rb |
Tests task account discovery metadata. |
spec/unit/lib/cloud_controller/diego/service_account_identity_spec.rb |
Tests runtime identity validation and serialization. |
spec/unit/lib/cloud_controller/diego/qualified_service_account_identity_spec.rb |
Tests qualified identities and contract gating. |
spec/unit/lib/cloud_controller/diego/environment_spec.rb |
Tests authoritative runtime discovery metadata. |
spec/unit/lib/cloud_controller/diego/app_recipe_builder_spec.rb |
Tests preservation of launch identity after unassignment. |
spec/unit/lib/cloud_controller/backends/staging_environment_builder_spec.rb |
Tests exclusion of account discovery from staging. |
spec/unit/jobs/v3/service_account_provision_spec.rb |
Tests job serialization, retries and lifecycle guards. |
spec/unit/actions/task_create_spec.rb |
Tests task identity snapshots and entitlement validation. |
spec/unit/actions/space_delete_spec.rb |
Tests account retention during space deletion. |
spec/unit/actions/service_credential_binding_app_create_spec.rb |
Adjusts the concurrent binding fixture. |
spec/unit/actions/service_account_registration_profile_spec.rb |
Tests UAA attribution and resource-policy registration. |
spec/unit/actions/service_account_quota_concurrency_spec.rb |
Tests quota freshness and concurrent creation. |
spec/unit/actions/service_account_provision_spec.rb |
Tests canonical registration, collisions and retries. |
spec/unit/actions/service_account_org_lifecycle_spec.rb |
Tests organization-owned account cleanup and retention. |
spec/unit/actions/service_account_creation_budget_spec.rb |
Tests concurrent per-principal creation limits. |
spec/unit/actions/service_account_binding_guard_spec.rb |
Tests identity-bearing binding lifecycle guards. |
spec/unit/actions/process_restart_spec.rb |
Tests restart-only identity changes. |
spec/unit/actions/organization_service_account_prefix_spec.rb |
Tests atomic namespace allocation. |
spec/unit/actions/organization_delete_spec.rb |
Tests account cleanup and deletion retries. |
spec/unit/actions/app_assign_service_account_spec.rb |
Tests assignment permissions and provisioning-job reuse. |
spec/request/spaces_spec.rb |
Updates embedded organization expectations. |
spec/request/service_accounts_spec.rb |
Tests account APIs, budgets and lifecycle operations. |
spec/request/service_account_route_policies_spec.rb |
Tests account route sources and grant cleanup. |
spec/request/service_account_roles_spec.rb |
Tests explicit resource-role authorization. |
spec/request/service_account_quotas_spec.rb |
Tests organization account quotas. |
spec/request/service_account_namespaces_spec.rb |
Tests qualified names, reuse and deletion history. |
spec/request/service_account_entitlements_spec.rb |
Tests entitlement authorization and revocation. |
spec/request/roles_spec.rb |
Updates embedded organization expectations. |
spec/request/organizations_spec.rb |
Adds namespace fields to response expectations. |
spec/request/organization_service_account_prefix_spec.rb |
Tests namespace allocation, validation and permissions. |
spec/request/organization_quotas_spec.rb |
Adds account quota response expectations. |
spec/request/apps_spec.rb |
Updates embedded organization expectations. |
spec/request/app_service_accounts_spec.rb |
Tests app assignment APIs and asynchronous provisioning. |
spec/migrations/service_account_namespace_rollback_spec.rb |
Tests namespace and assignment rollback. |
spec/migrations/20261109121000_add_service_account_organization_ownership_spec.rb |
Tests ownership and entitlement backfill. |
lib/services/service_brokers/v2/client.rb |
Sends captured account identity in broker context. |
lib/diego/bbs/models/certificate_properties_pb.rb |
Adds generated account certificate fields. |
lib/cloud_controller/uaa/service_account_client.rb |
Adds retrying UAA client-management operations. |
lib/cloud_controller/diego/task_recipe_builder.rb |
Adds task certificate identity properties. |
lib/cloud_controller/diego/task_environment.rb |
Adds authoritative task account discovery. |
lib/cloud_controller/diego/service_account_identity.rb |
Builds and validates runtime identity metadata. |
lib/cloud_controller/diego/environment.rb |
Adds process account discovery metadata. |
lib/cloud_controller/diego/app_recipe_builder.rb |
Adds process certificate identity properties. |
lib/cloud_controller/dependency_locator.rb |
Constructs the configured account provisioner. |
lib/cloud_controller/config_schemas/worker_schema.rb |
Adds provisioning and runtime settings. |
lib/cloud_controller/config_schemas/clock_schema.rb |
Adds runtime identity settings. |
lib/cloud_controller/config_schemas/api_schema.rb |
Adds account feature and provisioning settings. |
lib/cloud_controller/backends/staging_environment_builder.rb |
Removes account discovery from staging environments. |
errors/v3.yml |
Adds account conflict and budget errors. |
docs/v3/source/index.html.md |
Includes service-account API documentation. |
docs/v3/source/includes/resources/organizations/_update.md.erb |
Documents namespace allocation restrictions. |
docs/v3/source/includes/resources/organizations/_object.md.erb |
Documents organization namespace fields. |
docs/v3/source/includes/resources/organizations/_create.md.erb |
Documents namespace creation semantics. |
docs/v3/source/includes/api_resources/_service_accounts.md |
Documents account APIs, integrations and rollout. |
docs/v3/source/includes/api_resources/_organizations.erb |
Updates organization response examples. |
db/migrations/20261109131000_snapshot_service_binding_account.rb |
Adds binding identity snapshots. |
db/migrations/20261109130000_preserve_legacy_account_namespace.rb |
Preserves legacy global name reservations. |
db/migrations/20261109125000_create_service_account_deletion_history.rb |
Adds durable account deletion history. |
db/migrations/20261109124000_add_org_service_account_quota.rb |
Adds organization account quota storage. |
db/migrations/20261109123000_scope_service_account_names_to_organizations.rb |
Introduces organization-scoped names and identity prefixes. |
db/migrations/20261109122000_allow_accounts_without_original_space.rb |
Allows accounts to survive original-space deletion. |
db/migrations/20261109121000_add_service_account_organization_ownership.rb |
Adds organization ownership and space entitlements. |
db/migrations/20261109120000_add_organization_service_account_prefixes.rb |
Adds immutable namespace reservations. |
db/migrations/20261007120000_create_service_account_creations.rb |
Adds the creation-budget ledger. |
db/migrations/20261005120400_add_process_service_account_snapshot.rb |
Adds process launch identity storage. |
db/migrations/20261005120300_add_task_service_account_snapshot.rb |
Adds task launch identity storage. |
db/migrations/20261005120200_add_service_account_metadata.rb |
Adds descriptions, labels and annotations. |
db/migrations/20261005120100_add_app_service_account.rb |
Adds the app-account relationship and index. |
db/migrations/20261005120000_create_service_accounts.rb |
Creates the initial account registry. |
config/routes.rb |
Registers account and assignment endpoints. |
app/repositories/service_account_event_repository.rb |
Records account and assignment audit events. |
app/presenters/v3/service_account_presenter.rb |
Presents account identities and relationships. |
app/presenters/v3/route_policy_presenter.rb |
Presents account route-source relationships. |
app/presenters/v3/organization_quota_presenter.rb |
Exposes account quota limits. |
app/presenters/v3/organization_presenter.rb |
Exposes organization namespaces. |
app/models/services/service_binding.rb |
Captures account identity during binding creation. |
app/models/runtime/task_model.rb |
Exposes captured task identity. |
app/models/runtime/service_account_model.rb |
Implements ownership, reservations and lifecycle references. |
app/models/runtime/service_account_label_model.rb |
Adds account label associations. |
app/models/runtime/service_account_annotation_model.rb |
Adds account annotation associations. |
app/models/runtime/route_policy.rb |
Resolves qualified account route sources. |
app/models/runtime/process_model.rb |
Captures account identity when starting processes. |
app/models/runtime/organization.rb |
Validates and reserves immutable namespaces. |
app/models/runtime/app_model.rb |
Adds account assignment and entitlement validation. |
app/models.rb |
Loads account and metadata models. |
app/messages/service_accounts_list_message.rb |
Validates account list filters. |
app/messages/service_account_update_message.rb |
Validates account metadata and state updates. |
app/messages/service_account_create_message.rb |
Validates creation and name-reuse requests. |
app/messages/route_policy_create_message.rb |
Accepts qualified account route sources. |
app/messages/route_policies_list_message.rb |
Accepts account source filters. |
app/messages/organization_update_message.rb |
Validates organization namespace input. |
app/messages/organization_quotas_update_message.rb |
Validates account quota limits. |
app/messages/app_service_account_update_message.rb |
Validates assignment relationship payloads. |
app/jobs/v3/service_account_provision.rb |
Runs retryable account lifecycle operations. |
app/decorators/include_route_policy_source_decorator.rb |
Includes readable account route sources. |
app/controllers/v3/service_accounts_controller.rb |
Implements account management and entitlement APIs. |
app/controllers/v3/route_policies_controller.rb |
Authorizes and filters account route sources. |
app/controllers/v3/organizations_controller.rb |
Restricts namespace allocation to administrators. |
app/controllers/v3/app_service_accounts_controller.rb |
Implements app-account relationship endpoints. |
app/collection_transformers/username_populator.rb |
Resolves managed account principal names. |
app/actions/task_create.rb |
Captures task identity under account locks. |
app/actions/space_delete.rb |
Retains accounts while removing original-space references. |
app/actions/service_account_provision.rb |
Reconciles UAA clients and CAPI principals. |
app/actions/service_account_creation_budget.rb |
Enforces atomic rolling creation budgets. |
app/actions/route_policy_create.rb |
Locks account sources during grant creation. |
app/actions/process_restart.rb |
Refreshes identity snapshots on restart. |
app/actions/organization_update.rb |
Applies namespace allocation and reports conflicts. |
app/actions/organization_quotas_update.rb |
Updates account quota limits. |
app/actions/organization_quotas_create.rb |
Initializes account quota limits. |
app/actions/organization_delete.rb |
Coordinates account cleanup during organization deletion. |
app/actions/organization_create.rb |
Allocates namespaces during organization creation. |
app/actions/app_assign_service_account.rb |
Enforces assignment guards and queues provisioning. |
Review effort: Balanced
Comment on lines
+11
to
+12
| names = users.select(&:is_oauth_client).map { |user| user.guid.delete_prefix('cf:service-account:').split(':').last } | ||
| accounts = ServiceAccountModel.where(name: names).all |
| def index | ||
| message = ServiceAccountsListMessage.from_params(query_params) | ||
| invalid_param!(message.errors.full_messages) unless message.valid? | ||
| dataset = ServiceAccountModel.dataset |
| require_writable_org!(account.organization) | ||
| space = account.entitled_spaces_dataset.where(spaces__guid: hashed_params[:space_guid]).first | ||
| resource_not_found!(:space) unless space | ||
| unauthorized! unless org_manager?(account) || developer?(space) |
Comment on lines
+93
to
+95
| apps_dataset.any? || ServiceBinding.where(service_account_guid: guid).any? || | ||
| ProcessModel.where(service_account_guid: guid, state: ProcessModel::STARTED).any? || | ||
| TaskModel.where(service_account_guid: guid, state: [TaskModel::PENDING_STATE, TaskModel::RUNNING_STATE, TaskModel::CANCELING_STATE]).any? |
| end | ||
|
|
||
| def before_create | ||
| if app |
| errors.add(:service_account, 'must be entitled to the app space') | ||
| raise validation_failed_error | ||
| end | ||
| self.service_account_guid = account&.guid |
| else | ||
| add_column :service_account_guid, String, **options, size: 255 | ||
| end | ||
| add_foreign_key [:service_account_guid], :service_accounts, key: :guid, name: :fk_apps_service_account_guid |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Implement the Cloud Controller portion of Service Accounts for Cloud Foundry Workload Identity (community #1645), aligned with the RFC document at its merged revision.
Service accounts are org-owned identities, shared by apps only in explicitly entitled spaces of the same org. Each org has an admin-allocated, immutable, foundation-unique
service_account_prefix; account names are immutable and unique within that org. Both use lowercase RFC 1123 labels of 3–63 characters.For prefix
acme-paymentsand accountpayments-worker:cf:service-account:acme-payments:payments-workerpayments-worker.acme-payments.svc.identitycf:svc:acme-payments:payments-workerOne app has at most one account; many apps can share an account. Assignment grants no permissions: account use, CAPI roles, external grants and route policies remain explicit and independent.
Intentional RFC divergence: independent CAPI user lifecycle
Unlike RFC Appendix D, first bind provisions only the managed UAA client; it does not create a roleless CAPI user. CAPI users and their role grants remain independently managed. The existing CAPI authentication flow can lazily create a roleless OAuth user on its first API request; explicit roles are still required for resource access. No new explicit-user-creation command or authentication behavior is introduced here.
Disable, enable and service-account retirement do not create, disable or delete CAPI users or their roles, nor do they change external WIF identities/grants. Account deletion removes the managed UAA client and account resource. Ordinary space/org deletion still removes roles on deleted resources. If an identity is explicitly reused, independently retained CAPI roles can apply to the same client ID, just as retained external grants can; remove those grants independently when retiring access. This avoids coupling workload identity lifecycle to CAPI user lifecycle and requires no account-to-principal ownership field.
Cloud Controller implementation
Accounts, namespaces and permissions
/v3/service_accountslifecycle, metadata, filtering, app listing and space-entitlement APIs, plus GET/PATCH/v3/apps/:guid/relationships/service_account.service_account_prefixon creation or one-time unset-to-value update. Allocation is admin-only and atomic; an allocated prefix cannot be renamed, cleared or automatically released on org deletion. An unset prefix prevents account creation, entitlement expansion and assignment.service_accounts.total_service_accountsto org quotas and an optional rolling seven-day creation budget per authenticated user/client across spaces. Admins are exempt from the budget; failed creation rolls back consumption and deletion does not refund it. Concurrent creation and first-bind provisioning are serialized, including MySQL repeatable-read handling.reuse_namecreates a new UUID, is audited, and warns that external grants and still-valid old certificates may apply. Live collisions and quotas remain enforced.Lifecycle, UAA registration and runtime identity
tls_client_certificate_bound_access_tokens: false, configure a maximum 300-second token validity, and map instance/app/space/org attribution claims. Support the named allowed-resource-policy reference for federation audiences; developers do not control registration inputs.VCAP_SERVICE_ACCOUNT. Snapshot process identity until explicit restart; new runtime tasks use the current assignment; staging excludes account identity.Explicit authorization and integrations
cf:svc:<prefix>:<name>route-policy sources, account resolution and domain org/space restrictions while preserving existing source semantics.{ issuer, subject }to the CF platformcontext.service_accountof broker bind requests for ready accounts; service keys do not receive it. Identity-based bindings must be removed before app reassignment. Existing bindings require rebind to adopt the account. Broker-side shared-grant lifecycle remains the broker's responsibility.Verification
498d40b4c, including normal and backward-compatibility suites on MySQL 8.0/8.2 and PostgreSQL 14/15/17, OpenAPI request conformance, docs, RuboCop, CodeQL and CLA. Main test run · backward compatibility.cnf, named resource-policy audience selection/denials, and policy-only updates without re-registering account clients. These are integration evidence, not proof of every external provider's token requirements.Dependencies, rollout and remaining validation
service_account_runtime_contract_version: 2and compatible BBS/Diego cells before activation; this is an operator assertion, not automatic capability negotiation. The app-facing token endpoint must support reachable, trusted mTLS, either directly or through UAA's authenticated trusted-proxy mode.azp/audience semantics), broker-managed shared grants end to end, Windows runtime, and namespace-limited UAA client administration. CAPI registration alone does not demonstrate these UAA/provider/broker properties.The RFC document's recorded status is Draft, although community #1645 is merged. This PR implements the CAPI scope and records its cross-component dependencies; it does not claim complete end-to-end RFC acceptance.