Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4ce5cde
Add Redis-backed cache client and wire it through AppState
ianmuchyri Jul 29, 2026
f92a604
Cache AuthN inputs: sessions, entity/tenant status, and credentials
ianmuchyri Jul 29, 2026
4d7fa44
Cache AuthZ grant expansion with race-safe group/role invalidation
ianmuchyri Jul 29, 2026
4a0666a
Add cache invalidation test suite
ianmuchyri Jul 29, 2026
1ec70f1
Fix enumerate-before-lock race in bulk session/credential invalidation
ianmuchyri Jul 29, 2026
1f60c2a
Add Redis service to CI so cache-gated tests run
ianmuchyri Jul 29, 2026
cbfd8de
Trim redundant doc-comment boilerplate in the cache-invalidation code
ianmuchyri Jul 30, 2026
8beb0a5
Fix cache poisoning and under-invalidation in the auth/authz cache
dborovcanin Jul 31, 2026
32371ea
cache documentation
arvindh123 Jul 31, 2026
47edd80
Collapse the copy-pasted cache-barrier blocks and instrument populates
dborovcanin Jul 31, 2026
69171ce
update cache documentation
arvindh123 Jul 31, 2026
491b758
Keep the cache barrier intact when Redis is down or a payload is corrupt
dborovcanin Jul 31, 2026
f8c1da8
Update docs
dborovcanin Jul 31, 2026
b9b4a97
Switch cache payload encoding from JSON to MessagePack
ianmuchyri Aug 1, 2026
e80f1f7
cargo fmt after rebase conflict resolution
ianmuchyri Aug 1, 2026
a0fabea
Close the logout cache-barrier gap before the revoke commits
ianmuchyri Aug 3, 2026
29a6563
Establish entity/tenant delete cache barriers before the status flip
ianmuchyri Aug 3, 2026
86d9099
Make the dirty barrier nesting-aware and restore missing observe-path…
ianmuchyri Aug 3, 2026
7576eb2
Invalidate session cache entries when a tenant is disabled or frozen
ianmuchyri Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,46 @@ ATOM_DB_CONNECT_TIMEOUT_SECS=10
ATOM_DB_IDLE_TIMEOUT_SECS=600
ATOM_DB_MAX_LIFETIME_SECS=1800

# --- Cache (Redis) --------------------------------------------------------
# Redis-backed cache for AuthN/AuthZ decision *inputs* (session validity,
# entity/tenant status, credential verification, the effective-grants
# expansion) — never the allow/deny decision itself, which is always
# recomputed. Postgres remains authoritative: this is a pure performance
# optimization, off by default, and every check works correctly with it
# disabled. See src/cache/mod.rs for the full consistency model.
#
# Invalidation is precise and synchronous on every relevant mutation (not
# TTL-based) — the TTLs below are a defense-in-depth safety net only, for the
# rare case an invalidation call itself is lost (e.g. a crash mid-mutation).
# While enabled, an unreachable Redis refuses security-sensitive mutations
# (grants/session/credential changes) rather than committing a change the
# cache can't be told about; reads always fall back to Postgres regardless.
#
# Off by default. Uncomment and point at a reachable Redis (e.g. the `redis`
# service in docker-compose.yml) to enable:
ATOM_CACHE_ENABLED=false
# ATOM_CACHE_REDIS_URL=redis://redis:6379/0
# ATOM_CACHE_POOL_MAX_SIZE=20
# ATOM_CACHE_CONNECT_TIMEOUT_MS=2000
# A slow Redis must never make auth slower than a Postgres-only path — keep
# this small; a request that misses this timeout just falls through to Postgres.
# ATOM_CACHE_OP_TIMEOUT_MS=50
# If true, an unreachable Redis at startup aborts boot (like an unreachable
# Postgres does). If false (default), Atom logs an error and starts anyway
# with caching disabled — recommended, since Redis is a performance
# optimization for reads, not a correctness dependency.
# ATOM_CACHE_FAIL_FAST_ON_STARTUP=false
# Per-category entry TTLs (seconds) — defense-in-depth only, see above.
# ATOM_CACHE_TTL_SESSION_SECS=60
# ATOM_CACHE_TTL_ENTITY_STATUS_SECS=60
# ATOM_CACHE_TTL_TENANT_STATUS_SECS=60
# ATOM_CACHE_TTL_CREDENTIAL_SECS=60
# ATOM_CACHE_TTL_CREDENTIAL_CEILING_SECS=60
# ATOM_CACHE_TTL_GRANTS_SECS=60
# Redis should be network-private (not publicly reachable) and, in any real
# deployment, protected with auth/TLS appropriate to your network — the
# docker-compose `redis` service here is dev-only (loopback-bound, no auth).

# --- Audit retention and abuse controls --------------------------------
ATOM_AUDIT_RETENTION_DAYS=365
ATOM_AUDIT_RETENTION_ENABLED=true
Expand Down
29 changes: 27 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ jobs:
--health-timeout 5s
--health-retries 5

# Cache-gated tests (src/cache/mod.rs's unit tests, tests/m25_cache_invalidation.rs)
# are #[ignore]d specifically so they're skippable without Redis, but
# `--include-ignored` below runs them anyway — they need a reachable
# Redis. One instance serves the whole job, but it is flushed between
# binaries alongside the database recreate: m25 caches under keys derived
# from the *fixed* seeded admin id, so without a flush the admin's grant
# expansion outlives the database it was derived from (and outlives a job
# re-run), and a later binary authorizes against a tenant/role graph that
# no longer exists.
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -66,15 +85,21 @@ jobs:
env:
PGPASSWORD: atom
MAINT_URL: postgres://atom:atom@localhost:5432/atom
ATOM_TEST_REDIS_URL: redis://localhost:6379/0
run: |
set -euo pipefail
command -v psql >/dev/null || {
sudo apt-get update && sudo apt-get install -y postgresql-client
command -v psql >/dev/null && command -v redis-cli >/dev/null || {
sudo apt-get update && sudo apt-get install -y postgresql-client redis-tools
}
psql "$MAINT_URL" -c "SELECT 1" >/dev/null
redis-cli -h localhost -p 6379 PING >/dev/null
run_one() {
psql "$MAINT_URL" -c "DROP DATABASE IF EXISTS atom_test;" >/dev/null
psql "$MAINT_URL" -c "CREATE DATABASE atom_test;" >/dev/null
# Redis must be reset with the database, not just alongside it:
# cached entries keyed off fixed ids (the seeded admin) would
# otherwise describe the database this just dropped.
redis-cli -h localhost -p 6379 FLUSHALL >/dev/null
DATABASE_URL="postgres://atom:atom@localhost:5432/atom_test" \
cargo test "$@" -- --include-ignored --test-threads=1
}
Expand Down
108 changes: 108 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ lapin = { version = "4.10.0", default-features = false, features = [
"rustls--ring",
"rustls-webpki-roots-certs",
] }
redis = { version = "1", default-features = false, features = ["script"] }
deadpool-redis = { version = "0.23", default-features = false, features = ["rt_tokio_1"] }
rmp-serde = "1"

[features]
# Metrics are on by default. Disable at compile time for maximum-performance
Expand Down
40 changes: 40 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ services:
timeout: 5s
retries: 5

# Redis-backed AuthN/AuthZ cache (see src/cache/mod.rs). Off by default
# (ATOM_CACHE_ENABLED=false) — a pure performance optimization, Postgres
# remains authoritative. Pure cache, no persistence needed.
redis:
image: redis:7-alpine
command: ["redis-server", "--save", "", "--appendonly", "no"]
ports:
- "127.0.0.1:${ATOM_CACHE_REDIS_PORT:-6379}:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 5

atom:
image: ${ATOM_IMAGE:-ghcr.io/absmach/atom:latest}
build:
Expand Down Expand Up @@ -88,6 +102,20 @@ services:
ATOM_LOG_LEVEL: ${ATOM_LOG_LEVEL:-${RUST_LOG:-info}}
ATOM_LOG_FORMAT: ${ATOM_LOG_FORMAT:-text}
ATOM_EMAIL_TEMPLATES_DIR: ${ATOM_EMAIL_TEMPLATES_DIR:-/email-templates}
# Off by default. Set ATOM_CACHE_ENABLED=true (and add `redis` to
# depends_on below) once Redis is provisioned — see .env.example.
ATOM_CACHE_ENABLED: ${ATOM_CACHE_ENABLED:-false}
ATOM_CACHE_REDIS_URL: ${ATOM_CACHE_REDIS_URL:-redis://redis:6379/0}
ATOM_CACHE_POOL_MAX_SIZE: ${ATOM_CACHE_POOL_MAX_SIZE:-20}
ATOM_CACHE_CONNECT_TIMEOUT_MS: ${ATOM_CACHE_CONNECT_TIMEOUT_MS:-2000}
ATOM_CACHE_OP_TIMEOUT_MS: ${ATOM_CACHE_OP_TIMEOUT_MS:-50}
ATOM_CACHE_FAIL_FAST_ON_STARTUP: ${ATOM_CACHE_FAIL_FAST_ON_STARTUP:-false}
ATOM_CACHE_TTL_SESSION_SECS: ${ATOM_CACHE_TTL_SESSION_SECS:-60}
ATOM_CACHE_TTL_ENTITY_STATUS_SECS: ${ATOM_CACHE_TTL_ENTITY_STATUS_SECS:-60}
ATOM_CACHE_TTL_TENANT_STATUS_SECS: ${ATOM_CACHE_TTL_TENANT_STATUS_SECS:-60}
ATOM_CACHE_TTL_CREDENTIAL_SECS: ${ATOM_CACHE_TTL_CREDENTIAL_SECS:-60}
ATOM_CACHE_TTL_CREDENTIAL_CEILING_SECS: ${ATOM_CACHE_TTL_CREDENTIAL_CEILING_SECS:-60}
ATOM_CACHE_TTL_GRANTS_SECS: ${ATOM_CACHE_TTL_GRANTS_SECS:-60}
volumes:
- ${ATOM_CERTS_CA_DIR:-./certs}:/certs:ro
- ${ATOM_EMAIL_TEMPLATES_HOST_DIR:-./email-templates}:/email-templates:ro
Expand Down Expand Up @@ -175,6 +203,18 @@ services:
ATOM_LOG_LEVEL: ${ATOM_LOG_LEVEL:-${RUST_LOG:-info}}
ATOM_LOG_FORMAT: ${ATOM_LOG_FORMAT:-text}
ATOM_EMAIL_TEMPLATES_DIR: ${ATOM_EMAIL_TEMPLATES_DIR:-/email-templates}
ATOM_CACHE_ENABLED: ${ATOM_CACHE_ENABLED:-false}
ATOM_CACHE_REDIS_URL: ${ATOM_CACHE_REDIS_URL:-redis://redis:6379/0}
ATOM_CACHE_POOL_MAX_SIZE: ${ATOM_CACHE_POOL_MAX_SIZE:-20}
ATOM_CACHE_CONNECT_TIMEOUT_MS: ${ATOM_CACHE_CONNECT_TIMEOUT_MS:-2000}
ATOM_CACHE_OP_TIMEOUT_MS: ${ATOM_CACHE_OP_TIMEOUT_MS:-50}
ATOM_CACHE_FAIL_FAST_ON_STARTUP: ${ATOM_CACHE_FAIL_FAST_ON_STARTUP:-false}
ATOM_CACHE_TTL_SESSION_SECS: ${ATOM_CACHE_TTL_SESSION_SECS:-60}
ATOM_CACHE_TTL_ENTITY_STATUS_SECS: ${ATOM_CACHE_TTL_ENTITY_STATUS_SECS:-60}
ATOM_CACHE_TTL_TENANT_STATUS_SECS: ${ATOM_CACHE_TTL_TENANT_STATUS_SECS:-60}
ATOM_CACHE_TTL_CREDENTIAL_SECS: ${ATOM_CACHE_TTL_CREDENTIAL_SECS:-60}
ATOM_CACHE_TTL_CREDENTIAL_CEILING_SECS: ${ATOM_CACHE_TTL_CREDENTIAL_CEILING_SECS:-60}
ATOM_CACHE_TTL_GRANTS_SECS: ${ATOM_CACHE_TTL_GRANTS_SECS:-60}
volumes:
- ${ATOM_CERTS_CA_DIR:-./certs}:/certs:ro
- ${ATOM_EMAIL_TEMPLATES_HOST_DIR:-./email-templates}:/email-templates:ro
Expand Down
Loading
Loading