[go: up one dir, main page]

Skip to content

Draft: Resolve "[Rake] OpenBao Recovery Key Generation"

What does this MR do and why?

References

Testing

gdk stop openbao && yes | gdk reset-openbao-data && gdk start && sleep 3 && bundle exec rake gitlab:secrets_management:openbao:recovery_key_generate
ok: down: /Users/samroque-worcel/code/gdk/services/openbao: 1s
[...]

Persisted key to database
~/code/gdk/gitlab % bundle exec rails c
--------------------------------------------------------------------------------
 Ruby:         ruby 3.3.9 (2025-07-24 revision f5c772fc7c) [arm64-darwin24]
 GitLab:       18.6.0-pre (d69805f1103) EE
 GitLab Shell: 14.45.3
 PostgreSQL:   16.10
--------------------------------------------------------------------------------
Loading development environment (Rails 7.1.5.2)
[1] pry(main)> SecretsManagement::RecoveryKey.active.take.key
  SecretsManagement::RecoveryKey Load (0.3ms)  SELECT "secrets_management_recovery_keys".* FROM "secrets_management_recovery_keys" WHERE "secrets_management_recovery_keys"."active" = TRUE LIMIT 1 /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:sroque-worcel--20250519-QHYJX,console_username:samroque-worcel,line:(pry):1:in `__pry__'*/
=> "5ed5fe015df1ae9a9148a21e89d73562203aa2ad92fe2b1d3965fa9b7a4c83d3"

Database overview:

For secrets manager, we use cell-local OpenBao instances. Access to these is pretty restricted and in certain cases we need to interact with these instances using the recovery key. There is one recovery key per instance. Because the data is sensitive, we've opted to encrypt the data using encrypts:

r = SecretsManagement::RecoveryKey.new { |m| m.key = "test" }.save
  TRANSACTION (0.1ms)  BEGIN
  SecretsManagement::RecoveryKey Create (1.5ms)  INSERT INTO "secrets_management_recovery_keys" ("key", "created_at", "updated_at") VALUES ('{"p":"OM5P/Q==","h":{"iv":"MeBM2a5ro9cMQHgZ","at":"soFE2mjzBvqsS99gbn9Bjw==","i":"MWU2MQ=="}}', '2025-10-20 20:34:18.636187', '2025-10-20 20:34:18.636187') RETURNING "id" /*application:console,db_config_database:gitlabhq_development,db_config_name:main,console_hostname:sroque-worcel--20250519-QHYJX,console_username:samroque-worcel,line:(pry):1:in `__pry__'*/
  TRANSACTION (0.1ms)  COMMIT
=> true

OpenBao provides more documentation, but the summary of it is that the recovery key can be retrieved once. Once retrieved, the endpoint returns an empty array. To my mind, this enforces two requirements:

  • We should enforce that there is only one recovery key in the database.
  • We should avoid losing the recovery key.

Nevertheless, from a Rails perspective, it is possible that the rake command may be called two times, and the API returns two distinct recovery keys. This could happen, for example, if the OpenBao instance is reset. To make the script resilient, we should handle this scenario.

I considered two approaches:

  • Enforce a restriction at the model/database level that enforces that only one row exists in the table. If a new recovery key is retrieved, we can upsert that value. This is the simplest but risks data loss.
  • Add a boolean attribute on the Model, i.e. active. When a new row is inserted, we mark all existing rows as inactive. This prevents data loss.

Note on indexes

An index on "active" is not required because there are less than 1000 rows, according to the docs.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #571318

Edited by Sam Roque-Worcel

Merge request reports

Loading