Secrets Manager workers and policies
Secrets Manager runs its background work as Sidekiq workers under ee/app/workers/secrets_management/.
Authorization runs in two layers.
Rails policies live under ee/app/policies/secrets_management/.
OpenBao access control lists (ACLs) are checked against the caller’s JSON Web Token (JWT).
OpenBao also calls back into GitLab through an internal API to deliver audit log lines.
Workers
| Worker | Purpose | Enqueued by |
|---|---|---|
ProvisionProjectSecretsManagerTaskWorker | Provisions a project secrets manager in OpenBao | ProjectSecretsManagers::InitializeService, and the project maintenance tasks cron worker on retry |
ProvisionGroupSecretsManagerTaskWorker | Provisions a group secrets manager in OpenBao | GroupSecretsManagers::InitializeService, and the group maintenance tasks cron worker on retry |
ProvisionProjectSecretsManagerWorker | Legacy direct provision worker, superseded by the task worker above | No longer enqueued anywhere in the codebase |
ProvisionGroupSecretsManagerWorker | Legacy direct provision worker, superseded by the task worker above | No longer enqueued anywhere in the codebase |
DeprovisionProjectSecretsManagerWorker | Deprovisions a project secrets manager | ProjectSecretsManagers::InitiateDeprovisionService, and the project maintenance tasks cron worker on retry |
DeprovisionGroupSecretsManagerWorker | Deprovisions a group secrets manager | GroupSecretsManagers::InitiateDeprovisionService, and the group maintenance tasks cron worker on retry |
ProjectSecretsManagerMaintenanceTasksCronWorker | Retries stale project provision or deprovision maintenance tasks | Cron |
GroupSecretsManagerMaintenanceTasksCronWorker | Retries stale group provision or deprovision maintenance tasks | Cron |
ProjectSecretsManagerReapOrphanTasksCronWorker | Terminates project maintenance tasks that exhausted their retries | Cron |
GroupSecretsManagerReapOrphanTasksCronWorker | Terminates group maintenance tasks that exhausted their retries | Cron |
ReconcileNamespaceSecretCountWorker | Refreshes the cached secret count for one namespace | ReconcileNamespaceSecretCountsCronWorker, and services that change a namespace’s secret count |
ReconcileNamespaceSecretCountsCronWorker | Enqueues ReconcileNamespaceSecretCountWorker for every namespace with a secrets manager | Cron |
EmitSecretsStoredBillableEventWorker | Emits the secrets-stored billing event for one root namespace | EmitSecretsStoredBillableEventCronWorker |
EmitSecretsStoredBillableEventCronWorker | Enqueues EmitSecretsStoredBillableEventWorker for every root namespace with stored secrets | Cron |
SecretRotationReminderBatchWorker | Deprecated, does nothing, replaced by the two workers below | None. It is no longer scheduled. |
ProjectSecretRotationReminderBatchWorker | Sends rotation reminders for project secrets | Cron |
GroupSecretRotationReminderBatchWorker | Sends rotation reminders for group secrets | Cron |
AuditLogWorker | Processes one OpenBao audit log line into a GitLab audit event and a billing event | API::Internal::SecretsManager |
For the business meaning of the billing events, see Secrets Manager fulfillment and entitlement. For the destroy and transfer flows that create deprovision maintenance tasks, see Secrets Manager states and side effects.
All workers set feature_category :secrets_management and idempotent!.
The provision and deprovision workers also set worker_has_external_dependencies!, because they call OpenBao.
Cron schedules
Cron entries live in ee/config/schedule.yml.
| Worker | Schedule |
|---|---|
ProjectSecretsManagerMaintenanceTasksCronWorker | Every minute |
GroupSecretsManagerMaintenanceTasksCronWorker | Every minute |
ProjectSecretsManagerReapOrphanTasksCronWorker | Every 15 minutes |
GroupSecretsManagerReapOrphanTasksCronWorker | Every 15 minutes |
ProjectSecretRotationReminderBatchWorker | Every minute |
GroupSecretRotationReminderBatchWorker | Every minute |
ReconcileNamespaceSecretCountsCronWorker | Daily at 02:23 |
EmitSecretsStoredBillableEventCronWorker | Daily at 04:30 |
Maintenance task retry and reaper rules
ProjectSecretsManagerMaintenanceTasksCronWorker and GroupSecretsManagerMaintenanceTasksCronWorker both inherit from SecretsManagement::BaseMaintenanceTasksCronWorker.
Each run selects tasks that are processable (last_processed_at older than STALE_THRESHOLD) and retryable (retry_count under MAX_RETRIES).
It then re-enqueues the matching provision or deprovision worker per task:
| Constant | Value |
|---|---|
BATCH_SIZE | 100 |
STALE_THRESHOLD | Five minutes |
MAX_RETRIES | 3 |
Each task is retried in isolation. A StandardError on one task is tracked and does not stop the rest of the batch.
A task that reaches MAX_RETRIES drops out of the retryable scope and is never retried again by the cron worker.
ProjectSecretsManagerReapOrphanTasksCronWorker and GroupSecretsManagerReapOrphanTasksCronWorker, both inheriting from SecretsManagement::BaseReapOrphanTasksCronWorker, are the safety net for these stuck rows.
They select tasks with retry_count at or above MAX_RETRIES and last_processed_at older than GRACE_PERIOD (30 minutes).
They then call the deprovision service directly, regardless of whether the task’s action was provision or deprovision.
The deprovision service tears down whatever OpenBao state exists at the task’s snapshot paths, deletes the secrets manager row if it still exists, and deletes the task.
After this pass the parent project or group has no secrets manager and no maintenance task, so the user can initialize again.
The GRACE_PERIOD is longer than both STALE_THRESHOLD and the provisioning and deprovisioning lease duration.
So the reaper never races a worker that is about to retry naturally, or a lease that is about to expire on its own.
Rotation reminder workers
SecretsManagement::ProjectSecretRotationReminderBatchWorker and SecretsManagement::GroupSecretRotationReminderBatchWorker both inherit from SecretsManagement::BaseSecretRotationReminderBatchWorker.
Each run calls its service class (ProjectSecretRotationBatchReminderService or GroupSecretRotationBatchReminderService) in a loop.
The loop is bounded by a MAX_RUNTIME of 30 seconds, and stops after a batch returns fewer processed and skipped rows than the service’s batch size.
SecretsManagement::SecretRotationReminderBatchWorker is deprecated. It does nothing and is no longer scheduled.
The class stays until removal so that jobs queued by older versions still run.
The rotation reminder cron also cleans up orphaned rotation information rows left behind when a secret update fails after the rotation information write. These orphan rows are expected. They are not a problem to fix.
Authorization policies
Policy classes live under ee/app/policies/secrets_management/.
Every one of them delegates entirely to the parent resource’s policy instead of defining abilities itself:
| Policy | Delegates to |
|---|---|
ProjectSecretsManagerPolicy | The project’s ProjectPolicy, through @subject.project |
GroupSecretsManagerPolicy | The group’s GroupPolicy, through @subject.group |
ProjectSecretPolicy | The project’s ProjectPolicy, through @subject.project |
GroupSecretPolicy | The group’s GroupPolicy, through @subject.group |
ProjectSecretsPermissionPolicy | The permission’s resource, through @subject.resource |
GroupSecretsPermissionPolicy | The permission’s resource, through @subject.resource |
NamespaceEnrollmentPolicy | The enrollment’s namespace, through @subject.namespace |
EE::ProjectPolicy and EE::GroupPolicy hold the actual ability rules.
Both follow the same shape:
- A condition checks whether Secrets Manager is available for the subject (
SecretsManagement::Availability.for_project?or.for_group?). When it is not, abilities such asadmin_project_secrets_manager,read_project_secrets,create_project_secrets,update_project_secrets,delete_project_secrets, andcreate_secrets_manager_api_jwt(project), orprovision_secrets_manager,read_secrets_manager,read_secrets_permission,update_secrets_permission, anddelete_secrets_permission(group), are all prevented. - When entitlement awareness is active, further conditions call
SecretsManagement::Entitlement.forand prevent reads, writes, or JWT issuance based on the entitlement state. - When the top-level group is
trial_eligibleand the namespace is not in the beta cohort, conditions on every project and every group that is not the top-level group prevent reads and deletes on the entitlement-aware path, because only the top-level group can start a trial. - On
EE::GroupPolicy, a separate enrollment-allowed condition controls the enrollment and trial abilities (create_secrets_manager_enrollment,delete_secrets_manager_enrollment,read_secrets_manager_enrollment,enable_secrets_manager_add_on,start_secrets_manager_trial).
A Rails ability passing is not enough on its own. OpenBao ACL policies are a second, independent layer: the caller’s JWT must also carry the right OpenBao policy for the operation to succeed. A user with the Rails ability but no matching OpenBao policy still gets a permission error from OpenBao.
Internal API
ee/lib/api/internal/secrets_manager.rb exposes POST /api/v4/internal/secrets_manager/audit_logs.
OpenBao, not a user, calls this endpoint to deliver one audit log line per operation.
Request handling:
- The request body is capped at 1 megabyte.
- Authentication is a shared secret compared against the
Gitlab-Openbao-Auth-Tokenheader. The secret is read from a file at a path fromGitlab.config.openbao.authentication_token_secret_file_path. That path is resolved withPathname#realpathand checked against an allowlist of root paths (Rails.root, or/etc/gitlab/outside development and test), to block symlink-based path traversal. - OpenBao emits two lines per operation, one
requesttype and oneresponsetype. Only theresponseline carries the outcome, sorequestlines are dropped before any processing. - Processing is asynchronous: the endpoint enqueues
SecretsManagement::AuditLogWorkerand returns202 Acceptedwithout waiting on the database.
Audit log processing
SecretsManagement::AuditLog, backed by ActiveModel, parses the raw JSON from OpenBao.
It maps each line to one of eight event types, based on the operation (create, read, update, delete) and whether the path matched a secret’s data or metadata path, for both project and group scope:
secrets_manager_read_project_secret
secrets_manager_create_project_secret
secrets_manager_update_project_secret
secrets_manager_delete_project_secret
secrets_manager_read_group_secret
secrets_manager_create_group_secret
secrets_manager_update_group_secret
secrets_manager_delete_group_secretA create and an update both write the secret’s metadata path as update operations.
AuditLog logs a metadata write only when its custom_metadata carries update_completed_at, which only an update’s final write sets. So a create logs only its create event, from the data path write, and an update logs once.
ProjectSecret and GroupSecret are not ActiveRecord models, so they cannot be audit targets themselves.
AuditLog#log! calls Gitlab::Audit::Auditor.audit to create the GitLab audit event instead, with the project or group as both scope and target.
Sidekiq can redeliver a job whose write already committed, and the audit tables have no natural-key protection against a duplicate insert.
So SecretsManagement::AuditLogWorker wraps this processing with two guards against duplicate processing, keyed on OpenBao’s request ID and cached for six hours:
- One marker guards the audit event write itself.
- A second marker guards emitting the secrets-read billing event, because the local billing path has no event ID of its own to deduplicate on downstream.
Internal event tracking
Secrets Manager tracks product usage through Gitlab::InternalEventsTracking, with event definitions under ee/config/events/.
Examples outside enrollment include create_ci_secret, visit_project_secrets_manager, visit_group_secrets_manager, generate_id_token_for_secrets_manager_authentication, enable_ci_secrets_manager_for_project, enable_ci_secrets_manager_for_group, and disable_ci_secrets_manager_for_group.
Entitlement denial telemetry lives in SecretsManagement::Entitlement::DenialTelemetry.
For enrollment and trial events, see Secrets Manager enrollment.