Add SecretsManagerInstanceStartTrial mutation and instance entitlement query
What does this MR do and why?
This MR adds the backend for the Secrets Manager trial when GitLab is run Self-Managed, resolving https://gitlab.com/gitlab-org/gitlab/-/work_items/628116.
The existing SecretsManagerStartTrial GraphQL mutation was built for GitLab.com. It takes a groupPath and authorizes admin_group. On Self-Managed the trial belongs to the instance's subscription, not to any group. That meant any top-level group Owner could start the instance-wide trial, which is the wrong actor. There was also no instance-level way to read the trial entitlement, which the admin settings page needs.
- Adds a new
SecretsManagerInstanceStartTrialmutation. It takes no arguments, is markedexperiment, and ships in milestone 19.4. It is admin only, enforced by a newstart_secrets_manager_instance_trialGlobalPolicy permission that requires Self-Managed, anative_secrets_managementlicense, and thesecrets_manager_paid_experiencefeature flag. The mutation does a live entitlement check first: the entitlement must betrial_eligible, otherwise it returns "already active" for an existing trial or "ineligible" for anything else. It then calls CustomersDot'sstart_secrets_manager_trial(instance_id:), expires both entitlement cache layers, and auto-enrolls the instance throughInstanceEnrollmentService#enrollif it is not already enrolled, so the frontend does not need to callinstanceSecretsManagerEnrollseparately. It writes asecrets_manager_trial_startaudit event with instance scope and returns the post-trial entitlement. It shares the same error taxonomy as the group mutation, plus offline and unavailable states. - Adds a
Query.secretsManagerInstanceEntitlementfield that returns the existingSecretsManagerEntitlementtype, resolved with a nil namespace. It is admin only via a newread_secrets_manager_instance_entitlementpermission, and is not available on GitLab.com or when the feature flag is off. - Updates the
SecretsManagerEntitlementtype so its fine-grained token boundary can be either a group or the instance. Theread_secrets_managerpermission now has aninstanceboundary, because the adapter'sgroupis nil when the entitlement is instance-wide. - Adds an
offlineLicensefield toSecretsManagerEntitlement. It istrueon an air-gapped instance,falseon an online one, andnullon GitLab.com. The entitlement state alone cannot tell an air-gapped instance from an online one past its grace window, since both resolve toBLOCKED, so frontends had to infer air-gapped fromOFFLINE_PAID. The field is available on bothGroup.secretsManagerEntitlementand the instance query. - Gives the group mutation a dedicated permission, following !253805 (merged).
SecretsManagerStartTrialnow authorizesstart_secrets_manager_trialinstead ofadmin_group. The permission is granted to Owners throughconfig/authz/roles/owner.ymland prevented in GroupPolicy unlessNamespaceEnrollment.trial_allowed?holds: GitLab.com, top-level group, and a license that includes Secrets Manager. It deliberately does not depend on thesecrets_manager_namespace_enrollmentflag, so the billing gate rolls out withsecrets_manager_paid_experiencealone.SecretsHelper#can_start_secrets_manager_trial?uses the same ability, so the Start trial button and the mutation agree. On Self-Managed a group Owner gets a permission error from the group mutation, and the trial can only be started through the new admin mutation. The root-group check, the Self-Managed license branches, and theinstance_idbranch inside the mutation became unreachable and were removed. - Adds instrumentation. Two new internal events,
secrets_manager_instance_trial_startedandsecrets_manager_instance_trial_start_failed, record the user identifier only, and both feed Service Pingcounts_allmetrics. - Adds a new assignable fine-grained token permission,
start_secrets_manager_instance_trial, scoped to the instance boundary. - Both trial mutations declare
read_secrets_manageralongside their own permission as the fine-grained token scope, because the returnedSecretsManagerEntitlementtype requires it. Without it a token holding only the start permission would start the trial and get a silentnullentitlement back. Client#start_secrets_manager_trialnow also wrapsGitlab::HTTP::SilentModeBlockedErrorandJSON::ParserErrorinto its error class, so silent mode on Self-Managed surfaces as the unavailable error instead of a 500.- Updates docs: regenerates the GraphQL reference, the fine-grained token GraphQL table, and the audit event types. User-facing docs for the trial are left to the frontend MR, since the Admin area entry point does not exist yet.
Everything in this MR sits behind the existing default-off secrets_manager_paid_experience feature flag, so no changelog entry is included.
Out of scope / follow-ups
- Hiding the group page "Start trial" button for Owners on Self-Managed. This is frontend gating, tracked at https://gitlab.com/gitlab-org/gitlab/-/work_items/623384.
- The Admin area "Start trial" UI itself, tracked at https://gitlab.com/gitlab-org/gitlab/-/work_items/623334.
References
- Issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/628116
- Parent epic: https://gitlab.com/groups/gitlab-org/-/work_items/21755
- Frontend counterpart: https://gitlab.com/gitlab-org/gitlab/-/work_items/623334
- Add-on sibling: https://gitlab.com/gitlab-org/gitlab/-/work_items/628115
- Self-managed E2E plan: https://gitlab.com/gitlab-org/gitlab/-/work_items/628023
- gitlab.com trial mutation issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/602353
Screenshots or screen recordings
Not applicable. This MR is backend only.
How to set up and validate locally
Run GDK as Self-Managed, not as a SaaS simulation, with a real online cloud license that has an Ultimate plan. With a real online cloud license, CustomersDot staging is called for the entitlement and trial-start calls, so the instance's UUID needs to be registered there.
-
In a Rails console, enable the feature flag:
Feature.enable(:secrets_manager_paid_experience) -
Sign in as an admin, enable admin mode, and open GraphiQL at
/-/graphql-explorer. -
Run the query below and confirm
stateisTRIAL_ELIGIBLE. This requires the instance UUID to already be registered on CustomersDot staging.{ secretsManagerInstanceEntitlement { state creditsRemaining trialExpiresAt } } -
Run the mutation below and confirm
stateisTRIAL,errorsis empty,Gitlab::CurrentSettings.secrets_manager_instance_enrolledis nowtrue, and an audit event of typesecrets_manager_trial_startexists.mutation { secretsManagerInstanceStartTrial(input: {}) { entitlement { state creditsRemaining } errors } } -
Run the same mutation again and confirm it returns the error "A Secrets Manager trial is already active for this instance." with no call made to CustomersDot.
-
As a non-admin top-level group Owner, run the mutation below against a group you own and confirm it returns a permission error. On Self-Managed the group mutation is denied by GroupPolicy.
mutation { secretsManagerStartTrial(input: { groupPath: "<root group path>" }) { errors } } -
Sign out of the admin account, sign in as a non-admin user, and run both the instance query and the instance mutation from above. Confirm both return a permission error.
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.