Implement create/update services for group secret

What does this MR do and why?

This MR implements the create and update services for group-level secrets, preparing the foundation for the GraphQL mutations. This is MR 1 in the implementation sequence, focusing on the service layer and model refactoring.

What this adds:

  • GroupSecret model - Represents group secrets with validation and dirty tracking
  • BaseSecret class - Shared base class for both ProjectSecret and GroupSecret
  • Create and Update services for group secrets
  • Group-specific CI policy refresher for managing OpenBao access policies
  • Service helper modules for shared create/update logic

Key implementation details:

Model architecture:

  • Introduces BaseSecret class that both ProjectSecret and GroupSecret inherit from
  • Extracts common attributes: name, description, environment, metadata_version, timestamps
  • Extracts common validations: name format, presence checks
  • Each subclass adds its own specific attributes:
    • ProjectSecret: branch (for branch-based scoping)
    • GroupSecret: protected (for protected branch restriction)
  • Dirty tracking for policy-relevant attributes (environment, protected for groups; environment, branch for projects)

Service layer refactoring:

  • Shared helpers extracted:
    • Secrets::CreateServiceHelpers - Common create logic for both project and group secrets
    • Secrets::UpdateServiceHelpers - Common update logic for both project and group secrets
  • Group-specific services:
    • GroupSecrets::CreateService - Handles group secret creation with OpenBao integration
    • GroupSecrets::ReadService - Retrieves group secret metadata from OpenBao
    • GroupSecrets::UpdateService - Updates group secrets with optimistic locking
  • Refactored project services:
    • ProjectSecrets::CreateService - Now uses shared helpers
    • ProjectSecrets::UpdateService - Now uses shared helpers
    • Significantly reduced code duplication

CI Policy management:

  • Base refresher: CiPolicies::BaseSecretRefresher with shared policy refresh logic
  • Group refresher: GroupSecretRefresher - Manages policies based on environment + protected
  • Project refresher: ProjectSecretRefresher - Manages policies based on environment + branch
  • Policies are created/updated when secrets are created/updated
  • Policies are automatically removed when no secrets reference them
  • Group secrets refresh policies before metadata update (to count secrets correctly)
  • Project secrets refresh policies after metadata update (to update JWT role)

Two-phase creation/update:

  1. Start phase: Write secret value + metadata with started_at timestamp
  2. Policy refresh: Update OpenBao policies for CI access
  3. Complete phase: Update metadata with completed_at timestamp
  4. Status tracking: Secrets have lifecycle status (COMPLETED, CREATE_IN_PROGRESS, etc.)

Authorization:

  • GroupSecretPolicy delegates authorization to the parent group
  • Uses existing write_secret and read_secret permissions

Why this approach:

  • Group secrets use environment + protected for scoping (vs. project secrets which use environment + branch)
  • Protected flag determines if secret is only accessible from protected branches
  • Follows the same two-phase creation pattern as project secrets for consistency
  • Policy management ensures secrets are only accessible to authorized CI jobs
  • Shared helpers reduce code duplication and ensure consistent behavior

Code organization

Before (project secrets only):

ProjectSecret (standalone model)
ProjectSecrets::CreateService (all logic inline)
ProjectSecrets::UpdateService (all logic inline)
CiPolicies::SecretRefresher (project-specific)

After (supports both project and group secrets):

BaseSecret (shared base class)
├── ProjectSecret (inherits from BaseSecret)
└── GroupSecret (inherits from BaseSecret)

Secrets::CreateServiceHelpers (shared create logic)
Secrets::UpdateServiceHelpers (shared update logic)

ProjectSecrets::CreateService (uses shared helpers)
ProjectSecrets::UpdateService (uses shared helpers)
GroupSecrets::CreateService (uses shared helpers)
GroupSecrets::UpdateService (uses shared helpers)

CiPolicies::BaseSecretRefresher (shared policy logic)
├── ProjectSecretRefresher (project-specific policies)
└── GroupSecretRefresher (group-specific policies)

Related to #577342

This is MR 1 based on !218365 (comment 3004462962)

Next steps:

  • MR 2: Add GraphQL mutations (groupSecretCreate, groupSecretUpdate)
Edited by Erick Bajao

Merge request reports

Loading