generated from coulomb/repo-seed
feat(local-identity): Stage 4 — security hardening (NK-WP-0002-T04)
Permission enforcement on startup: enforce_permissions() checks store dir (700), user files (600), signing key, TLS key, audit.log, revoked.json. CLI and run_server() call it before any sensitive operation. New modules: security.py check_store(), enforce_permissions(), print_security_check() audit.py log_event() — append-only TSV audit log (mode 600) revoke.py revoke(jti), is_revoked(jti) — revocation list (mode 600) New CLI commands: security-check Print per-check pass/warn/fail report; exit 1 on failure revoke-token <jti|jwt> Add JTI to revocation list; accepts raw JTI or full JWT Serve integration: Audit log written for auth request, token issuance, and userinfo calls Revocation checked at /userinfo; revoked tokens return 401 Docs: security model section in LocalIdentity.md — threat model, assumptions, non-guarantees, SELinux/AppArmor guidance, revocation usage. 138 tests passing (34 new for Stage 4). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,11 +107,13 @@ representation to prevent silent drift.
|
||||
## CLI reference
|
||||
|
||||
```
|
||||
local-identity init # derive primary user, generate test users
|
||||
local-identity list # list all users in the store
|
||||
local-identity show <username> # display user file
|
||||
local-identity export <username> # emit Keycloak-compatible JSON
|
||||
local-identity security-check # validate filesystem permissions and config
|
||||
local-identity init # derive primary user, generate test users
|
||||
local-identity list # list all users in the store
|
||||
local-identity show <username> # display user file
|
||||
local-identity export <username> # emit Keycloak-compatible JSON
|
||||
local-identity serve [--port P] [--ttl T] # start minimal OIDC server
|
||||
local-identity security-check # validate filesystem permissions
|
||||
local-identity revoke-token <jti|jwt> # add a token JTI to the revocation list
|
||||
```
|
||||
|
||||
## OIDC provider (Stage 3)
|
||||
@@ -228,6 +230,81 @@ production_identity:
|
||||
Re-run `local-identity export --all` — the exported JSON will use `bworsch`
|
||||
as the Keycloak username and a deterministic UUID derived from `net-kingdom/bworsch`.
|
||||
|
||||
## Security model
|
||||
|
||||
### Threat model
|
||||
|
||||
Local Identity is designed for **single-operator, localhost-only** use. The
|
||||
threat model covers accidental exposure, not active adversarial attack.
|
||||
|
||||
| Threat | Control |
|
||||
|--------|---------|
|
||||
| Other local users reading credential files | `~/.local-identity/` mode `700`; user files mode `600`; startup check exits on violation |
|
||||
| Attacker elevates a local OIDC token to production | `iss: local-identity` rejected by production Keycloak; `environment: local` attribute rejected by Keycloak attribute check |
|
||||
| Stolen token used after the fact | Token revocation list (`revoke-token <jti>`); configurable TTL (default 1h) |
|
||||
| Long-lived store left behind post-migration | Explicit retirement step: `rm -rf ~/.local-identity` after Keycloak migration |
|
||||
| OIDC server exposed on non-loopback interface | Server hard-codes `127.0.0.1`; `0.0.0.0` binding is not offered |
|
||||
|
||||
### Assumptions
|
||||
|
||||
- The operator's Linux account is not compromised (Local Identity cannot
|
||||
protect against a root-level attacker).
|
||||
- The `LOCAL_IDENTITY_HOME` environment variable is not set to a
|
||||
world-readable path by accident.
|
||||
- The operator's umask does not silently widen permissions before the tool
|
||||
can apply `os.chmod`. (The tool sets permissions explicitly after every
|
||||
write, which limits this window.)
|
||||
|
||||
### Non-guarantees
|
||||
|
||||
- **No MFA.** Token issuance requires only user selection in the browser
|
||||
form; there is no second factor.
|
||||
- **No audit-log integrity.** `audit.log` is append-only by convention, but
|
||||
the OS does not enforce append-only at the file level without `chattr +a`
|
||||
(which requires root). The log records events; it does not prove they were
|
||||
not tampered with.
|
||||
- **Self-signed TLS is not CA-trusted.** The TLS certificate generated for
|
||||
`local-identity serve` is not signed by a trusted CA. OIDC clients must
|
||||
either skip certificate verification or import the certificate manually.
|
||||
- **No privilege separation.** All operations run as the operator's user.
|
||||
|
||||
### Optional SELinux / AppArmor hardening
|
||||
|
||||
If your system uses SELinux or AppArmor you can apply labels to further
|
||||
restrict access to `~/.local-identity/`:
|
||||
|
||||
**SELinux** (example — adapt context type for your policy):
|
||||
```bash
|
||||
chcon -R -t user_home_t ~/.local-identity
|
||||
```
|
||||
|
||||
**AppArmor** — create a profile snippet that denies access to
|
||||
`~/.local-identity/` from any process other than `local-identity`:
|
||||
```
|
||||
deny /home/*/.local-identity/ r,
|
||||
deny /home/*/.local-identity/** r,
|
||||
```
|
||||
|
||||
These are optional hardening layers. The tool's own permission controls
|
||||
(mode 700/600, startup enforcement) provide the baseline.
|
||||
|
||||
### Token revocation
|
||||
|
||||
Tokens issued by `local-identity serve` can be revoked at any time:
|
||||
|
||||
```bash
|
||||
# By JTI (extract from JWT payload manually or from audit.log):
|
||||
local-identity revoke-token <jti-uuid>
|
||||
|
||||
# By passing the full JWT — the JTI is extracted automatically:
|
||||
local-identity revoke-token <jwt-string>
|
||||
```
|
||||
|
||||
Revoked JTIs are stored in `~/.local-identity/revoked.json` (mode 600).
|
||||
The revocation list is checked on every `/userinfo` request. There is no
|
||||
endpoint to un-revoke a token; if you need to re-grant access, obtain a
|
||||
new token via the authorization code flow.
|
||||
|
||||
## Relationship to the SSO platform
|
||||
|
||||
Local Identity is a complementary workstream to the SSO & MFA Platform
|
||||
|
||||
Reference in New Issue
Block a user