Repository navigation
fix(policy): API policy reloads survive a restart and are recorded (F21) - #12
Merged
Merged
Conversation
POST /tenants/{tenant}/policy only swapped the compiled policy in
memory. A restart silently put the configured file back, and nothing
recorded that the policy had changed, or that anyone had tried.
Finishes the work-in-progress on f21-policy-durability, ported onto the
current code, with the regression tests it listed and three changes to
its design:
- Saved under ${SENTINEL_DATA_DIR}/policies/ BEFORE the policy takes
effect: staged, fsynced, then renamed (and the directory fsynced on
POSIX), so a crash leaves the old file or the new. A failed save
changes nothing; the API answers 500.
- Files are named by SHA-256 of the tenant, with the tenant stored
inside, instead of <tenant>.yaml behind a new [A-Za-z0-9._-] rule. Any
tenant name SENTINEL_API_TOKENS accepts still works, no name can
escape the directory, and "acme" and "ACME" cannot share a file on a
case-insensitive file system (Windows, macOS).
- Reloads are serialized. Interleaved, a slow save of v2 could land
after v3's: v3 enforced, v2 on disk, and the restart brought back v2.
The concurrency test shows exactly that with the lock removed.
At startup a saved policy replaces the configured one only when its
policy_version is higher, the reload's own rule. A saved policy that
cannot be read or no longer loads stops serve and check with an error
naming the file, as a broken configured file does.
Every reload becomes a PolicyChanged span (applied, ignored, rejected
or failed; versions; SHA-256 of the submitted text) in a policy-change
run owned by that tenant, exported at severity medium, rendered on the
dashboard, and logged as a POLICY security line. The registry gains
apply_if_newer so the submitted text is parsed once.
Verified end to end against a real `sentinel serve`: an admin reload to
v42 is applied, an agent token is refused (403), the reload appears in
the tenant's runs, and after a restart the tenant is still on v42.
Docs: README limitations, and the site's policy, limitations,
deployment and forensics pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
# Conflicts: # CHANGELOG.md
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.
What and why
Review finding F21:
POST /tenants/{tenant}/policyonly swapped the compiled policy in memory. A restart silently put the configured file back, and nothing recorded that the policy changed, or that anyone tried. The docs listed it as a limitation ("a restart forgets API policy reloads").This finishes the WIP on
f21-policy-durability(18 days old, "NOT READY TO MERGE: the regression tests … are not written"), ported onto the current code, with its tests and three design changes:<tenant>.yaml, after a new[A-Za-z0-9._-]rule on tenant namesSENTINEL_API_TOKENSaccepts still works;acme/ACMEcannot share a file on Windows or macOSBehaviour:
${SENTINEL_DATA_DIR}/policies/before it takes effect. A failed save changes nothing and answers500.policy_version(the reload's own rule), so an operator who bumps the configured file gets the file.sentinel serveandsentinel checkwith an error naming the file (checked by hand:checkexits 1,serverefuses to start).PolicyChangedspan (applied / ignored / rejected / failed, versions, SHA-256 of the submitted text) in apolicy-changerun owned by that tenant, exported at severitymedium, shown on the dashboard, and logged as aPOLICYline.deploy/compose.yamlalready mounts its volume atSENTINEL_DATA_DIR, so saved policies persist with no deployment change.Testing
tests/test_policy_durability.py(9 tests): survives a restart; a configured file with a higher version wins; applied/ignored/rejected each recorded; the record belongs to that tenant only (another tenant cannot see it; audit export fields); tenant names like../escape,acme,ACME,Acme Corp/EUeach get their own file inside the directory; a failed save changes nothing (manager and API); concurrent reloads; a corrupt saved file stops startup.sentinel serve: admin reload to v42 applied, agent token refused (403), the reload listed in the tenant's runs, still v42 after a restart.ruff,mypy --strictclean. Docs site builds.Rollback note (in the changelog): versions before this cannot replay
policy-changeruns, since they do not know the event type.Security invariants
SENTINEL_ADMIN_TOKENreaches the reload (403 otherwise, verified).🤖 Generated with Claude Code