Operations Guide¶
This guide covers deploying, configuring, and operating a Diogenes instance for IT professionals and system administrators.
Prerequisites¶
- Python 3.12+
- PostgreSQL 14+ (with asyncpg driver)
- Linux, macOS, or Windows with WSL
Installation¶
From Source¶
Dependencies¶
Diogenes uses the following core dependencies:
| Package | Purpose |
|---|---|
| FastAPI | Web framework and API |
| SQLAlchemy (async) | Database ORM with asyncpg driver |
| Alembic | Database migrations |
| Cryptography | Cryptographic primitives |
| Pydantic | Data validation and serialization |
| Jinja2 | HTML template rendering |
| Argon2-cffi | Password hashing |
| PyJWT | JWT token management |
Database Setup¶
PostgreSQL Configuration¶
Create a database for Diogenes:
Run Migrations¶
Diogenes uses Alembic for database schema management:
Connection String¶
Configure the database URL via environment variable:
Configuration¶
Diogenes is configured via environment variables, managed by Pydantic Settings.
Core Settings¶
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
-- | PostgreSQL connection string |
ENFORCE_HTTPS |
true |
Redirect HTTP to HTTPS |
SECRET_KEY |
-- | Secret key for JWT signing |
Rate Limiting¶
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_API_QUERY |
100 |
Requests per minute per IP |
RATE_LIMIT_KEY_REGISTRATION |
10 |
Key registrations per hour per IP |
RATE_LIMIT_ENDORSEMENT_OFFER |
20 |
Endorsement offers per day per IP |
RATE_LIMIT_ATTESTATION_EVENT |
50 |
Attestation events per hour per IP |
RATE_LIMIT_LOG_ANCHOR |
12 |
Log head anchors per hour per IP |
RATE_LIMIT_ATTESTATION_REQUEST_PER_TARGET |
50 |
Attestation requests per hour per target |
RATE_LIMIT_REQUEST_POLICY_PER_TARGET |
20 |
Request-policy publications per hour per target |
Graph Traversal¶
| Variable | Default | Description |
|---|---|---|
GRAPH_TRAVERSAL_TIMEOUT_SECONDS |
10 |
BFS timeout for endorsement graph queries |
GRAPH_TRAVERSAL_CACHE_TTL_SECONDS |
60 |
Cache TTL for graph query results |
Starting the Server¶
Development¶
Or directly with uvicorn:
Production¶
For production deployments, use multiple workers and disable reload:
uvicorn diogenes.server.app:create_app --factory \
--host 0.0.0.0 \
--port 8000 \
--workers 4 \
--log-level info
Consider placing behind a reverse proxy (nginx, Caddy) for TLS termination and additional security headers.
Health Monitoring¶
Health Endpoint¶
Returns {"status": "ok", "version": "..."} when the server is running.
Log Chain Integrity¶
Periodically verify the transparency log chain:
Returns {"valid": true, "entry_count": N, "errors": []} if the chain is intact.
Log Head¶
Monitor the log head for growth:
Backup and Recovery¶
Database Backup¶
Back up the PostgreSQL database regularly:
Restore¶
Key Recovery¶
If a key holder loses access to their active key but has a backup, they can use key succession with recovery to transition to a new key. This requires the expired key's recovery credentials.
Security Hardening¶
Production Checklist¶
- [ ] Enable HTTPS enforcement (
ENFORCE_HTTPS=true) - [ ] Set a strong
SECRET_KEY(usepython -c "import secrets; print(secrets.token_urlsafe(32))") - [ ] Configure rate limits appropriate for your traffic patterns
- [ ] Place behind a reverse proxy with TLS termination
- [ ] Restrict database access to the application server only
- [ ] Enable PostgreSQL SSL connections
- [ ] Set up automated database backups
- [ ] Monitor the health endpoint
- [ ] Schedule periodic log chain verification
Firewall Rules¶
Only the following ports need to be exposed:
| Port | Service | Access |
|---|---|---|
| 443 | HTTPS (reverse proxy) | Public |
| 8000 | Uvicorn (if no proxy) | Public or internal |
| 5432 | PostgreSQL | Internal only |
Temporal Anchoring¶
OpenTimestamps Setup¶
Temporal anchoring uses OpenTimestamps to anchor log hashes to Bitcoin. In Phase 0, a stub service is used. For production Bitcoin anchoring:
- Set
DIOGENES_OTS_ENABLED=true. Theopentimestampsclient library is a main dependency, so no extra install step is needed. - Configure the calendar servers via
DIOGENES_OTS_CALENDAR_URLS(comma-separated). - Anchor log heads periodically via
POST /api/v1/log/anchor. The endpoint is operator-only (issue #501): sendAuthorization: Bearer <token>with a JWT signed by the operator key whosesubis the operator fingerprint. SeePOST /api/v1/log/anchorfor the response shape and error codes.
Anchored entries include an ots_proof field that can be independently verified against the Bitcoin blockchain.
GET /api/v1/log/anchor-status stays public; poll it and act on its
advisory anchor_due flag to decide when to call the anchor endpoint.
What an anchor status means¶
POST /api/v1/log/verify and the verification portal classify each proof into
one of four states. The distinctions are the point — before #515 the code
reported "verified" whenever proof bytes were present, without decoding them.
| Status | What it asserts | What it does not assert |
|---|---|---|
confirmed |
The proof parses, a Bitcoin attestation is reachable, and the block header at the attested height carries exactly the attested Merkle root. Reports the block height, block time, and which header source vouched for it. | Nothing beyond the trustworthiness of that header source. |
pending |
The proof parses; a calendar holds the commitment but it is not yet in a block. | That anything is anchored yet. |
unverifiable |
An anchor is present but cannot be confirmed here: no header source configured (the default), the source was unreachable, the per-request lookup budget was spent, or it is the legacy development stub proof. | That the anchor is good or bad. It is an absence of evidence. |
invalid |
The proof is provably not an anchor for this digest — it does not parse, or its attested Merkle root disagrees with the block header at that height. | — |
Only invalid is reported as an anchor error. unverifiable deliberately is
not: POST /log/verify clears the durable verification checkpoint whenever an
anchor error appears, so treating "no evidence" as an error would make every
request re-scan the whole log.
Confirming anchors against Bitcoin¶
Confirmation requires a block header at the attested height. There is no
header source by default, so every Bitcoin attestation reads unverifiable
until you configure one:
DIOGENES_BITCOIN_HEADER_SOURCE_URL=https://blockstream.info/api
DIOGENES_BITCOIN_HEADER_SOURCE_TIMEOUT=5.0 # seconds
DIOGENES_OTS_HEADER_LOOKUP_BUDGET=8 # header lookups per verify request
Off by default on purpose: enabling it routes part of every verification
request through a third-party block explorer, and confirmed is then only as
trustworthy as that explorer. The status records header_source so the claim
is never overstated. If you enable one, update the HTTPS-egress description in
infra/vpc.tf to name it.
The lookup budget bounds how many header fetches one POST /log/verify may
perform. That route is unauthenticated, so the bound is what keeps it from
becoming an outbound-amplification vector. It counts fetches, not anchors: a
request is charged only when a classification actually reached for a header, so
with no header source configured — the default, where classification performs
no network I/O at all — the budget is never spent. Past the budget the free,
offline parse still runs, so a corrupt proof is still reported invalid; only
an unresolved Bitcoin attestation truncates the pass, and a truncated
verification is never written to the durable checkpoint.
Header sources are expected to speak the Esplora JSON dialect, which renders
merkle_root in Bitcoin's conventional display order (reversed hex). Diogenes
flips it back to the raw header order the attestation commits to before
comparing; a custom source must return raw order.
Upgrading pending proofs¶
A pending proof only becomes evidence once the calendar folds it into a Bitcoin
block. POST /api/v1/log/anchor will fetch that completion and merge it in —
but only when explicitly enabled:
POST /log/anchor is already operator-only (#501, see above), so this flag is
not what stands between an anonymous caller and a calendar. Enable it when you
want a completed proof merged in rather than left pending; leave it off if a
calendar round-trip inside the request transaction is not acceptable in your
deployment. With it off, the upgrade performs no network I/O at all.
Upgrading only ever contacts a calendar named in DIOGENES_OTS_CALENDAR_URLS
(https only): the URI comes out of the proof, so it is attacker-influenced. A
proof naming an unconfigured calendar is skipped, and the skip is reported in
the response's upgrade_detail and logged at WARNING rather than silently
looking like "not yet in a block". A re-anchor upgrades an existing proof
and never overwrites it, so stored evidence survives.
Legacy stub proofs¶
Every log_entries.ots_proof written before real OTS was enabled holds
b"ots-stub-proof", which is not an OpenTimestamps serialization. These are
classified unverifiable — never invalid — so turning DIOGENES_OTS_ENABLED
on against existing data produces zero anchor errors and no checkpoint churn.
No migration or backfill is required.
Optionally, clear them first so those heads can be re-anchored for real. This is deliberately not automated:
Diagnosing "everything reads unverifiable"¶
GET /api/v1/log/anchor-status reports two signals for exactly this:
ots_library_available—falsemeans theopentimestampslibrary is not importable in this image (a stripped install), so nothing can be classified.bitcoin_header_source_configured—falsemeans no header source is set, which is the default and the usual answer.
Known limitation¶
Without a header source, a proof whose Bitcoin attestation commits to the wrong
digest is unverifiable, not invalid. That is structural: catching it
requires a block header. It is never reported confirmed.
Troubleshooting¶
Common Issues¶
| Symptom | Cause | Solution |
|---|---|---|
503 Transparency log unreachable |
Database connection issue | Check PostgreSQL is running and DATABASE_URL is correct |
429 Too Many Requests |
Rate limit exceeded | Wait for the rate limit window to reset, or adjust limits |
HTTPS redirect loop |
Proxy not forwarding X-Forwarded-Proto |
Configure reverse proxy to pass protocol headers |
| Slow graph traversal | Large endorsement graph | Increase GRAPH_TRAVERSAL_TIMEOUT_SECONDS or reduce max_depth |
| Challenge expired errors | Clock skew or slow client | Ensure server and client clocks are synchronized (NTP) |
Logs¶
Diogenes logs to standard output via Python's logging module. Configure log level via uvicorn's --log-level flag.