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 existing secrets_manager_settings JSON column (no column migration).
    • Default 0, which means no limit. Only instance admins can change it, through the application settings API.
  • InitializeServiceHelpers#execute counts project and group secrets managers in the top-level group, and returns reason: :root_namespace_limit_reached when the count reaches the limit.
    • All entry points go through it: both Initialize mutations, StartTrial, and EnableAddOn.
    • 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.
  • New gitlab_com_derisk flag limit_secrets_managers_per_root_namespace (default off), with the top-level group as the actor. Enforcement needs the flag and a limit above 0.
  • New index on root_namespace_id for project_secrets_managers and group_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

Database

  • Post-deploy migration adds two indexes. Both tables are gitlab_main_org and table_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 (limit is 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_count

Plan: 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_count

Plan: 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_limit back to 0.
  • Either one stops enforcement right away. No data changes to undo.

How to set up and validate locally

  1. 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)
  2. Enable the secrets manager in one project under your-group. It succeeds.

  3. Enable the secrets manager in a second project or subgroup under your-group. It fails with Secrets manager limit reached for this top-level group.

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

Edited by Erick Bajao

Merge request reports

Loading
Loading