Look up tokens and digests with every db_key_base key
What does this MR do and why?
Phase 2 of making db_key_base rotatable: tokens, digests and signatures derived from db_key_base keep working under every configured key.
db_key_base can be an array: the last key is current, every key decrypts. During a rotation ([new, old], then [old, new]), lookups that compute a value with the current key only and compare it to what is stored would fail. That affects PAT login, runner, deploy, trigger, agent, build and SCIM token lookups, secret detection lookups, webhook log URL checks, JWT, LFS and dependency proxy tokens, and Geo logout state.
Nothing changes for instances today: config/settings.rb still raises when more than one key is configured (Phase 3 removes that). With a single key, every digest, ciphertext and signing secret is byte-identical to today.
Changes, by area:
- Candidates
Gitlab::CryptoHelper.sha256_candidatesandaes256_gcm_encrypt_candidatesreturn one value per key, current key first.encryption_keyis no longer memoized in the module (KeyProvideralready memoizes).
- Encrypted token lookups use every key:
Authn::TokenField::EncryptionHelper.encrypt_token_candidates,encode_candidateson the encrypted and digest strategies and onTokenAuthenticatablemodels,Authn::TokenField::Finders::BaseEncrypted(also behind the partitioned job and runner finders),Ci::Trigger.with_token,Ci::Runner.search,WebHookLog#url_current?, and EESecurity::SecretDetection::TokenLookupService(falls back toencodefor models without candidates, such as OAuth applications, whose secrets do not depend ondb_key_base). - Digest tokens (personal access tokens, EE runner controller tokens) are found under any key's salt. Rewriting them with the current salt on use is in !259034.
- JWT/HMAC verification accepts every key:
Gitlab::JWTToken.decode, the dependency proxy token (Auth::ContainerProxyAuthenticationService.secrets),Gitlab::LfsToken#token_valid?and EEGitlab::Geo::Oauth::LogoutState#decode. New tokens are still signed with the current key.
Out of scope: tokens found under an old key are not re-encrypted here; the Phase 4 rake task does that. Not changed because they are short-lived or never compared: rate-limit scopes, the GitHub import cache key, release evidence summary_sha, WebHookLog#url_hash writes. Stored digests that can't be recomputed (credit card hashes, analytics ids, import source identifiers) still use the current key, as on master; the approach is tracked in https://gitlab.com/gitlab-org/gitlab/-/work_items/631665.
Specs cover each lookup and verifier with a token created under the previous key. A mutation check (candidates returning only the current key) makes 9 of the new lookup examples fail. This adds the spec helper stub_db_key_base_keys(*keys), the same as in !258816 (merged).
References
- Issue (Phase 2): https://gitlab.com/gitlab-org/gitlab/-/work_items/631457
- Epic: https://gitlab.com/groups/gitlab-org/-/work_items/23829
- Phase 1: !258705 (merged)
- Phase 1.2: !258816 (merged)
- Phase 3: https://gitlab.com/gitlab-org/gitlab/-/work_items/631456
- Stored digests: https://gitlab.com/gitlab-org/gitlab/-/work_items/631665
- Token digest retirement status: https://gitlab.com/gitlab-org/gitlab/-/work_items/631693
- Rehash token digests on use: https://gitlab.com/gitlab-org/gitlab/-/work_items/631700
Screenshots or screen recordings
Not applicable, backend only.
How to set up and validate locally
-
Run the specs:
bin/rspec spec/lib/gitlab/crypto_helper_spec.rb spec/lib/authn/token_field spec/lib/gitlab/jwt_token_spec.rb spec/lib/gitlab/lfs_token_spec.rb spec/services/dependency_proxy/auth_token_service_spec.rb spec/models/ci/trigger_spec.rb spec/models/hooks/web_hook_log_spec.rb ee/spec/lib/gitlab/geo/oauth/logout_state_spec.rb ee/spec/services/security/secret_detection/token_lookup_service_spec.rb
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.