[Rails] Support OpenBao metadata CAS
Summary
Implement Compare-And-Set (CAS) support for both secret values and metadata in the Rails backend to prevent race conditions when multiple clients update the same secret concurrently. This extends the existing secret CAS functionality to include metadata versioning, utilizing OpenBao's new metadata CAS capabilities.
Prerequisites: This implementation requires OpenBao PR #1372 to be merged first.
Why this matters
Business Value:
- Prevents data loss from concurrent secret updates in multi-user environments
- Improves reliability of GitLab's secrets management for enterprise customers
- Enables safe collaboration on secret configurations across teams
User Stories
- As Priyanka (Platform Engineer), I want to update secret metadata without overwriting concurrent changes made by my teammate so that I can safely manage infrastructure configurations across teams.
- As Amy (Application Security Engineer), I want version-controlled secret updates so that I can ensure no security configurations are accidentally overwritten during compliance reviews.
- As Sasha (Software Developer), I want to receive clear error messages when my secret update conflicts with recent changes so that I can resolve conflicts and continue my development work.
Proposal
Extend the existing GraphQL mutations and types to support OpenBao's metadata CAS functionality by adding metadata version tracking fields and CAS parameters.
Important: Metadata CAS Only This implementation focuses exclusively on metadata CAS and does not include secret value CAS for the following reasons:
- Current
ProjectSecretUpdate
mutation's secret value updates are temporary functionality - Secret value updates will be moved to a separate, dedicated mutation in future iterations
- Metadata updates are the primary concern for the current mutation design
- Adding secret CAS now would create technical debt when secret value functionality is separated
Key Changes:
- Add
metadata_version
field toProjectSecretType
- Add required
metadata_cas
parameter to update mutation - Implement proper error handling for metadata CAS failures
Out of Scope
- Automatic conflict resolution: Users must manually resolve version conflicts
- Historical version browsing: This only tracks current versions, not full version history
Acceptance Criteria
-
ProjectSecretType
returnsmetadata_version
field from OpenBao -
ProjectSecretUpdate
mutation requiresmetadata_cas
parameter -
When metadata_cas
matches current version, update succeeds -
When metadata_cas
doesn't match current version, update fails with clear error message
Implementation Table
Group | Issue Link |
---|---|
backend |
|
frontend | Frontend: Handle metadata CAS in Secret Manager UI |
Technical Implementation Details
OpenBao Version Management
Files to Update:
-
GITLAB_OPENBAO_VERSION
- Contains commit hash from openbao-internal repository -
ee/lib/tasks/gitlab/secrets_management/openbao.rake
- Contains SHA256 checksums for binary downloads
Update Process:
- Wait for openbao-internal to publish new release with CAS support
- Update
GITLAB_OPENBAO_VERSION
with new commit hash - Update SHA256 checksums in
openbao.rake
for new binaries:bao-darwin-arm64
bao-darwin-amd64
bao-linux-amd64
GraphQL Schema Changes
ProjectSecretType Extensions
field :metadata_version,
type: GraphQL::Types::Int,
null: false,
description: 'Current version of the metadata.'
Mutation Argument Updates
# ProjectSecretUpdate
argument :metadata_cas, GraphQL::Types::Int,
required: true,
description: 'Check-and-set parameter for metadata version. Metadata update will only succeed if current metadata version matches this value.'
Service Layer Updates
-
SecretsManagement::SecretsManagerClient
- Addmetadata_cas
parameter to update_kv_secret_metadata method -
SecretsManagement::ProjectSecret
- Addmetadata_version
attribute -
SecretsManagement::UpdateProjectSecretService
- Handle requiredmetadata_cas
parameter and version extraction from responses -
SecretsManagement::CreateProjectSecretService
- Extract and returnmetadata_version
from OpenBao create responses -
SecretsManagement::ReadProjectSecretService
- Extract and returnmetadata_version
from secret metadata -
SecretsManagement::ListProjectSecretsService
- Extract and returnmetadata_version
for each secret in list
Verification Steps
1. GraphQL Schema Verification
query {
project(fullPath: "test-project") {
projectSecrets {
nodes {
name
metadataVersion
}
}
}
}
Expected Response:
{
"data": {
"project": {
"projectSecrets": {
"nodes": [
{
"name": "database-password",
"metadataVersion": 2
}
]
}
}
}
}
2. CAS Mutation Testing
These mutations should succeed and return updated metadata version.
mutation {
projectSecretUpdate(
input: {
projectPath: "test-project"
name: "test-secret"
description: "updated description"
metadataCas: 1
}
) {
projectSecret {
metadataVersion
}
errors
}
}
mutation {
projectSecretCreate(
input: {
projectPath: "test-project"
name: "new-secret"
description: "new secret description"
secret: "secret-value"
environment: "production"
branch: "main"
}
) {
projectSecret {
name
metadataVersion
}
errors
}
}
3. Error Scenario Testing
# Should fail with CAS error when metadata version is outdated
mutation {
projectSecretUpdate(
input: {
projectPath: "test-project"
name: "test-secret"
description: "updated description"
metadataCas: 1 # Assuming current metadata version is 3
}
) {
errors # Should contain CAS failure details
}
}