Skip to content

security(key_management): stop Key Management Admin group from granting system admin#329

Draft
gonzalesedwin1123 wants to merge 7 commits into
19.0from
security-key-admin-group-implies-system
Draft

security(key_management): stop Key Management Admin group from granting system admin#329
gonzalesedwin1123 wants to merge 7 commits into
19.0from
security-key-admin-group-implies-system

Conversation

@gonzalesedwin1123

@gonzalesedwin1123 gonzalesedwin1123 commented Jul 23, 2026

Copy link
Copy Markdown
Member

Do not merge yet — held for human review.

Summary

spp_key_management.group_key_admin ("Key Management Admin") was defined with implied_ids linking base.group_system. In Odoo, implied_ids grants 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

  • Remove the escalation: group_key_admin no longer implies base.group_system. It implies group_key_operator_officer instead (admin ⊇ operator; operator implies base.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.
  • Deliberately NOT linked into 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.
  • Preserve key-admin function on the cloud providers: encrypted_key's field gate now includes group_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.
  • Migration for released databases: migrations/19.0.2.0.1/post-migration.py strips 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 not noupdate, but data loads only add Command.link entries); 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.
  • Menu reachability (adversarial-review finding): the Key Management root menu moved from Settings to top level. The Settings root is gated to base.group_erp_manager and 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 a load_menus-based test; disclosed in HISTORY.
  • Version bump 19.0.2.0.0 → 19.0.2.0.1 + HISTORY.md fragment.

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 from implied_ids and all_implied_ids (blocks reintroduction through an intermediate group); behavioral: a user granted the group does not has_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, runs migrate() under assertLogs, asserts the link and its closure effects are gone and the operator-facing warning is emitted.
  • Green: spp_key_management 75/75, dependents spp_encryption 26/26 and spp_claim_169 26/26. Lint (ruff/prettier/pylint-odoo) clean; security audit 0 errors / 0 warnings.

Operational note for deployments

On v19.0.2.0.0 / 2026.07 installs, 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; MigrationManager bounds run the script exactly on the 2.0.0 → 2.0.1 upgrade; field-level groups raise AccessError on 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 sanctioned KeyManager API (_check_key_access allows 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_access checks 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 transit base.group_system and 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 in ir.config_parameter via spp_oauth, not in spp.encryption.key). The migration logs a warning, but it belongs in the notes.

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
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.98%. Comparing base (1caf794) to head (a464f75).
⚠️ Report is 3 commits behind head on 19.0.

Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
spp_analytics ?
spp_api_v2_cycles ?
spp_api_v2_entitlements ?
spp_api_v2_gis ?
spp_api_v2_programs ?
spp_api_v2_simulation ?
spp_attachment_av_scan 83.98% <ø> (?)
spp_audit_programs ?
spp_base_common 91.07% <ø> (+0.80%) ⬆️
spp_case_demo ?
spp_case_entitlements ?
spp_case_programs ?
spp_claim_169 58.18% <ø> (?)
spp_cr_type_assign_program ?
spp_dci_compliance ?
spp_dci_demo 94.28% <ø> (+0.88%) ⬆️
spp_dci_server_social ?
spp_demo ?
spp_demo_phl_luzon ?
spp_drims ?
spp_drims_sl ?
spp_drims_sl_demo ?
spp_encryption 55.79% <ø> (?)
spp_farmer_registry_demo ?
spp_gis_report ?
spp_grm_demo ?
spp_indicator ?
spp_indicator_studio ?
spp_key_management 38.52% <ø> (?)
spp_metric_service ?
spp_mis_demo_v2 70.99% <ø> (+0.03%) ⬆️
spp_programs 65.27% <ø> (+<0.01%) ⬆️
spp_registry 86.94% <ø> (+0.10%) ⬆️
spp_security 69.56% <ø> (+2.89%) ⬆️
spp_simulation ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_key_management/models/encryption_key.py 91.66% <ø> (ø)

... and 296 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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
@gonzalesedwin1123

Copy link
Copy Markdown
Member Author

Staff-review follow-ups filed as separate issues (all pre-existing, deliberately out of this PR's scope):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant