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 SecretsManagerInstanceStartTrial mutation. It takes no arguments, is marked experiment, and ships in milestone 19.4. It is admin only, enforced by a new start_secrets_manager_instance_trial GlobalPolicy permission that requires Self-Managed, a native_secrets_management license, and the secrets_manager_paid_experience feature flag. The mutation does a live entitlement check first: the entitlement must be trial_eligible, otherwise it returns "already active" for an existing trial or "ineligible" for anything else. It then calls CustomersDot's start_secrets_manager_trial(instance_id:), expires both entitlement cache layers, and auto-enrolls the instance through InstanceEnrollmentService#enroll if it is not already enrolled, so the frontend does not need to call instanceSecretsManagerEnroll separately. It writes a secrets_manager_trial_start audit 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.secretsManagerInstanceEntitlement field that returns the existing SecretsManagerEntitlement type, resolved with a nil namespace. It is admin only via a new read_secrets_manager_instance_entitlement permission, and is not available on GitLab.com or when the feature flag is off.
  • Updates the SecretsManagerEntitlement type so its fine-grained token boundary can be either a group or the instance. The read_secrets_manager permission now has an instance boundary, because the adapter's group is nil when the entitlement is instance-wide.
  • Adds an offlineLicense field to SecretsManagerEntitlement. It is true on an air-gapped instance, false on an online one, and null on GitLab.com. The entitlement state alone cannot tell an air-gapped instance from an online one past its grace window, since both resolve to BLOCKED, so frontends had to infer air-gapped from OFFLINE_PAID. The field is available on both Group.secretsManagerEntitlement and the instance query.
  • Gives the group mutation a dedicated permission, following !253805 (merged). SecretsManagerStartTrial now authorizes start_secrets_manager_trial instead of admin_group. The permission is granted to Owners through config/authz/roles/owner.yml and prevented in GroupPolicy unless NamespaceEnrollment.trial_allowed? holds: GitLab.com, top-level group, and a license that includes Secrets Manager. It deliberately does not depend on the secrets_manager_namespace_enrollment flag, so the billing gate rolls out with secrets_manager_paid_experience alone. 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 the instance_id branch inside the mutation became unreachable and were removed.
  • Adds instrumentation. Two new internal events, secrets_manager_instance_trial_started and secrets_manager_instance_trial_start_failed, record the user identifier only, and both feed Service Ping counts_all metrics.
  • Adds a new assignable fine-grained token permission, start_secrets_manager_instance_trial, scoped to the instance boundary.
  • Both trial mutations declare read_secrets_manager alongside their own permission as the fine-grained token scope, because the returned SecretsManagerEntitlement type requires it. Without it a token holding only the start permission would start the trial and get a silent null entitlement back.
  • Client#start_secrets_manager_trial now also wraps Gitlab::HTTP::SilentModeBlockedError and JSON::ParserError into 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

References

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.

  1. In a Rails console, enable the feature flag: Feature.enable(:secrets_manager_paid_experience)

  2. Sign in as an admin, enable admin mode, and open GraphiQL at /-/graphql-explorer.

  3. Run the query below and confirm state is TRIAL_ELIGIBLE. This requires the instance UUID to already be registered on CustomersDot staging.

    {
      secretsManagerInstanceEntitlement {
        state
        creditsRemaining
        trialExpiresAt
      }
    }
  4. Run the mutation below and confirm state is TRIAL, errors is empty, Gitlab::CurrentSettings.secrets_manager_instance_enrolled is now true, and an audit event of type secrets_manager_trial_start exists.

    mutation {
      secretsManagerInstanceStartTrial(input: {}) {
        entitlement {
          state
          creditsRemaining
        }
        errors
      }
    }
  5. 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.

  6. 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
      }
    }
  7. 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.

Edited by Dmytro Biryukov

Merge request reports

Loading
Loading