Add a per top-level group limit on secrets managers
What does this MR do and why?
Problem
- Each project or group secrets manager adds a namespace to OpenBao.
- OpenBao loads every namespace when a pod starts or a standby is promoted. More namespaces means slower failover for every tenant.
- Nothing limits how many secrets managers one top-level group can enable.
Solution
- New application setting
root_namespace_secrets_managers_limit, in the existingsecrets_manager_settingsJSON column (no column migration).- Default
0, which means no limit. Only instance admins can change it, through the application settings API.
- Default
InitializeServiceHelpers#executecounts project and group secrets managers in the top-level group, and returnsreason: :root_namespace_limit_reachedwhen the count reaches the limit.- All entry points go through it: both Initialize mutations,
StartTrial, andEnableAddOn. - The check runs before the entitlement check, so a blocked request makes no CustomersDot call.
- Rows in any status count, because each row becomes an OpenBao namespace.
- Existing secrets managers are not affected. Only new enables are blocked.
- All entry points go through it: both Initialize mutations,
- New
gitlab_com_deriskflaglimit_secrets_managers_per_root_namespace(default off), with the top-level group as the actor. Enforcement needs the flag and a limit above0. - New index on
root_namespace_idforproject_secrets_managersandgroup_secrets_managers, used by the count.
Design notes
- Cap, not rate limit. Memory and failover time depend on how many namespaces exist, not how fast they were created. A rate limit only slows growth.
- Global setting, not per group. Group Owners are the ones enabling secrets managers, so a limit they could change would not protect other tenants. A per-group override set by an admin can come later if a customer needs one.
- Soft cap. Two parallel enables can both pass the check and go one over the limit. This is fine for this purpose, so there is no lease.
- Out of scope. Concurrent provisioning and the total namespace count across all tenants are tracked in https://gitlab.com/gitlab-org/gitlab/-/work_items/638687 and https://gitlab.com/gitlab-org/gitlab/-/work_items/638688.
References
- Implements https://gitlab.com/gitlab-org/gitlab/-/work_items/637347
- Rollout issue: #638705 (closed)
Database
- Post-deploy migration adds two indexes. Both tables are
gitlab_main_organdtable_size: small.
CREATE INDEX index_project_secrets_managers_on_root_namespace_id ON project_secrets_managers USING btree (root_namespace_id);
CREATE INDEX index_group_secrets_managers_on_root_namespace_id ON group_secrets_managers USING btree (root_namespace_id);- New queries, run once per initialize (
limitis the setting value):
SELECT COUNT(*) FROM (SELECT 1 AS one FROM "project_secrets_managers" WHERE "project_secrets_managers"."root_namespace_id" = 9970 LIMIT 1000) subquery_for_count;
SELECT COUNT(*) FROM (SELECT 1 AS one FROM "group_secrets_managers" WHERE "group_secrets_managers"."root_namespace_id" = 9970 LIMIT 1000) subquery_for_count;Query plans
Run on gitlab-production-main, with the new indexes created on the clone. Both queries run once per initialize, when the limit is set.
1. Project secrets managers in the top-level group
SELECT COUNT(*) FROM (SELECT 1 AS one FROM "project_secrets_managers" WHERE "project_secrets_managers"."root_namespace_id" = <root_namespace_id> LIMIT <limit>) subquery_for_countPlan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/82388/commands/189727
(Index Only Scan on index_project_secrets_managers_on_root_namespace_id, 28 rows, 0.58 ms cold, buffers: 6 hit / 1 read)
2. Group secrets managers in the top-level group
SELECT COUNT(*) FROM (SELECT 1 AS one FROM "group_secrets_managers" WHERE "group_secrets_managers"."root_namespace_id" = <root_namespace_id> LIMIT <limit>) subquery_for_countPlan: https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/82388/commands/189728
(Index Only Scan on index_group_secrets_managers_on_root_namespace_id, 2 rows, 0.69 ms cold, buffers: 4 hit / 2 read)
Migration output (up and down, tested on main, ci and sec)
main: == 20261105180734 AddRootNamespaceIdIndexToSecretsManagers: reverting =========
main: -- index_name_exists?(:group_secrets_managers, "index_group_secrets_managers_on_root_namespace_id")
main: -- remove_index(:group_secrets_managers, {algorithm: :concurrently, name: "index_group_secrets_managers_on_root_namespace_id"})
main: -- index_name_exists?(:project_secrets_managers, "index_project_secrets_managers_on_root_namespace_id")
main: -- remove_index(:project_secrets_managers, {algorithm: :concurrently, name: "index_project_secrets_managers_on_root_namespace_id"})
main: == 20261105180734 AddRootNamespaceIdIndexToSecretsManagers: reverted (0.1064s)
main: == 20261105180734 AddRootNamespaceIdIndexToSecretsManagers: migrating =========
main: -- index_exists?(:project_secrets_managers, :root_namespace_id, {name: "index_project_secrets_managers_on_root_namespace_id", algorithm: :concurrently})
main: -- add_index(:project_secrets_managers, :root_namespace_id, {name: "index_project_secrets_managers_on_root_namespace_id", algorithm: :concurrently})
main: -- index_exists?(:group_secrets_managers, :root_namespace_id, {name: "index_group_secrets_managers_on_root_namespace_id", algorithm: :concurrently})
main: -- add_index(:group_secrets_managers, :root_namespace_id, {name: "index_group_secrets_managers_on_root_namespace_id", algorithm: :concurrently})
main: == 20261105180734 AddRootNamespaceIdIndexToSecretsManagers: migrated (0.1210s)Rollback
- Disable the flag:
/chatops gitlab run feature set limit_secrets_managers_per_root_namespace false - Or set
root_namespace_secrets_managers_limitback to0. - Either one stops enforcement right away. No data changes to undo.
How to set up and validate locally
-
Set the limit and enable the flag in the Rails console:
group = Group.find_by_full_path('your-group') ApplicationSetting.current.update!(root_namespace_secrets_managers_limit: 1) Feature.enable(:limit_secrets_managers_per_root_namespace, group) -
Enable the secrets manager in one project under
your-group. It succeeds. -
Enable the secrets manager in a second project or subgroup under
your-group. It fails withSecrets manager limit reached for this top-level group. -
Set the limit back to
0, or disable the flag, and try again. It succeeds.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Changelog: added EE: true