security(key_management): stop Key Management Admin group from granting system admin#329
Draft
gonzalesedwin1123 wants to merge 7 commits into
Draft
security(key_management): stop Key Management Admin group from granting system admin#329gonzalesedwin1123 wants to merge 7 commits into
gonzalesedwin1123 wants to merge 7 commits into
Conversation
TDD red: group_key_admin currently implies base.group_system, escalating any Key Management Admin to full Settings/System admin. New tests assert the implication is gone (direct and transitive), that admin implies operator instead, that key admins keep field access to the KMS-wrapped key material (the AWS/Azure providers read it in user context), and that the 19.0.2.0.1 migration strips the link from existing databases.
…tem admin spp_key_management.group_key_admin listed base.group_system in implied_ids. In Odoo, implied_ids grants the implied groups to members - it is not a membership precondition - so assigning the key management role silently made the user a full Settings/System administrator. - replace the base.group_system link with group_key_operator_officer (admin is a superset of operator and stays an internal user); system admins keep access through their own explicit ACL rows on every key model, so nothing is lost and nothing new is granted - extend the encrypted_key field gate to include group_key_admin: the AWS KMS / Azure Key Vault providers read the cached wrapped key in the calling user's context, which previously only worked because key admins were escalated to system; the field holds KMS-wrapped ciphertext, never plaintext key material - ship migrations/19.0.2.0.1/post-migration.py to strip the unsafe link from databases installed from the v19.0.2.0.0 / 2026.07 releases (removing the XML line alone never unlinks an existing relation); membership is a computed transitive closure, so removal revokes the escalation immediately - reorder the groups file so the operator group is defined before the admin group that now references it - bump to 19.0.2.0.1 with HISTORY fragment
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 19.0 #329 +/- ##
==========================================
- Coverage 74.28% 64.98% -9.31%
==========================================
Files 372 141 -231
Lines 25385 13645 -11740
==========================================
- Hits 18857 8867 -9990
+ Misses 6528 4778 -1750
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
TDD red: the menu root is parented under Settings, which Odoo only shows to base.group_erp_manager; load_menus prunes menus whose ancestors are invisible, so a user holding only the key admin role has no way to reach Key Management in the UI once the group no longer escalates to system admin.
…admins Review follow-ups: - move the Key Management root menu from Settings to top level: the Settings root is gated to base.group_erp_manager and Odoo prunes menus with invisible ancestors, so dedicated key admins (no longer system admins) had no way to reach the menu; disclosed in HISTORY - the encrypted_key field-gate comment also names GCP KMS, which reads the cached wrapped key in user context like AWS/Azure - the migration logs all_user_ids (includes members of any implying group), and its test now pins idempotence with assertNoLogs
…on; pin admin retention Staff-review polish: - security_groups.xml now states that group_key_admin is deliberately NOT linked into spp_security.group_spp_admin (key custody separated from platform administration), so a future consistency sweep doesn't reintroduce the over-grant - add a test that a system admin (without the key group) retains field read access and menu visibility - pinning the other half of the design
This was referenced Jul 23, 2026
Member
Author
This was referenced Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
spp_key_management.group_key_admin("Key Management Admin") was defined withimplied_idslinkingbase.group_system. In Odoo,implied_idsgrants the implied groups to members — it is not a membership precondition — so assigning the key management role silently made that user a full Settings/System administrator. The link shipped with the initial platform export and is present in both released tags (v19.0.2.0.0,2026.07). This is the same bug shape fixed for the DCI group in #327; a repo-wide sweep (multi-line-aware) confirms these were the only two instances.Unlike the DCI case, the escalation was load-bearing:
spp.encryption.key.encrypted_key(the KMS-wrapped key material) is field-gated, and the AWS KMS / Azure Key Vault providers read and write it in the calling user's context (the database provider sudo's; those two don't) — so key admins could only use those providers because they were escalated to system.Fix
group_key_adminno longer impliesbase.group_system. It impliesgroup_key_operator_officerinstead (admin ⊇ operator; operator impliesbase.group_user, so a key admin stays an internal user). The groups file is reordered so the operator group is defined before the admin group that now references it.spp_security.group_spp_admin(divergence from security(dci): stop DCI Administrator group from granting system admin #327): system admins already hold their own explicit ACL rows on every key model and the menu lists both groups, so they lose nothing — while the conventional link would newly grant key rotation and provider configuration to every OpenSPP admin. Least privilege wins for the encryption control-plane.encrypted_key's field gate now includesgroup_key_admin. This is a strict reduction of that role's access (from "entire system" to "key models + KMS-wrapped ciphertext"); plaintext key material is never stored in that field.migrations/19.0.2.0.1/post-migration.pystrips the link via the ORM and logs the count of affected users. Removing the XML line alone never unlinks an existing relation (the file is notnoupdate, but data loads only addCommand.linkentries); Odoo 19 computes membership as a transitive closure, so the migration revokes the escalation immediately. The new admin→operator implication is applied by the regular data load.base.group_erp_managerand Odoo prunes menus whose ancestors are invisible, so a dedicated key admin (no longer a system admin) had no way to reach Key Management in the UI. Pinned by aload_menus-based test; disclosed in HISTORY.19.0.2.0.0 → 19.0.2.0.1+HISTORY.mdfragment.Tests (TDD — red commit first; the module had no group tests at all)
New
tests/test_key_admin_group.py:test_key_admin_does_not_grant_system: link absent fromimplied_idsandall_implied_ids(blocks reintroduction through an intermediate group); behavioral: a user granted the group does nothas_group('base.group_system').test_key_admin_implies_operator: operator and internal-user membership via the closure.test_key_admin_reads_encrypted_key: key admin reads the wrapped key field (guards the AWS/Azure provider paths); operator is denied (pins the field gate).test_migration_removes_escalation: recreates the released state, runsmigrate()underassertLogs, asserts the link and its closure effects are gone and the operator-facing warning is emitted.spp_key_management75/75, dependentsspp_encryption26/26 andspp_claim_16926/26. Lint (ruff/prettier/pylint-odoo) clean; security audit 0 errors / 0 warnings.Operational note for deployments
On
v19.0.2.0.0/2026.07installs, anyone granted Key Management Admin has been a de-facto system admin since the grant. The migration revokes the privilege on upgrade, but operators should audit actions taken by those users while escalated. Additionally — unlike the DCI case — such users had system-level access to KMS-wrapped key material and provider configuration, so if key-custody policy assumes separation between platform admins and key admins, consider rotating data keys after the upgrade.Known gap (pre-existing, not addressed here)
Key operators cannot use the AWS KMS / Azure Key Vault provider paths (model read ACL but no field access to
encrypted_key, and those providers don't sudo like the database provider does). This predates this PR — those paths only ever worked for system-level users — and will be filed separately rather than mixing a functional repair into this security fix.Reviewed
Adversarial staff review: the group rework, migration, and tests were confirmed sound against Odoo 19 source (sequential
ref()resolution makes the operator-first reorder necessary and sufficient;MigrationManagerbounds run the script exactly on the2.0.0 → 2.0.1upgrade; field-levelgroupsraiseAccessErroron non-member attribute reads even with a warm cache, so the operator-denied test cannot false-pass; the operator implication grants nothing beyond internal-user status — its other ACL rows were already internal-user-readable metadata). On the critical field-gate question: for the database provider the stored value is AES-GCM ciphertext under a KEK from env/config unreachable via the ORM, and key admins already receive plaintext DEKs through the sanctionedKeyManagerAPI (_check_key_accessallows them all operations), so field access to ciphertext is strictly weaker than what the role legitimately has — not an escalation. The review's one confirmed problem — key admins losing all UI access to Key Management because the menu lived under the erp-manager-gated Settings root — is fixed in this PR (menu re-parented to top level, test-pinned, disclosed in HISTORY); its minor notes (GCP KMS also reads the field in user context; migration log counting; migration idempotence coverage) are addressed. The pre-existing asymmetric-key field-gate inconsistency and operator-on-KMS gap remain out of scope as documented.Merge order & release-note note
Per the cross-PR interaction analysis (2026-07-24,
internal/plans/security-prs-interaction-analysis.md): no hard ordering — every combination with the other open security PRs commutes (verified: key-management workflows don't regress; key admins have their own ACL rows and_check_key_accesschecks the group independently; the two system-gated points are fixed in this PR). Soft preference: land this early (or in the same release as #336/#265) — until the implication is stripped, key admins still transitbase.group_systemand pass those PRs' new gates.Release notes must state: Key Management Admin no longer grants system admin; in combination with #265, OAuth signing-key rotation now requires explicit
base.group_system(the two key domains are deliberately separate — OAuth JWT keys live inir.config_parametervia spp_oauth, not inspp.encryption.key). The migration logs a warning, but it belongs in the notes.