From e7231e01dc69fb35cd209dee915e8cd8b385fa59 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:41:59 +0200 Subject: [PATCH 01/20] Test service account worker configuration rendering (red) --- .../cloud_controller_worker_spec.rb | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb index 7927ea28ab..c9229e6ec5 100644 --- a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb +++ b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb @@ -128,6 +128,30 @@ module Test let(:template) { job.template('config/cloud_controller_ng.yml') } + it 'renders dedicated provisioning credentials and instance trust only when explicitly enabled' do + manifest_properties['cc']['service_accounts'] = { + 'provisioning_enabled' => true, + 'runtime_enabled' => false, + 'management_client_id' => 'account-manager', + 'management_client_secret' => 'manager-secret', + 'identity_ca' => "-----BEGIN CERTIFICATE-----\nidentity-ca\n-----END CERTIFICATE-----", + 'token_endpoint' => 'https://uaa.example.test/oauth/token/mtls' + } + result = YAML.safe_load(template.render(manifest_properties, consumes: links)) + expect(result['service_account_provisioning']).to eq( + 'client_id' => 'account-manager', 'client_secret' => 'manager-secret', + 'identity_ca' => manifest_properties['cc']['service_accounts']['identity_ca'] + ) + expect(result['service_account_runtime_enabled']).to be(false) + expect(result['service_account_token_endpoint']).to eq('https://uaa.example.test/oauth/token/mtls') + end + + it 'keeps service account runtime and provisioning disabled by default' do + result = YAML.safe_load(template.render(manifest_properties, consumes: links)) + expect(result['service_account_runtime_enabled']).to be(false) + expect(result).not_to have_key('service_account_provisioning') + end + it 'creates the cloud_controller_ng.yml config file' do expect do YAML.safe_load(template.render(manifest_properties, consumes: links)) From 84b18957aa445453a4d4e8735bb2d13db18e126a Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:42:48 +0200 Subject: [PATCH 02/20] Render dedicated service account worker trust and credentials --- jobs/cloud_controller_worker/spec | 19 ++++++++++++++++++- .../templates/cloud_controller_ng.yml.erb | 9 +++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/jobs/cloud_controller_worker/spec b/jobs/cloud_controller_worker/spec index 855d6743db..5771682bd7 100644 --- a/jobs/cloud_controller_worker/spec +++ b/jobs/cloud_controller_worker/spec @@ -68,6 +68,24 @@ consumes: optional: true properties: + cc.service_accounts.provisioning_enabled: + description: Enable dedicated managed service account client reconciliation in workers. + default: false + cc.service_accounts.runtime_enabled: + description: Enable typed account runtime identity only after all BBS/rep/cells support it. + default: false + cc.service_accounts.token_endpoint: + description: HTTPS UAA certificate-authenticated token endpoint exposed to bound workloads. + default: "" + cc.service_accounts.management_client_id: + description: Dedicated UAA client with managed account namespace client-management permissions. + default: "" + cc.service_accounts.management_client_secret: + description: Secret of the dedicated UAA account-management client; never exposed to workloads. + default: "" + cc.service_accounts.identity_ca: + description: Instance identity CA PEM trusted for exact account DNS SAN authentication. + default: "" ssl.skip_cert_verify: description: "specifies that the job is allowed to skip ssl cert verification" default: false @@ -599,4 +617,3 @@ properties: when applying manifests. If a service broker fails to complete a service binding request before the specified duration, the manifest job will fail." default: 60 - diff --git a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb index 01f66b3467..ef32fdd55d 100644 --- a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb @@ -167,6 +167,15 @@ uaa: client_timeout: <%= cc_internal.p("cc.uaa.client_timeout")%> <% end %> +service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> +service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> +<% if p('cc.service_accounts.provisioning_enabled') %> +service_account_provisioning: + client_id: <%= yaml_escape(p('cc.service_accounts.management_client_id')) %> + client_secret: <%= yaml_escape(p('cc.service_accounts.management_client_secret')) %> + identity_ca: <%= p('cc.service_accounts.identity_ca').to_json %> +<% end %> + # App staging parameters staging: timeout_in_seconds: <%= p("cc.staging_timeout_in_seconds") %> From bd7d8831d6cba02871a55e06a5d9c6858267d237 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:43:46 +0200 Subject: [PATCH 03/20] Test service account API gate and discovery rendering (red) --- spec/cloud_controller_ng/cloud_controller_ng_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/cloud_controller_ng/cloud_controller_ng_spec.rb b/spec/cloud_controller_ng/cloud_controller_ng_spec.rb index b1d749ef75..9ee8975f4e 100644 --- a/spec/cloud_controller_ng/cloud_controller_ng_spec.rb +++ b/spec/cloud_controller_ng/cloud_controller_ng_spec.rb @@ -142,6 +142,18 @@ module Test describe 'config/cloud_controller_ng.yml' do let(:template) { job.template('config/cloud_controller_ng.yml') } + it 'renders explicit service account gates and discovery without exposing management credentials' do + merged_manifest_properties['cc']['service_accounts'] = { + 'provisioning_enabled' => true, 'runtime_enabled' => true, + 'token_endpoint' => 'https://uaa.example.test/oauth/token/mtls' + } + result = YAML.safe_load(template.render(merged_manifest_properties, consumes: links)) + expect(result['service_account_provisioning_enabled']).to be(true) + expect(result['service_account_runtime_enabled']).to be(true) + expect(result['service_account_token_endpoint']).to eq('https://uaa.example.test/oauth/token/mtls') + expect(result).not_to have_key('service_account_provisioning') + end + it 'creates the cloud_controller_ng.yml config file' do expect do YAML.safe_load(template.render(merged_manifest_properties, consumes: links)) From 333cda02c4ed5c603909c724e53443e138049f31 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:44:26 +0200 Subject: [PATCH 04/20] Render default-off service account API and runtime gates --- jobs/cloud_controller_ng/spec | 9 +++++++++ .../templates/cloud_controller_ng.yml.erb | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/jobs/cloud_controller_ng/spec b/jobs/cloud_controller_ng/spec index ba7e1b0d25..85d3c4c4b0 100644 --- a/jobs/cloud_controller_ng/spec +++ b/jobs/cloud_controller_ng/spec @@ -243,6 +243,15 @@ consumes: optional: true properties: + cc.service_accounts.provisioning_enabled: + description: Enable asynchronous service account client reconciliation requests. + default: false + cc.service_accounts.runtime_enabled: + description: Enable typed account runtime identity only after all BBS/rep/cells support it. + default: false + cc.service_accounts.token_endpoint: + description: HTTPS UAA certificate-authenticated token endpoint for account discovery. + default: "" ssl.skip_cert_verify: description: "specifies that the job is allowed to skip ssl cert verification" default: false diff --git a/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb index c7f69f1ca7..bfb0555b80 100644 --- a/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb @@ -314,6 +314,10 @@ credential_references: interpolate_service_bindings: <%= p("cc.credential_references.interpolate_service_bindings") %> # App staging parameters +service_account_provisioning_enabled: <%= p('cc.service_accounts.provisioning_enabled') %> +service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> +service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> + staging: legacy_md5_buildpack_paths_enabled: <%= p("cc.legacy_md5_buildpack_paths_enabled") %> timeout_in_seconds: <%= p("cc.staging_timeout_in_seconds") %> From ed47d22184a6f7f21d2be8f317229306b0057265 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:45:03 +0200 Subject: [PATCH 05/20] Test service account clock runtime rendering (red) --- spec/cloud_controller_clock/cloud_controller_clock_spec.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/cloud_controller_clock/cloud_controller_clock_spec.rb b/spec/cloud_controller_clock/cloud_controller_clock_spec.rb index 12c9e24361..f3e6c0574d 100644 --- a/spec/cloud_controller_clock/cloud_controller_clock_spec.rb +++ b/spec/cloud_controller_clock/cloud_controller_clock_spec.rb @@ -118,6 +118,13 @@ module Test let(:template) { job.template('config/cloud_controller_ng.yml') } + it 'renders service account runtime discovery for clock-driven reconciliation' do + manifest_properties['cc']['service_accounts'] = { 'runtime_enabled' => true, 'token_endpoint' => 'https://uaa.example.test/oauth/token/mtls' } + result = YAML.safe_load(template.render(manifest_properties, consumes: links)) + expect(result['service_account_runtime_enabled']).to be(true) + expect(result['service_account_token_endpoint']).to eq('https://uaa.example.test/oauth/token/mtls') + end + it 'creates the cloud_controller_ng.yml config file' do expect do YAML.safe_load(template.render(manifest_properties, consumes: links)) From 7dbf40bb5a38ff7a254246037c7868053bb5a960 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 20:45:43 +0200 Subject: [PATCH 06/20] Render service account runtime discovery in clock config --- jobs/cloud_controller_clock/spec | 6 ++++++ .../templates/cloud_controller_ng.yml.erb | 3 +++ 2 files changed, 9 insertions(+) diff --git a/jobs/cloud_controller_clock/spec b/jobs/cloud_controller_clock/spec index b5745d9c8e..cf13475d2b 100644 --- a/jobs/cloud_controller_clock/spec +++ b/jobs/cloud_controller_clock/spec @@ -60,6 +60,12 @@ consumes: optional: true properties: + cc.service_accounts.runtime_enabled: + description: Enable typed account identity in clock reconciliation after runtime rollout. + default: false + cc.service_accounts.token_endpoint: + description: HTTPS UAA certificate-authenticated token endpoint for account discovery. + default: "" ssl.skip_cert_verify: description: "specifies that the job is allowed to skip ssl cert verification" default: false diff --git a/jobs/cloud_controller_clock/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_clock/templates/cloud_controller_ng.yml.erb index 650ef6a0b4..05153a299e 100644 --- a/jobs/cloud_controller_clock/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_clock/templates/cloud_controller_ng.yml.erb @@ -223,6 +223,9 @@ uaa: <% end %> # App staging parameters +service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> +service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> + staging: timeout_in_seconds: <%= p("cc.staging_timeout_in_seconds") %> auth: From d2f5bb7ef679c99e3493ef44a56a6e0c5fe74987 Mon Sep 17 00:00:00 2001 From: rkoster Date: Mon, 5 Oct 2026 22:41:58 +0200 Subject: [PATCH 07/20] Bump cloud_controller_ng for space-owned service account POC --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index 3a4f0bc491..8af06c4402 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit 3a4f0bc49185f08feea8d8f9f5e821eb262f8968 +Subproject commit 8af06c44022e64b1b5b4e081234c807f2747a6a9 From fefb8c4bcb966e41348ae30f78345545d17c67c8 Mon Sep 17 00:00:00 2001 From: rkoster Date: Tue, 6 Oct 2026 07:10:05 +0200 Subject: [PATCH 08/20] Make packaged application directories readable by runtime user --- packages/cloud_controller_ng/packaging | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cloud_controller_ng/packaging b/packages/cloud_controller_ng/packaging index 9344859f97..0d5b9b8a14 100644 --- a/packages/cloud_controller_ng/packaging +++ b/packages/cloud_controller_ng/packaging @@ -3,6 +3,9 @@ set -e -x # shellcheck disable=1090 source "${BOSH_PACKAGES_DIR:-/var/vcap/packages}/ruby-4.0/bosh/compile.env" cp -a * ${BOSH_INSTALL_TARGET} +# Archive-created parent directories may inherit a private build umask. +# The application is read by the unprivileged vcap runtime user. +chmod -R a+rX "${BOSH_INSTALL_TARGET}/cloud_controller_ng" cd ${BOSH_INSTALL_TARGET}/cloud_controller_ng mariadb_dir=/var/vcap/packages/mariadb_connector_c From 9fa307b81fc1d5d8eb8d1b07f70d3318b6e3f970 Mon Sep 17 00:00:00 2001 From: rkoster Date: Tue, 6 Oct 2026 07:22:09 +0200 Subject: [PATCH 09/20] Bump cloud_controller_ng for MySQL service account migration recovery --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index 8af06c4402..e4fdc2b67e 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit 8af06c44022e64b1b5b4e081234c807f2747a6a9 +Subproject commit e4fdc2b67ee2b15a927cb9ea5f75a653f615191b From ca65001417c34285c1394b4c2b6b2679c014b5b2 Mon Sep 17 00:00:00 2001 From: rkoster Date: Tue, 6 Oct 2026 08:25:09 +0200 Subject: [PATCH 10/20] Bump cloud_controller_ng for normalized UAA client reconciliation --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index e4fdc2b67e..8a4e504ae5 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit e4fdc2b67ee2b15a927cb9ea5f75a653f615191b +Subproject commit 8a4e504ae54b3b90753ebeedb0215a0c553c3ad1 From fbd0c3ed3fcad4e8fbd25388227f453bed4d77c7 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 7 Oct 2026 09:40:18 +0200 Subject: [PATCH 11/20] Test configurable service account creation budget rendering (red) --- spec/cloud_controller_ng/cloud_controller_ng_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/cloud_controller_ng/cloud_controller_ng_spec.rb b/spec/cloud_controller_ng/cloud_controller_ng_spec.rb index 9ee8975f4e..24739efa13 100644 --- a/spec/cloud_controller_ng/cloud_controller_ng_spec.rb +++ b/spec/cloud_controller_ng/cloud_controller_ng_spec.rb @@ -142,6 +142,17 @@ module Test describe 'config/cloud_controller_ng.yml' do let(:template) { job.template('config/cloud_controller_ng.yml') } + it 'renders an unlimited service account creation budget by default' do + result = YAML.safe_load(template.render(merged_manifest_properties, consumes: links)) + expect(result['service_account_creation_limit']).to eq(-1) + end + + it 'renders the foundation-wide non-admin service account creation budget' do + merged_manifest_properties['cc']['service_accounts'] = { 'creation_limit' => 25 } + result = YAML.safe_load(template.render(merged_manifest_properties, consumes: links)) + expect(result['service_account_creation_limit']).to eq(25) + end + it 'renders explicit service account gates and discovery without exposing management credentials' do merged_manifest_properties['cc']['service_accounts'] = { 'provisioning_enabled' => true, 'runtime_enabled' => true, From e2a5bce25aa68d60820d3b4bd6649f4fdad0ace0 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 7 Oct 2026 09:40:57 +0200 Subject: [PATCH 12/20] Render configurable weekly service account creation limit --- jobs/cloud_controller_ng/spec | 3 +++ jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb | 1 + 2 files changed, 4 insertions(+) diff --git a/jobs/cloud_controller_ng/spec b/jobs/cloud_controller_ng/spec index 85d3c4c4b0..5adc48970d 100644 --- a/jobs/cloud_controller_ng/spec +++ b/jobs/cloud_controller_ng/spec @@ -243,6 +243,9 @@ consumes: optional: true properties: + cc.service_accounts.creation_limit: + description: Foundation-wide maximum successful service account creations per non-admin authenticated principal over a rolling seven-day window. Platform admins are exempt; -1 means unlimited and 0 prevents non-admin creation. Deletion does not refund budget. + default: -1 cc.service_accounts.provisioning_enabled: description: Enable asynchronous service account client reconciliation requests. default: false diff --git a/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb index bfb0555b80..167d139942 100644 --- a/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_ng/templates/cloud_controller_ng.yml.erb @@ -315,6 +315,7 @@ credential_references: # App staging parameters service_account_provisioning_enabled: <%= p('cc.service_accounts.provisioning_enabled') %> +service_account_creation_limit: <%= p('cc.service_accounts.creation_limit') %> service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> From a186d2d41732ffcee9c4736750117d431e9e7612 Mon Sep 17 00:00:00 2001 From: rkoster Date: Wed, 7 Oct 2026 09:43:00 +0200 Subject: [PATCH 13/20] Bump cloud_controller_ng for creation budget and recursive account cleanup --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index 8a4e504ae5..ec582b703e 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit 8a4e504ae54b3b90753ebeedb0215a0c553c3ad1 +Subproject commit ec582b703ef80fa9b9c5b3c34ca2798a29b4b801 From 5089b31e445b32283a9306f45d8a9670dcc2ee8b Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:03:48 +0200 Subject: [PATCH 14/20] Test rendering service account trust bundle reference for workers (red) --- .../cloud_controller_worker_spec.rb | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb index c9229e6ec5..585e673478 100644 --- a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb +++ b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb @@ -146,6 +146,29 @@ module Test expect(result['service_account_token_endpoint']).to eq('https://uaa.example.test/oauth/token/mtls') end + it 'renders a UAA trust bundle reference instead of inline instance trust' do + manifest_properties['cc']['service_accounts'] = { + 'provisioning_enabled' => true, + 'management_client_id' => 'account-manager', + 'management_client_secret' => 'manager-secret', + 'identity_ca_ref' => 'diego-instance-identity' + } + result = YAML.safe_load(template.render(manifest_properties, consumes: links)) + expect(result['service_account_provisioning']).to eq( + 'client_id' => 'account-manager', 'client_secret' => 'manager-secret', + 'identity_ca_ref' => 'diego-instance-identity' + ) + end + + it 'refuses to render both inline instance trust and a trust bundle reference' do + manifest_properties['cc']['service_accounts'] = { + 'provisioning_enabled' => true, + 'identity_ca' => "-----BEGIN CERTIFICATE-----\nidentity-ca\n-----END CERTIFICATE-----", + 'identity_ca_ref' => 'diego-instance-identity' + } + expect { template.render(manifest_properties, consumes: links) }.to raise_error(/exactly one of cc.service_accounts.identity_ca or identity_ca_ref/) + end + it 'keeps service account runtime and provisioning disabled by default' do result = YAML.safe_load(template.render(manifest_properties, consumes: links)) expect(result['service_account_runtime_enabled']).to be(false) From 90d4e4bf67c72e9859057d0dda3274873f2f71d7 Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:04:05 +0200 Subject: [PATCH 15/20] Render service account trust bundle reference for workers --- jobs/cloud_controller_worker/spec | 5 ++++- .../templates/cloud_controller_ng.yml.erb | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/jobs/cloud_controller_worker/spec b/jobs/cloud_controller_worker/spec index 5771682bd7..b243a392af 100644 --- a/jobs/cloud_controller_worker/spec +++ b/jobs/cloud_controller_worker/spec @@ -84,7 +84,10 @@ properties: description: Secret of the dedicated UAA account-management client; never exposed to workloads. default: "" cc.service_accounts.identity_ca: - description: Instance identity CA PEM trusted for exact account DNS SAN authentication. + description: Instance identity CA PEM trusted for exact account DNS SAN authentication. Mutually exclusive with identity_ca_ref. + default: "" + cc.service_accounts.identity_ca_ref: + description: Name of a UAA trust bundle (uaa.mtls.trust_bundles) containing the instance identity CA, registered on account clients instead of inline PEM so CA rotation is a UAA configuration change. Mutually exclusive with identity_ca. default: "" ssl.skip_cert_verify: description: "specifies that the job is allowed to skip ssl cert verification" diff --git a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb index ef32fdd55d..c1e65596fa 100644 --- a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb @@ -170,10 +170,21 @@ uaa: service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> <% if p('cc.service_accounts.provisioning_enabled') %> +<% + identity_ca = p('cc.service_accounts.identity_ca') + identity_ca_ref = p('cc.service_accounts.identity_ca_ref') + if identity_ca.to_s.strip.empty? == identity_ca_ref.to_s.strip.empty? + raise 'Set exactly one of cc.service_accounts.identity_ca or identity_ca_ref' + end +%> service_account_provisioning: client_id: <%= yaml_escape(p('cc.service_accounts.management_client_id')) %> client_secret: <%= yaml_escape(p('cc.service_accounts.management_client_secret')) %> - identity_ca: <%= p('cc.service_accounts.identity_ca').to_json %> +<% if identity_ca_ref.to_s.strip.empty? %> + identity_ca: <%= identity_ca.to_json %> +<% else %> + identity_ca_ref: <%= identity_ca_ref.to_json %> +<% end %> <% end %> # App staging parameters From 7bc5fe1010d168a5a08be607ad9f702072b3e72c Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:04:06 +0200 Subject: [PATCH 16/20] Bump cloud_controller_ng for trust bundle references and owned trust drift --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index ec582b703e..249433196f 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit ec582b703ef80fa9b9c5b3c34ca2798a29b4b801 +Subproject commit 249433196fd6df870ade8d0c888c1f32b04ca603 From 42721a29fd77707c7b9fb843ec7c38053d5f9cd2 Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:25:52 +0200 Subject: [PATCH 17/20] Bump cloud_controller_ng for management client updates --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index 249433196f..38ba2c904e 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit 249433196fd6df870ade8d0c888c1f32b04ca603 +Subproject commit 38ba2c904e5789eafc794f3f49a93bc6b439bf99 From 6845b53aa63cb288e7966ddcdd4287b2f213d4ed Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:46:18 +0200 Subject: [PATCH 18/20] Test requiring a trust bundle reference for worker provisioning (red) --- .../cloud_controller_worker_spec.rb | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb index 585e673478..9263bddb05 100644 --- a/spec/cloud_controller_worker/cloud_controller_worker_spec.rb +++ b/spec/cloud_controller_worker/cloud_controller_worker_spec.rb @@ -134,39 +134,29 @@ module Test 'runtime_enabled' => false, 'management_client_id' => 'account-manager', 'management_client_secret' => 'manager-secret', - 'identity_ca' => "-----BEGIN CERTIFICATE-----\nidentity-ca\n-----END CERTIFICATE-----", + 'identity_ca_ref' => 'diego-instance-identity', 'token_endpoint' => 'https://uaa.example.test/oauth/token/mtls' } result = YAML.safe_load(template.render(manifest_properties, consumes: links)) expect(result['service_account_provisioning']).to eq( 'client_id' => 'account-manager', 'client_secret' => 'manager-secret', - 'identity_ca' => manifest_properties['cc']['service_accounts']['identity_ca'] + 'identity_ca_ref' => 'diego-instance-identity' ) expect(result['service_account_runtime_enabled']).to be(false) expect(result['service_account_token_endpoint']).to eq('https://uaa.example.test/oauth/token/mtls') end - it 'renders a UAA trust bundle reference instead of inline instance trust' do + it 'refuses to render provisioning without a UAA trust bundle reference' do manifest_properties['cc']['service_accounts'] = { 'provisioning_enabled' => true, 'management_client_id' => 'account-manager', - 'management_client_secret' => 'manager-secret', - 'identity_ca_ref' => 'diego-instance-identity' + 'management_client_secret' => 'manager-secret' } - result = YAML.safe_load(template.render(manifest_properties, consumes: links)) - expect(result['service_account_provisioning']).to eq( - 'client_id' => 'account-manager', 'client_secret' => 'manager-secret', - 'identity_ca_ref' => 'diego-instance-identity' - ) + expect { template.render(manifest_properties, consumes: links) }.to raise_error(/Set cc.service_accounts.identity_ca_ref/) end - it 'refuses to render both inline instance trust and a trust bundle reference' do - manifest_properties['cc']['service_accounts'] = { - 'provisioning_enabled' => true, - 'identity_ca' => "-----BEGIN CERTIFICATE-----\nidentity-ca\n-----END CERTIFICATE-----", - 'identity_ca_ref' => 'diego-instance-identity' - } - expect { template.render(manifest_properties, consumes: links) }.to raise_error(/exactly one of cc.service_accounts.identity_ca or identity_ca_ref/) + it 'no longer declares inline instance identity CA certificates' do + expect(YAML.safe_load(File.read(File.join(release_path, 'jobs/cloud_controller_worker/spec')))['properties']).not_to have_key('cc.service_accounts.identity_ca') end it 'keeps service account runtime and provisioning disabled by default' do From 1178b267ec5165769a2eff1a8f79b209c8cc95d9 Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:46:34 +0200 Subject: [PATCH 19/20] Require a UAA trust bundle reference for worker provisioning --- jobs/cloud_controller_worker/spec | 5 +---- .../templates/cloud_controller_ng.yml.erb | 9 +-------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/jobs/cloud_controller_worker/spec b/jobs/cloud_controller_worker/spec index b243a392af..18ba085042 100644 --- a/jobs/cloud_controller_worker/spec +++ b/jobs/cloud_controller_worker/spec @@ -83,11 +83,8 @@ properties: cc.service_accounts.management_client_secret: description: Secret of the dedicated UAA account-management client; never exposed to workloads. default: "" - cc.service_accounts.identity_ca: - description: Instance identity CA PEM trusted for exact account DNS SAN authentication. Mutually exclusive with identity_ca_ref. - default: "" cc.service_accounts.identity_ca_ref: - description: Name of a UAA trust bundle (uaa.mtls.trust_bundles) containing the instance identity CA, registered on account clients instead of inline PEM so CA rotation is a UAA configuration change. Mutually exclusive with identity_ca. + description: Required with provisioning. Name of a UAA trust bundle (uaa.mtls.trust_bundles) containing the instance identity CA, referenced by every account client. Rotate the CA by changing the bundle's contents in UAA; clients never change. default: "" ssl.skip_cert_verify: description: "specifies that the job is allowed to skip ssl cert verification" diff --git a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb index c1e65596fa..120970b453 100644 --- a/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb +++ b/jobs/cloud_controller_worker/templates/cloud_controller_ng.yml.erb @@ -171,21 +171,14 @@ service_account_runtime_enabled: <%= p('cc.service_accounts.runtime_enabled') %> service_account_token_endpoint: <%= yaml_escape(p('cc.service_accounts.token_endpoint')) %> <% if p('cc.service_accounts.provisioning_enabled') %> <% - identity_ca = p('cc.service_accounts.identity_ca') identity_ca_ref = p('cc.service_accounts.identity_ca_ref') - if identity_ca.to_s.strip.empty? == identity_ca_ref.to_s.strip.empty? - raise 'Set exactly one of cc.service_accounts.identity_ca or identity_ca_ref' - end + raise 'Set cc.service_accounts.identity_ca_ref to a UAA trust bundle name' if identity_ca_ref.to_s.strip.empty? %> service_account_provisioning: client_id: <%= yaml_escape(p('cc.service_accounts.management_client_id')) %> client_secret: <%= yaml_escape(p('cc.service_accounts.management_client_secret')) %> -<% if identity_ca_ref.to_s.strip.empty? %> - identity_ca: <%= identity_ca.to_json %> -<% else %> identity_ca_ref: <%= identity_ca_ref.to_json %> <% end %> -<% end %> # App staging parameters staging: From f267b9ea1bf0feb9f69fea74449640ec774d5fc6 Mon Sep 17 00:00:00 2001 From: rkoster Date: Fri, 16 Oct 2026 11:46:35 +0200 Subject: [PATCH 20/20] Bump cloud_controller_ng for reference-only account client trust --- src/cloud_controller_ng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cloud_controller_ng b/src/cloud_controller_ng index 38ba2c904e..bbf68fabf8 160000 --- a/src/cloud_controller_ng +++ b/src/cloud_controller_ng @@ -1 +1 @@ -Subproject commit 38ba2c904e5789eafc794f3f49a93bc6b439bf99 +Subproject commit bbf68fabf84407f6014a554a3df37d57fc90dd5c