Use-Case Guide¶
A goal-oriented cookbook. Where the Quickstart is the fastest path to a running server and USAGE.md is the linear crypto walkthrough (sign / verify / encrypt / … in order), this guide is organized by what you're trying to do — run the rogue-agent demo, wire production auth, fan out audit to a SIEM, embed Aegis as a library, and so on. Each use case is self-contained and starts from a clean checkout.
Version: written for v0.2.0. Every knob below is set by an environment variable; the canonical defaults live in
modules/aegis-server/src/main/resources/application.conf.
Step 0 — Prerequisites & conventions¶
| Tool | Needed for | Notes |
|---|---|---|
| Docker + Docker Compose | every server use case | aegis-server + backing stores run as containers |
curl, jq |
the REST examples | jq is optional but assumed in the output snippets |
openssl |
minting secrets | openssl rand -base64 48 for HMAC/webhook secrets |
JDK 21 + sbt |
the library use case (UC-3) and minting bootstrap JWTs | only if you embed Aegis or run in hmac mode locally |
Get the code and set shell conventions used throughout:
bash
git clone https://github.com/sharma-bhaskar/aegis-kms.git
cd aegis-kms
export AEGIS=http://localhost:8080
export COMPOSE="docker compose -f deploy/docker/docker-compose.yml"
export POSTGRES_PASSWORD="$(openssl rand -base64 24)" # compose requires this; no insecure default
The config model in one line: the server reads HOCON with env-var overrides. Pass them as
-e VAR=value to the container, or add them under the aegis-server.environment: block in the
compose file. The knobs you'll meet here:
| Concern | Env var | Values |
|---|---|---|
| Auth mode | AEGIS_AUTH_KIND |
dev · hmac · oidc |
| Journal | AEGIS_JOURNAL_KIND |
in-memory · postgres · mysql · sqlite |
| Root of trust | AEGIS_CRYPTO_KIND |
in-memory · aws-kms |
| Audit sink | AEGIS_AUDIT_KIND |
stdout · postgres |
| JWT revocation | AEGIS_REVOCATION_KIND |
none · in-memory · redis |
| Policy | AEGIS_POLICY_KIND |
dev · role-based |
| Honey keys | AEGIS_HONEY_KEYS |
comma-separated KeyIds |
A full cheat-sheet is at the bottom of this page.
UC-1 — The flagship: catch a rogue agent and auto-revoke it¶
This is the demo Aegis exists for: an agent acting under a human's authority touches a honey key, and Aegis revokes the key out from under it before its next call lands — the canary's first touch is the trip wire.
Why
hmacmode: issued agent tokens are HS256 JWTs signed withAEGIS_AUTH_HMAC_SECRET, and the server verifies them with the same secret. Indevmode the server readsX-Aegis-Userand ignores Bearer tokens, so an agent token can't actually authenticate — the demo needshmac. We also use the Postgres journal so the honey key survives the restart in Step 3.
Step 1 — Boot in hmac mode with a durable journal¶
bash
export AEGIS_AUTH_HMAC_SECRET="$(openssl rand -base64 48)" # ≥32 bytes
AEGIS_AUTH_KIND=hmac AEGIS_JOURNAL_KIND=postgres AEGIS_REVOCATION_KIND=in-memory \
$COMPOSE up -d
Step 2 — Mint a bootstrap human token¶
In hmac mode even creating a key needs a Bearer JWT. Aegis exposes the issuer the server uses
(dev.aegiskms.iam.JwtIssuer) so you can mint one out-of-band. From the repo root:
bash
sbt 'iam/console'
scala
// In the sbt console (verified against the v0.2.0 JwtClaims / JwtIssuer API).
import dev.aegiskms.iam.*
import java.time.*, java.util.UUID
val secret = sys.env("AEGIS_AUTH_HMAC_SECRET")
val now = Instant.now()
val claims = JwtClaims.Human( // a human principal
jti = UUID.randomUUID().toString,
subject = "alice@org",
issuedAt = now,
expiresAt = now.plusSeconds(3600),
issuer = Some("aegis-demo"),
groups = Set("kms-admins")
)
println(JwtIssuer.hmac(secret).issue(claims)) // copy this JWT
bash
export ALICE_JWT="<paste the JWT>"
export H_ALICE="Authorization: Bearer $ALICE_JWT"
Step 3 — Create a key, then promote it to a honey key¶
```bash KEY_ID=$(curl -s -X POST "$AEGIS/v1/keys" -H "$H_ALICE" -H 'Content-Type: application/json' \ -d '{"spec":{"name":"prod-signing-canary","algorithm":"AES","sizeBits":256,"objectType":"SymmetricKey"}}' \ | jq -r '.id') curl -s -X POST "$AEGIS/v1/keys/$KEY_ID/activate" -H "$H_ALICE" | jq -r '.state' # → Active echo "honey key = $KEY_ID"
Honey keys are read at boot — recreate the server with this KeyId registered.¶
AEGIS_AUTH_KIND=hmac AEGIS_JOURNAL_KIND=postgres AEGIS_REVOCATION_KIND=in-memory \ AEGIS_HONEY_KEYS="$KEY_ID" $COMPOSE up -d ```
Step 4 — Issue an agent token under alice¶
bash
AGENT_JWT=$(curl -s -X POST "$AEGIS/v1/agents/issue" -H "$H_ALICE" -H 'Content-Type: application/json' \
-d '{"label":"invoice-bot","scopes":["Sign","Get"],"ttlSeconds":3600}' | jq -r '.jwt')
export H_AGENT="Authorization: Bearer $AGENT_JWT"
Step 5 — The agent touches the honey key → auto-revoke¶
```bash
First touch trips the HoneyKey detector at Severity.High. AutoResponder.DefaultRules¶
translate High → Revoke, so Aegis revokes the key out from under the agent.¶
curl -s -X POST "$AEGIS/v1/keys/$KEY_ID/sign" -H "$H_AGENT" -H 'Content-Type: application/json' \ -d '{"messageBase64":"aGVsbG8=","algorithm":"RsaPssSha256"}' | jq
The key is now Deactivated (revoke → Deactivated) — the agent's next sign against it fails¶
on the key's state (sign requires Active).¶
curl -s -X POST "$AEGIS/v1/keys/$KEY_ID/sign" -H "$H_AGENT" -H 'Content-Type: application/json' \ -d '{"messageBase64":"aGVsbG8=","algorithm":"RsaPssSha256"}' | jq # → IllegalOperation (key is Deactivated) ```
What v0.2.0 actually does: the auto-responder revokes the key (
AutoResponder→keyService.revoke). Blacklisting the agent's JWT on the same trigger is not yet wired — theRevocationListexists and backs manual revocation + the verifier, but the auto-responder doesn't populate it (a follow-up to #24). So the agent's token stays valid for other keys until it expires; the canary's payoff is the High-severity audit trail plus the dead key.
Step 6 — Read the story out of the audit log¶
bash
curl -s "$AEGIS/v1/audit?key=$KEY_ID" -H "$H_ALICE" | jq '.events[] | {occurredAt, actor, operation, "risk": .context."risk.score", outcome: .context."outcome.decision"}'
You'll see the agent's sign attempt, the HoneyKey detection at Severity.High, and the system's
Revoke action (audited under aegis-system) — all attributed to invoice-bot issued by
alice@org. That parent linkage is the thing a role-centric KMS can't give you.
Variations: swap the honey key for a rate-spike (loop the sign call ~50× to exceed the baseline) or a scope violation (issue the agent with
scopes:["Get"], then try toSign). Honey keys are the most deterministic because the first touch is the trip wire by design.
UC-2 — Exercise the full crypto surface¶
Create → activate → sign / verify → encrypt / decrypt (with AAD) → wrap / unwrap → rotate →
compromise, on REST + CLI. This is covered step-by-step (with expected output for each call) in
the linear walkthrough — follow USAGE.md, Steps 6–13. Use dev auth
(AEGIS_AUTH_KIND=dev, default) for the fastest path; pass -H 'X-Aegis-User: alice' on each
request.
UC-3 — Embed Aegis as a library in your JVM app¶
The library tier (aegis-core, aegis-iam, aegis-audit, aegis-crypto, aegis-persistence)
has zero Pekko on the classpath and exposes the pure KeyService[F[_]] algebra. You can run a
KMS in-process without the HTTP server.
scala
libraryDependencies += "dev.aegiskms" %% "aegis-core" % "0.2.0"
⚠️ Maven Central note: the library jars are not published yet (see Releasing). Until they are, build from source with
sbt publishLocaland depend on the resulting local artifact.
Every operation returns IO[Either[KmsError, _]] — the algebra makes failure explicit rather
than throwing, so you handle Left/Right at the call site:
```scala import cats.effect.* import dev.aegiskms.core.*
object Demo extends IOApp.Simple: private val alice = Principal.Human("alice@org", Set("kms-admins"))
val run: IO[Unit] = KeyService.inMemory.flatMap { svc => // IO[KeyService[IO]] — pure in-memory backend svc.create(KeySpec.rsa2048("demo"), alice).flatMap { case Left(err) => IO.println(s"create failed: $err") case Right(key) => for _ <- svc.activate(key.id, alice) sig <- svc.sign(key.id, "hello".getBytes, SigAlgorithm.RsaPssSha256, alice) _ <- IO.println(sig) // Either[KmsError, Signature] yield () } } ```
Wrap it with the audit + authorization decorators from aegis-audit / aegis-iam to get the
same pipeline the server runs. The Architecture page documents the decorator
stack.
UC-4 — Production authentication¶
dev mode (X-Aegis-User) is for workstations only. Anything network-reachable must use hmac
or oidc.
UC-4a — HMAC JWT (HS256), self-issued¶
bash
export AEGIS_AUTH_HMAC_SECRET="$(openssl rand -base64 48)"
AEGIS_AUTH_KIND=hmac $COMPOSE up -d
Mint caller tokens with JwtIssuer.hmac (see UC-1, Step 2).
Mint caller tokens with JwtIssuer.hmac (see UC-1, Step 2).
Use only inside a single trust boundary — the symmetric secret can both sign and verify.
UC-4b — OIDC / JWKS (Keycloak, Auth0, Okta, Google, …)¶
bash
AEGIS_AUTH_KIND=oidc \
AEGIS_AUTH_OIDC_ISSUER_URI="https://keycloak.example.com/realms/aegis" \
AEGIS_AUTH_OIDC_AUDIENCE="aegis-kms" \
$COMPOSE up -d
Aegis fetches the issuer's .well-known/openid-configuration once at boot to resolve jwks_uri,
caches the JWKS (AEGIS_AUTH_OIDC_JWKS_CACHE_TTL_SECONDS, default 1h), and refreshes lazily on a
kid miss — so provider key rotation needs no operator action. Supports RS256/384/512 and
ES256/384/512. Always set an audience in production to prevent token reuse across services.
UC-5 — Issue agent tokens with durable, multi-node revocation¶
For the auto-responder's Revoke to kill an agent's JWT before it expires — and for that to
hold across a multi-node deployment — back the revocation list with Redis:
bash
AEGIS_AUTH_KIND=hmac AEGIS_REVOCATION_KIND=redis \
AEGIS_REVOCATION_REDIS_URI="redis://localhost:6379" \
$COMPOSE up -d
- none — tokens revoke only at natural expiry.
- in-memory — process-local, lost on restart (fine for single-node dev).
- redis — durable, shared across nodes (production path; required for instant kill-switch).
Issue tokens with POST /v1/agents/issue (UC-1 Step 4) or aegis agent issue (UC-12). Max TTL is
24h by design — agent credentials are deliberately short-lived.
UC-6 — Honey keys (canary keys)¶
Mark real-looking KeyIds so any agent touch fires Severity.High → auto-revoke. Human touches
are not auto-revoked (operators may validate the canary is alive) but still produce an audit row.
bash
AEGIS_HONEY_KEYS="<key-id-1>,<key-id-2>" AEGIS_AUTH_KIND=hmac $COMPOSE up -d
Boot fails fast on a malformed KeyId (a typo'd canary that never catches anything is the opposite
of what you want). See the full walkthrough in UC-1.
of what you want). See the full walkthrough in UC-1.
UC-7 — Human RBAC via the role-based policy engine¶
By default every human is allowed every op (dev policy). To enforce "alice can sign but not
revoke," switch to the role-based engine and bind groups/subjects to KMIP operation names:
bash
AEGIS_POLICY_KIND=role-based $COMPOSE up -d
Bindings live under aegis.policy.role-based in HOCON (mount a config file or extend
application.conf):
hocon
aegis.policy.role-based {
role-bindings { kms-signers = ["Sign", "Verify", "Get"] }
subject-bindings { "break-glass@org" = ["Revoke", "Compromise", "Destroy"] }
}
Groups come from OIDC claims (or the dev header). Unknown operation names fail fast at boot;
kind=role-based with no bindings is rejected (silent allow-all would defeat the purpose).
Valid operations: Create, Get, Locate, Activate, Revoke, Destroy, Sign, Verify, Encrypt, Decrypt,
Wrap, Unwrap, Rotate, Compromise, Query, GetAttributes, AddAttribute.
UC-8 — Choose a persistence backend¶
AEGIS_JOURNAL_KIND |
Use it for | Required vars |
|---|---|---|
in-memory |
tests, demos (lost on restart) | — |
postgres |
production, multi-node | AEGIS_JDBC_URL, AEGIS_JDBC_USERNAME, AEGIS_JDBC_PASSWORD |
mysql |
MySQL 8 shops | AEGIS_MYSQL_JDBC_URL, AEGIS_MYSQL_USERNAME, AEGIS_MYSQL_PASSWORD |
sqlite |
laptops, CI, single-node edge | AEGIS_SQLITE_JDBC_URL (use a file path, not :memory:) |
```bash
SQLite single-file journal — no external DB needed¶
AEGIS_JOURNAL_KIND=sqlite AEGIS_SQLITE_JDBC_URL="jdbc:sqlite:/var/lib/aegis/journal.db" \ $COMPOSE up -d ``` All backends bootstrap their schema on startup; the migration is idempotent.
UC-9 — Use a real AWS KMS root of trust¶
The default in-memory root of trust is a dev stub (HMAC/XOR — not real crypto). For
production, layer Aegis over an AWS KMS customer master key:
bash
AEGIS_CRYPTO_KIND=aws-kms \
AEGIS_CRYPTO_AWS_KMS_REGION="us-east-1" \
AEGIS_CRYPTO_AWS_KMS_KEK_ARN="arn:aws:kms:us-east-1:123456789012:key/abcd-…" \
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… \
$COMPOSE up -d
Credentials come from the AWS SDK default provider chain (env vars, instance metadata, SSO).
GCP / Azure / Vault / PKCS#11 adapters are roadmapped for v0.3.0–v0.5.0.
UC-10 — Audit fan-out and the audit-read API¶
Aegis writes one immutable audit record per state-changing call. The primary sink is durable;
secondary sinks fan out best-effort (failures retry + dead-letter, never block the durable
write). A typical production setup is postgres (for the read API) plus one or more streams.
bash
AEGIS_AUDIT_KIND=postgres \
AEGIS_AUDIT_WEBHOOK_ENABLED=true \
AEGIS_AUDIT_WEBHOOK_URL="https://siem.example.com/ingest" \
AEGIS_AUDIT_WEBHOOK_SECRET="$(openssl rand -base64 48)" \
AEGIS_AUDIT_KAFKA_ENABLED=true \
AEGIS_AUDIT_KAFKA_BOOTSTRAP_SERVERS="broker:9092" \
AEGIS_AUDIT_KAFKA_TOPIC="aegis.audit" \
AEGIS_AUDIT_NATS_ENABLED=true \
AEGIS_AUDIT_NATS_SERVERS="nats://nats:4222" \
$COMPOSE up -d
- Webhook — HTTPS POST per record, X-Aegis-Signature: sha256=<hmac> (GitHub-webhook style).
- Kafka — idempotent producer (acks=all), record key = correlationId for per-request order.
- NATS JetStream — publishAsync + PubAck; durable ack before the record is considered sent.
Each has retry/backoff and a JSONL dead-letter file (AEGIS_AUDIT_*_DEAD_LETTER_FILE). Query the
durable log over REST:
bash
curl -s "$AEGIS/v1/audit?actor=invoice-bot&op=Sign&since=2026-05-01T00:00:00Z&limit=50" \
-H "$H_ALICE" | jq
Filters (since, until, actor, key, op, limit, offset) compose with AND. Retention is
AEGIS_AUDIT_RETENTION_DAYS (default 365; 0 disables pruning).
UC-11 — Observability (Prometheus + OpenTelemetry)¶
bash
$COMPOSE up -d
curl -s "$AEGIS/metrics" | grep aegis_ # per-op counters, latency histograms, error codes
Point traces at any OTLP collector by setting standard OTel env vars on the container:
bash
docker run … \
-e OTEL_TRACES_EXPORTER=otlp \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318 \
-e OTEL_SERVICE_NAME=aegis-server \
ghcr.io/sharma-bhaskar/aegis-server:0.2.0
Spans are named kms.<op> with attributes. Full matrix in
Operations → Observability.
UC-12 — CLI workflows¶
The aegis CLI (download the tarball from a release,
or sbt 'cli/Universal/packageZipTarball'):
bash
aegis login --server http://localhost:8080 --principal alice # stores dev X-Aegis-User config
aegis keys create --name demo --alg AES --size 256
aegis keys sign --id <key-id> --message "hello"
aegis keys rotate --id <key-id>
aegis agent issue --label invoice-bot --scopes Sign,Get --ttl 3600
aegis audit tail --actor invoice-bot --op Sign --watch # live poll every 2s
aegis loginstores the server URL + dev principal (it uses theX-Aegis-Userheader). Forhmac/oidcservers, the CLI needs a Bearer token; minting one is shown in UC-1 Step 2.aegis advisor scanis a stub until the v0.2.1 LLM advisor.
Reference — environment variable cheat sheet¶
| Var | Default | Purpose |
|---|---|---|
AEGIS_HTTP_HOST / AEGIS_HTTP_PORT |
0.0.0.0 / 8080 |
bind address |
AEGIS_AUTH_KIND |
dev |
dev · hmac · oidc |
AEGIS_AUTH_HMAC_SECRET |
— | HS256 secret (≥32 bytes); also signs issued agent tokens |
AEGIS_AUTH_OIDC_ISSUER_URI / _AUDIENCE |
— | OIDC discovery + expected aud |
AEGIS_JOURNAL_KIND |
in-memory |
in-memory · postgres · mysql · sqlite |
AEGIS_JDBC_URL / _USERNAME / _PASSWORD |
localhost | Postgres journal |
AEGIS_SQLITE_JDBC_URL |
…/journal.db |
SQLite file path |
AEGIS_CRYPTO_KIND |
in-memory |
in-memory (dev stub) · aws-kms |
AEGIS_CRYPTO_AWS_KMS_REGION / _KEK_ARN |
— | AWS KMS layered mode |
AEGIS_REVOCATION_KIND |
in-memory |
none · in-memory · redis |
AEGIS_REVOCATION_REDIS_URI |
— | Lettuce URI for the kill-switch |
AEGIS_POLICY_KIND |
dev |
dev · role-based |
AEGIS_HONEY_KEYS |
— | comma-separated canary KeyIds |
AEGIS_AUDIT_KIND |
stdout |
stdout · postgres |
AEGIS_AUDIT_RETENTION_DAYS |
365 |
0 disables pruning |
AEGIS_AUDIT_WEBHOOK_* |
disabled | SIEM HTTPS fan-out |
AEGIS_AUDIT_KAFKA_* |
disabled | Kafka fan-out |
AEGIS_AUDIT_NATS_* |
disabled | NATS JetStream fan-out |
Where to next¶
- Quickstart — the two-command path to a running server.
- USAGE.md — the linear, output-for-every-call crypto walkthrough.
- Architecture — module split, request lifecycle, audit model.
- Operations → Security — the deploy-time hardening matrix.