Implement deprovision secrets manager

Overview

Resolves #548419 (closed)

This MR implements the complete deprovision functionality for GitLab Project Secrets Manager, allowing full cleanup of project secrets infrastructure.

Architecture Overview

Design Decisions

Main Architectural Decision: Async Deprovision with Complete Resource Removal

The core decision is to implement deprovision as an asynchronous background process that completely removes all project-specific resources:

  • Deprovision flow: activedeprovisioning → (deleted) (via background job)
  • Fresh setup flow: After deprovision, must use existing provisioningactive flow
  • Cleanup scope: Delete JWT roles, all policies, secrets engine, and database record
  • Resource preservation: Keep auth mounts only (shared per namespace)

Key Design Principle: Complete Removal with State Tracking

We use the deprovisioning intermediate state to:

  • User experience: Show users that operation is in progress
  • Prevent duplicate operations: Users can't trigger multiple deprovision operations simultaneously
  • Consistent pattern: Matches existing provisioning workflow

Why Background Workers for Deprovision?

  • Distributed system reliability: Multiple API calls with DB state tracking
  • Auto-retry capability: All OpenBao methods are idempotent, enabling safe retries
  • Performance: Non-blocking UI operations - user gets immediate feedback
  • Scalability: Doesn't tie up web workers for external API calls
  • Error handling: Proper logging and monitoring of failures
  • Consistency: Same pattern as existing provision workflow

Service Separation

  • ProvisionService: Full setup (engines, auth, policies, JWT roles)
  • DeprovisionService: Complete removal (JWT roles, policies, secrets engine, DB record)

Wildcard Secret Handling

During deprovision, all secrets including wildcard secrets are completely removed along with their policies. No special handling is needed since everything is deleted.

GraphQL API

Deprovision Mutation

Request:

mutation {
  projectSecretsManagerDeprovision(input: {
    projectPath: "group/project"
  }) {
    projectSecretsManager {
      status
      project {
        fullPath
      }
    }
    errors
  }
}

Response:

{
  "data": {
    "projectSecretsManagerDeprovision": {
      "projectSecretsManager": {
        "status": "DEPROVISIONING",
        "project": {
          "fullPath": "group/project"
        }
      },
      "errors": []
    }
  }
}

Re-provision After Deprovision

Users must use the existing initialization mutation to set up a fresh secrets manager:

Request:

mutation {
  projectSecretsManagerInitialize(input: {
    projectPath: "group/project"
  }) {
    projectSecretsManager {
      status
      project {
        fullPath
      }
    }
    errors
  }
}

Response (Fresh provisioning):

{
  "data": {
    "projectSecretsManagerInitialize": {
      "projectSecretsManager": {
        "status": "PROVISIONING",
        "project": {
          "fullPath": "group/project"
        }
      },
      "errors": []
    }
  }
}
Edited by Erick Bajao

Merge request reports

Loading