Skip to content

UNOMI-875: PR4 security and tenant hardening (941/942/938)#809

Merged
sergehuber merged 9 commits into
masterfrom
UNOMI-875-pr4-security-tenant
Jul 6, 2026
Merged

UNOMI-875: PR4 security and tenant hardening (941/942/938)#809
sergehuber merged 9 commits into
masterfrom
UNOMI-875-pr4-security-tenant

Conversation

@sergehuber

@sergehuber sergehuber commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Plain-language summary

Multi-tenant security in Unomi 3.1 had gaps that could weaken tenant isolation or expose secrets. API keys could be stored in plain text, quota checks could count the wrong data, and background system work could leave the wrong security identity on a thread. This PR fixes those issues so tenants stay separated and keys are stored safely.

What changed

  • API keys (UNOMI-938): Store a SHA-256 digest and a masked display value instead of the plaintext key. Return the real key only once when it is created. REST responses hide the hash field.
  • Secret hashing: Add a small SecretHashService with hash() and verify() only. Key generation and masking live on ApiKey helpers. Migration tenant init hashes keys the same way.
  • Tenant service: Harden create/delete/reload error handling and null guards on key lookup.
  • Quotas (UNOMI-942): Count profiles and events per tenant, not cluster-wide. Log load and refresh failures clearly; one bad tenant does not block others.
  • Security context (UNOMI-941): Make the system subject thread-safe at startup. Fix executeAsSystem so it saves and restores only the request subject slot and does not copy a privileged subject onto the thread.
  • Tenant access filter: When @RequiresTenant is used, check the {tenantId} path parameter against the caller’s access, not the caller’s own tenant id (which always matched before). No endpoints use this annotation yet; the filter is ready for future tenant-scoped routes.

Test plan

  • mvn -pl api,services-common,services,rest -am test (unit tests including hash, tenant, quota, and execution context)
  • Full integration test suite locally — Elasticsearch profile
  • Full integration test suite locally — OpenSearch profile
  • CI unit test job
  • CI integration test matrix (ES + OS)

References

UNOMI-941, UNOMI-942, UNOMI-938, UNOMI-875

…tion

Derive @RequiresTenant checks from the authenticated subject instead of
X-Unomi-Tenant header. Make the system Subject immutable via AtomicReference
and clear thread-local subjects with remove() after executeAsSystem.
Add per-tenant getAllItemsCount(itemType, tenantId) to persistence and wire
TenantQuotaService to use tenant-scoped profile/event counts instead of
cluster-wide totals.
Store PBKDF2 key hashes and masked keys instead of plaintext in persistence.
Return plaintext only once via ApiKeyCreationResult; suppress keyHash in REST
via ApiKeyRestMixIn. Add legacy key read support and migrate-3.1.0-20 rehash
script. TenantService null guards, create/delete error contracts, and IT updates.
…legacy keys

Multi-tenancy was never released, so remove transitional plaintext "key" field
support and the hash-only migration script. Introduce a reusable SecretHashService
API with ApiKey helpers, implement PBKDF2 hashing in the services bundle, and
wire TenantService to the local bean instead of services-common.
TestTenantService defaulted to real PBKDF2, slowing every test that calls
setupCommonTestData by ~4.5s per method. Add TestSecretHashService for the
default in-memory double and keep SecretHashServiceImpl only in dedicated
tenant/hash unit tests.
API keys are machine-generated with high entropy, so slow password hashing
is unnecessary on every HTTP request. Store and verify keyHash with fast
SHA-256; keep PBKDF2 on hash()/verify() for future low-entropy secrets.
Remove the redundant lookupHash field.
Drop unused PBKDF2 and the hashHighEntropySecret split. API keys use a
single hash()/verify() pair with SHA-256 hex and constant-time compare.
…pers

Keep the new security API minimal (two methods). Move key generation and
masking onto ApiKey static helpers so the domain model does not depend on
SecretHashService for non-cryptographic operations.
…dling

Add getRequestSubject/clearRequestSubject so executeAsSystem snapshots only
the request-subject slot and does not leak a privileged subject on restore.
SecurityFilter checks the path tenantId for @RequiresTenant. Harden quota
load and per-tenant usage refresh logging. Clarify Tenant masked-key javadoc.
@sergehuber sergehuber merged commit 962647c into master Jul 6, 2026
6 checks passed
@sergehuber sergehuber deleted the UNOMI-875-pr4-security-tenant branch July 6, 2026 11:33
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