Closes 36 findings from the project audit. The headline win is correctness: the daemon now actually does what the README claims, all three audit-log integrity layers verify, and the rotation control loop terminates. Critical fixes: - Orchestrator bumps Credential.last_rotated_at on success and writes a sealed credential_versions row, so the policy evaluator stops re-firing the same rotation every tick - Envelope encryption (AES-256-GCM, KEK-wrapped DEK, AAD-bound) is now wired into the rotation success path; the credential_versions table is no longer dead schema - cre audit verify checks all three layers (hash chain + HMAC ratchet + Merkle batches) instead of only the chain - BatchSealerScheduler runs every 300s and on shutdown so audit_batches actually fills with signed Merkle roots - Compliance bundle exports the real audit_batches list and covers public_key.pem under the manifest's checksum - cre run/watch hard-fail without CRE_HMAC_KEY_HEX + CRE_KEK_HEX rather than silently using zero defaults Quality fixes: - HTTP::Client wrapper with connect/read timeouts + bounded retry-with- jitter on 408/429/5xx; Telegram error logs redact the bot token - EventBus dispatch isolates Block subscribers behind select+timeout so one stuck subscriber can't pin the bus - RotationWorker checks rotations.in_flight before dispatching to dedupe duplicate schedules; PG enforces it at the DB via partial unique index - Commit-step failures transition rotations to Inconsistent (a terminal state) and raise a critical alert instead of pretending success - env_file rotator uses per-PID pending paths plus an exclusive flock on a sibling .lock file so two daemons can't race on the rename - Versioned migration runner replaces the soup of CREATE IF NOT EXISTS; SQLite gains BEFORE UPDATE / BEFORE DELETE triggers on audit_events - AuditSubscriber publishes a critical alert + panics by default when log writes fail, instead of silently dropping - Engine.stop drains via an ack channel with a 2s deadline rather than the previous magic 50ms sleep - Policy DSL moved into CRE::Policy::Dsl module (consumers `include` it); multiple matching policies raise PolicyConflictError - Evaluator uses credentials.overdue() per-max_age group instead of loading every credential each tick - AuditRepo gains each_in_range streaming and all_batches enumeration; bundle export streams instead of buffering - New cre verify-bundle <zip> CLI re-runs every check the bundle README documents; new cre tui-demo for synthetic event preview Tests: 179 unit (up from 159), all passing. New rotation_worker_spec, new SigV4 regression vector, new orchestrator success-path coverage (envelope write + last_rotated_at bump + Inconsistent state). Docs: README + learn/00..04 aligned with the post-audit behavior; removed /snooze (was a stub) and the AwsIamKey/Database CredentialKind variants that had no rotators. Added required-env-var documentation. |
||
|---|---|---|
| .. | ||
| .github/workflows | ||
| config | ||
| docker | ||
| learn | ||
| scripts | ||
| spec | ||
| src | ||
| .editorconfig | ||
| .gitignore | ||
| CONFIGURATION.md | ||
| LICENSE | ||
| README.md | ||
| justfile | ||
| shard.lock | ||
| shard.yml | ||
README.md
██████╗██████╗ ███████╗
██╔════╝██╔══██╗██╔════╝
██║ ██████╔╝█████╗
██║ ██╔══██╗██╔══╝
╚██████╗██║ ██║███████╗
╚═════╝╚═╝ ╚═╝╚══════╝
Credential rotation enforcer written in Crystal. Tracks credentials, evaluates compile-time-checked policies, and executes the four-step rotation contract against AWS Secrets Manager, HashiCorp Vault, GitHub fine-grained PATs, and local
.envfiles. Single binary, live TUI, bidirectional Telegram bot, tamper-evident audit log, signed compliance evidence export.
This is a quick overview — security theory, architecture, and full walkthroughs are in the learn modules. Operator setup lives in CONFIGURATION.md.
What It Does
- Compile-time-checked policy DSL (typo'd action symbols, missing fields, bad credential property references all fail
crystal build) - Bus + plugin architecture — typed events fan out across Crystal channels; subscribers (audit, TUI, Telegram, log) react independently; rotators register at compile time via
register_as :kindmacro - Four-step rotation contract (
generate → apply → verify → commit) borrowed from AWS Secrets Manager's rotation Lambda template, dual-version safe under concurrent reads - Three-layer tamper-evident audit log: SHA-256 hash chain + ratcheting HMAC-SHA256 + Ed25519-signed Merkle batches
- AEAD envelope encryption (AES-256-GCM, per-row DEKs wrapped by KEK, AAD-bound to credential identity, reserved
algorithm_idbyte for crypto agility) - Hand-rolled live TUI (no external TUI framework — stdlib ANSI escapes only) with event-driven repaints coalesced to a tick interval
- Bidirectional Telegram bot — viewer tier (
/status,/queue,/history,/alerts) + operator tier (/rotate) - Compliance evidence export bundle (signed ZIP with audit log, Merkle batches, control mapping for SOC 2 / PCI-DSS / ISO 27001 / HIPAA)
Quick Start
git clone https://github.com/CarterPerez-dev/Cybersecurity-Projects.git
cd Cybersecurity-Projects/PROJECTS/intermediate/credential-rotation-enforcer
shards install && shards build cre --release
./bin/cre demo
Or use the install script:
curl -fsSL https://raw.githubusercontent.com/CarterPerez-dev/Cybersecurity-Projects/main/PROJECTS/intermediate/credential-rotation-enforcer/scripts/install.sh | bash
[!TIP] This project uses
justas a command runner. Typejustto see all available recipes.Install:
curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin
Demo tiers
just demo # Tier 1 — zero-deps SQLite + .env rotator (under 30s)
just tui-demo # Live TUI preview with synthetic events (8s, no setup)
just demo-full # Tier 2 — Docker Compose: PG + LocalStack + Vault + fake-GH
just demo-full-down # tear down the stack
Daemon usage
cre run and cre watch require two 32-byte secrets — the seed key for the audit-log HMAC ratchet and the KEK that wraps per-row data keys. Generate fresh values once and store them somewhere durable (KMS, password manager, sealed env file):
export CRE_HMAC_KEY_HEX=$(openssl rand -hex 32)
export CRE_KEK_HEX=$(openssl rand -hex 32)
export CRE_SIGNING_KEY_HEX=$(openssl rand -hex 32) # optional: enables Merkle-batch sealing
Then:
cre run --db=sqlite:cre.db # headless daemon
cre watch --db=sqlite:cre.db # daemon + live TUI
cre check --db=sqlite:cre.db --output=json # one-shot CI gate (no key required)
cre rotate <credential-id> # manual rotation (uses same env-driven rotators as run)
cre policy list # inspect compiled policies
cre audit verify # hash chain + HMAC ratchet (+ Merkle if --public-key given)
cre export --framework=soc2 --out=evidence.zip # signed compliance bundle
cre verify-bundle evidence.zip # offline re-verify a bundle
cre check exits 1 when any credential violates its policy — drop into any CI pipeline.
Flagship Rotators
| Rotator | What it talks to | Auth |
|---|---|---|
| AWS Secrets Manager | secretsmanager.<region>.amazonaws.com |
SigV4 (rolled from scratch in src/cre/aws/signer.cr) |
| HashiCorp Vault | vault read database/creds/<role> + lease revoke |
X-Vault-Token |
| GitHub fine-grained PATs | POST/DELETE /user/personal-access-tokens |
Bearer ghp_... |
Local .env file |
atomic temp+rename | n/a |
Adding a fifth rotator means dropping a single file in src/cre/rotators/ — the register_as :kind macro hooks it into the registry at compile time. Zero changes to the orchestrator, scheduler, or any subscriber.
Architecture
┌──────────────────────────────────────┐
│ cre (single Crystal binary) │
│ │
┌────────────┐ │ ┌──────────────────────────────┐ │
│ Scheduler │─────►│ │ Typed Event Bus │ │
│ (fiber) │ │ └──┬─────┬─────┬─────┬─────┬───┘ │
└────────────┘ │ │ │ │ │ │ │
│ ┌──▼──┐ ┌▼────┐ ┌▼──┐ ┌▼──┐ ┌▼───┐ │
│ │Rot. │ │Audit│ │TUI│ │Tg │ │Pol.│ │
│ │Wrkr │ │Sub │ │Sub│ │Bot│ │Eval│ │
│ └──┬──┘ └──┬──┘ └───┘ └───┘ └────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Persistence (PG / SQLite) │ │
│ │ + 3-layer audit integrity │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
All long-lived components are fibers in one OS process. The bus is in-process (Crystal channels are nanosecond-scale) so the architectural overhead is essentially free. Per-subscriber overflow policy: Block for audit (compliance — never drop), Drop for TUI / metrics / Telegram (best-effort).
The Three-Layer Audit Log
Layer 3 ─ Ed25519-signed Merkle batches → auditor verifies with public key only
Layer 2 ─ HMAC ratchet (key zeroized per rotation) → past entries unforgeable
Layer 1 ─ SHA-256 hash chain → any single-row tampering breaks forward chain
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Postgres ─ append-only via TRIGGER + role grants (INSERT-only)
cre audit verify walks all three layers and reports which (if any) is broken.
Stack
Language: Crystal 1.20+
Dependencies: crystal-db (DB abstraction), crystal-pg (PostgreSQL), crystal-sqlite3 (SQLite), tourmaline (Telegram framework, used minimally), webmock.cr (test HTTP mocks)
Direct LibCrypto FFI for AES-256-GCM AEAD (Crystal stdlib OpenSSL::Cipher lacks GCM auth_data/auth_tag) and Ed25519 signing (stdlib lacks high-level wrapper). Bindings live in src/cre/crypto/aead.cr and src/cre/audit/signing.cr.
Testing: stdlib Spec runner, 179+ unit tests + integration tests against real PostgreSQL via Docker.
Configuration
Setup is fully env-var driven — no config file required. See CONFIGURATION.md for the operator guide:
- Required env vars (
CRE_KEK_HEX,CRE_HMAC_KEY_HEX,DATABASE_URL) - Per-rotator setup (AWS IAM policy, Vault token policy, GitHub admin PAT)
- Telegram bot creation + chat-ID discovery
- systemd service unit with hardening directives
- Production security checklist
Learn
This project includes step-by-step learning materials covering security theory, architecture, and implementation.
| Module | Topic |
|---|---|
| 00 - Overview | Prerequisites, quick start, three-tier demo path |
| 01 - Concepts | Rotation theory, real breaches, NIST/SOC2/PCI/ISO/HIPAA framework controls |
| 02 - Architecture | Bus + plugin design, persistence layers, three-layer audit integrity, AEAD envelope |
| 03 - Implementation | Code-level walkthrough; where to look in source for each concept |
| 04 - Challenges | 10 ranked extension challenges (PG ALTER USER, Slack, ML-KEM, OpenTimestamps, SPIFFE, JIT broker, etc.) |
License
AGPL 3.0