Enable service account creation on free tier

Summary

Enables service account creation for free tier (SaaS) and starter/unlicensed (self-managed) plans, gated behind the existing service_accounts_available_on_free_or_unlicensed feature flag.

The previous MR (!225722 (merged)) introduced the 100-account limit enforcement. This MR completes the enablement by updating the availability checks in the policy layer so free tier users can actually create and manage service accounts when the feature flag is enabled.

Note: The service_accounts_available_on_free_or_unlicensed feature flag must not be enabled until the follow-up MR for PAT expiry enforcement is merged. See Design document compliance below.

Changes

  • Authn::ServiceAccounts.available?: Now accepts an optional root_namespace parameter and falls back to checking the service_accounts_available_on_free_or_unlicensed feature flag when the license does not include service_accounts
  • Authn::ServiceAccounts.available_for_namespace?: Falls back to the same feature flag for non-trial namespaces without a paid subscription
  • ee/app/policies/ee/project_policy.rb: Updated service_accounts_enabled condition from scope: :global to scope: :subject and passes @subject.root_ancestor to available? for per-namespace feature flag targeting
  • ee/lib/ee/api/entities/group_detail.rb: Replaced direct licensed_feature_available?(:service_accounts) call with Authn::ServiceAccounts.available_for_namespace? to ensure consistency with the feature flag path
  • ee/app/controllers/ee/admin/application_settings_controller.rb: Changed service account settings params to use Authn::ServiceAccounts.available? instead of License.feature_available?(:service_accounts) so free-tier admins (with FF enabled) can access SA settings

How it works

When the service_accounts_available_on_free_or_unlicensed feature flag is enabled (for a specific namespace or globally):

  1. Policy layerservice_accounts_available conditions in group, project, and global policies return true, unblocking all SA permissions (create, read, delete, admin)
  2. Service layer — The 100-account limit (from !225722 (merged)) is enforced during creation
  3. API/UI — Service account management endpoints and sidebar items become accessible

No changes to the license feature definition (PREMIUM_FEATURES) — the feature flag provides the free tier access path alongside the existing license check.

Design document compliance

Guards from the machine identity design document:

Requirement Status
100 SA limit for free/trial Enforced (!225722 (merged))
SA cannot create other SAs Existing policy guards
SA cannot create top-level namespaces Existing external: true + policy guards
Free tier PATs must always expire Follow-up MR required (blocker before FF enablement)

Per the design document, free tier service accounts must always have PAT expiration enforced (unlike Premium/Ultimate where it's configurable). This will be implemented in a separate follow-up MR to keep review scope focused.

How to validate

Click to expand validation steps

Prerequisites

Enable the feature flag:

# Rails console - globally
Feature.enable(:service_accounts_available_on_free_or_unlicensed)

# Or for a specific group (SaaS):
Feature.enable(:service_accounts_available_on_free_or_unlicensed, Group.find(<group_id>))

Self-Managed (Free/Unlicensed Instance)

1. Verify SA creation works

# As admin, create a service account via API
curl --request POST \
  --header "PRIVATE-TOKEN: <admin_token>" \
  --header "Content-Type: application/json" \
  "http://localhost:3000/api/v4/service_accounts"

Expected: Service account is created successfully.

2. Verify 100 SA limit is enforced

# Rails console - check current count
User.service_accounts.count

# For testing, stub the limit:
stub_const('Authn::ServiceAccounts::LIMIT_FOR_FREE', 2)

Expected: After reaching the limit, creation fails with appropriate error.

SaaS (Free-Tier Namespace)

1. Setup a free-tier group and enable FF

# Rails console
group = Group.find_by_path('<your-group>')
# Ensure no paid subscription
group.gitlab_subscription&.destroy
Feature.enable(:service_accounts_available_on_free_or_unlicensed, group)

2. Verify SA creation works

# As group owner
curl --request POST \
  --header "PRIVATE-TOKEN: <owner_token>" \
  --header "Content-Type: application/json" \
  "http://localhost:3000/api/v4/groups/<group_id>/service_accounts"

Expected: Service account is created successfully.

  • Closes #591625
  • Part of &20439 (Allow SAs for Free tier)
  • Depends on !225722 (merged) (merged) — limit enforcement for free tier
Edited by Smriti Garg

Merge request reports

Loading