<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Operations on backd</title><link>https://fernandezvara.github.io/backd/docs/operations/</link><description>Recent content in Operations on backd</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://fernandezvara.github.io/backd/docs/operations/index.xml" rel="self" type="application/rss+xml"/><item><title>Deploying config</title><link>https://fernandezvara.github.io/backd/docs/operations/deploying/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/operations/deploying/</guid><description>&lt;p&gt;Everything &lt;code&gt;backd&lt;/code&gt; serves comes from &lt;code&gt;CONFIG_DIR&lt;/code&gt;: realms, schemas, indexes, access rules and &lt;a href="../../functions/"&gt;function&lt;/a&gt; code. Every instance of a deployment must run exactly the same config. If two instances ran different rules or different function code, the same request could be allowed on one and refused on the other.&lt;/p&gt;
&lt;p&gt;So the config is treated like code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it lives in &lt;strong&gt;one config repository&lt;/strong&gt; per deployment;&lt;/li&gt;
&lt;li&gt;CI builds it at &lt;strong&gt;one pinned tag&lt;/strong&gt; into an &lt;strong&gt;immutable artifact&lt;/strong&gt;: an image with &lt;code&gt;backd&lt;/code&gt; and the config inside;&lt;/li&gt;
&lt;li&gt;staging and production run that same artifact;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backd&lt;/code&gt; checks it (see &lt;a href="#what-backd-enforces"&gt;what backd enforces&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-config-repository"&gt;The config repository&lt;a class="hd-anchor" href="#the-config-repository" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;backd template project --dir &amp;lt;directory&amp;gt; --realm &amp;lt;realm&amp;gt;&lt;/code&gt; scaffolds this layout in one step — a sample database and function with tests, both Dockerfiles, &lt;code&gt;compose.yaml&lt;/code&gt; and the CI workflow below — so you don&amp;rsquo;t have to assemble it by hand; see &lt;a href="../../configuration/config-dir/#starting-from-a-template"&gt;Starting from a template&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Production deployment</title><link>https://fernandezvara.github.io/backd/docs/operations/production/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/operations/production/</guid><description>&lt;p&gt;&lt;code&gt;backd&lt;/code&gt; deliberately leaves some protections to the infrastructure around it: TLS, request rate limits and sign-up spam, body size limits at the edge, and database security. The repository ships a reference deployment that puts all of these in place. It lives in &lt;a href="https://github.com/fernandezvara/backd/tree/main/deploy/production"&gt;&lt;code&gt;deploy/production/&lt;/code&gt;&lt;/a&gt;, and CI tests it end to end on every change.&lt;/p&gt;
&lt;p&gt;It uses Docker Compose, but each part maps directly onto other platforms: a Kubernetes ingress, a cloud load balancer, a managed MongoDB.&lt;/p&gt;</description></item><item><title>Metrics</title><link>https://fernandezvara.github.io/backd/docs/operations/metrics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/operations/metrics/</guid><description>&lt;p&gt;Every &lt;code&gt;backd&lt;/code&gt; process can tell &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; how it is doing: requests, errors, database latency and, as the next steps land, functions, jobs and email. It is &lt;strong&gt;off by default&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="turning-it-on"&gt;Turning it on&lt;a class="hd-anchor" href="#turning-it-on" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Variable&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Meaning&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;METRICS_ADDR&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;empty (off)&lt;/td&gt;
 &lt;td&gt;Where this process serves &lt;code&gt;/metrics&lt;/code&gt;, for example &lt;code&gt;:9090&lt;/code&gt;. A listener of its own: it can&amp;rsquo;t be &lt;code&gt;HTTP_ADDR&lt;/code&gt; or &lt;code&gt;BACKD_INTERNAL_ADDR&lt;/code&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;METRICS_TOKEN&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;empty&lt;/td&gt;
 &lt;td&gt;At least 32 characters. When set, every request needs &lt;code&gt;Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code&gt;. Needs &lt;code&gt;METRICS_ADDR&lt;/code&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;backd serve&lt;/code&gt;, &lt;code&gt;backd worker&lt;/code&gt;, &lt;code&gt;backd executor&lt;/code&gt; and &lt;code&gt;backd egress&lt;/code&gt; each serve their own &lt;code&gt;/metrics&lt;/code&gt; (the executor and egress read the same two variables); a process that runs both (&lt;code&gt;serve --with-worker&lt;/code&gt;) serves one. The listener answers &lt;code&gt;GET /metrics&lt;/code&gt; and nothing else (a request without the token gets &lt;code&gt;401&lt;/code&gt; whatever the path), serves at most four scrapes at once, and is a separate server: a slow or flooding scraper never slows the API. If the port can&amp;rsquo;t be bound, the process refuses to start, so a mistake shows at once.&lt;/p&gt;</description></item><item><title>Backup and restore</title><link>https://fernandezvara.github.io/backd/docs/operations/backup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/operations/backup/</guid><description>&lt;p&gt;&lt;code&gt;backd&lt;/code&gt; keeps all state in MongoDB. The config directory describes the structure (collections, validators, indexes, rules), and provisioning can rebuild it at any time, but not the data. Back up MongoDB, and keep the config directory in version control.&lt;/p&gt;
&lt;h2 id="what-a-backup-must-include"&gt;What a backup must include&lt;a class="hd-anchor" href="#what-a-backup-must-include" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Database&lt;/th&gt;
 &lt;th&gt;Holds&lt;/th&gt;
 &lt;th&gt;Rebuilt from config?&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;&amp;lt;realm&amp;gt;__&amp;lt;database&amp;gt;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;your documents&lt;/td&gt;
 &lt;td&gt;no&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;&amp;lt;realm&amp;gt;___system&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the realm&amp;rsquo;s users (emails, roles, disabled flag), password hashes, sessions, API keys, invitations, failed-login counters, the &lt;a href="../../auth/audit/"&gt;audit trail&lt;/a&gt;, encrypted &lt;a href="../../functions/secrets/"&gt;function secrets&lt;/a&gt;, &lt;a href="../../functions/logs/"&gt;invocation history&lt;/a&gt;, &lt;a href="../../functions/jobs/"&gt;async jobs&lt;/a&gt; (queued, running or recently done) and &lt;a href="../../functions/calling/#idempotency"&gt;idempotency claims&lt;/a&gt; (see &lt;a href="../../operations/#realm-system-databases"&gt;system databases&lt;/a&gt;)&lt;/td&gt;
 &lt;td&gt;no, except role seeds&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;What the configuration can and can&amp;rsquo;t rebuild:&lt;/p&gt;</description></item><item><title>Hardening checklist</title><link>https://fernandezvara.github.io/backd/docs/operations/checklist/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fernandezvara.github.io/backd/docs/operations/checklist/</guid><description>&lt;p&gt;Go through this page before any realm faces the internet, and again after changing the configuration or the deployment. The &lt;a href="../production/"&gt;production reference deployment&lt;/a&gt; implements the deployment items, and its test checks many of them.&lt;/p&gt;
&lt;h2 id="realms"&gt;Realms&lt;a class="hd-anchor" href="#realms" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;No realm has &lt;code&gt;auth: disabled&lt;/code&gt;.&lt;/strong&gt; Such a realm is open to anyone who can reach &lt;code&gt;backd&lt;/code&gt;. &lt;code&gt;backd&lt;/code&gt; logs a &lt;code&gt;WARN&lt;/code&gt; line for each one at startup.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The sample realms aren&amp;rsquo;t deployed as they are.&lt;/strong&gt; &lt;code&gt;blog&lt;/code&gt;, &lt;code&gt;shop&lt;/code&gt; and &lt;code&gt;expenses&lt;/code&gt; in &lt;code&gt;examples/config&lt;/code&gt;, and the realms &lt;code&gt;backd template realm --sample&lt;/code&gt; creates, are examples: &lt;code&gt;shop&lt;/code&gt; has &lt;code&gt;auth: disabled&lt;/code&gt;, &lt;code&gt;blog&lt;/code&gt; and &lt;code&gt;expenses&lt;/code&gt; let anyone sign up, and &lt;code&gt;expenses&lt;/code&gt; has &lt;a href="../../examples/expenses/#known-holes"&gt;known holes&lt;/a&gt;. Start from them, then review every setting.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Each realm&amp;rsquo;s &lt;code&gt;signup&lt;/code&gt; mode is the intended one.&lt;/strong&gt; &lt;code&gt;open&lt;/code&gt; lets anyone create an account (and needs the sign-up rate limit below), &lt;code&gt;invite&lt;/code&gt; needs an &lt;a href="../../auth/admin/#invitations"&gt;invitation&lt;/a&gt;, &lt;code&gt;closed&lt;/code&gt; leaves it to operators.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every collection that users or anonymous callers need has a reviewed &lt;code&gt;rules.yaml&lt;/code&gt;, with a &lt;code&gt;rules.test.yaml&lt;/code&gt;&lt;/strong&gt; that &lt;code&gt;backd rules test --strict&lt;/code&gt; runs in CI: see &lt;a href="../../auth/rules/#testing-rules"&gt;Testing rules&lt;/a&gt;. A missing rule denies; a too-broad one (&lt;code&gt;read: &amp;quot;true&amp;quot;&lt;/code&gt;) publishes a collection. See &lt;a href="../../auth/rules/"&gt;access rules&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;cors.origins&lt;/code&gt; lists exactly your web apps&amp;rsquo; origins&lt;/strong&gt;, and no &lt;code&gt;*&lt;/code&gt; for realms with users. Browser apps can keep sessions in an HttpOnly &lt;a href="../../auth/sessions/#session-cookies"&gt;cookie&lt;/a&gt; (&lt;code&gt;sessions.cookie&lt;/code&gt;), which needs this list.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Someone can administer each realm:&lt;/strong&gt; an &lt;a href="../../configuration/realm/#roles"&gt;admin role&lt;/a&gt; (&lt;code&gt;admin: true&lt;/code&gt;) held by the right people, the first created with &lt;a href="../../auth/cli/#the-first-administrator"&gt;&lt;code&gt;backd bootstrap&lt;/code&gt;&lt;/a&gt;. Startup warns when nobody holds an admin role.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Administrators hold only what they use:&lt;/strong&gt; besides one or two people with &lt;code&gt;admin: true&lt;/code&gt;, roles for the rest list their &lt;a href="../../auth/admin/#admin-rights"&gt;areas&lt;/a&gt; (&lt;code&gt;admin: [users, invitations]&lt;/code&gt;, &lt;code&gt;admin: [audit]&lt;/code&gt;), so the person who invites teammates doesn&amp;rsquo;t hold the secrets and the API keys.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;If you run &lt;a href="../../functions/"&gt;functions&lt;/a&gt;:&lt;/strong&gt; the executor and &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s internal listener (&lt;code&gt;BACKD_INTERNAL_ADDR&lt;/code&gt;) are reachable only from each other, never published; &lt;code&gt;BACKD_EXECUTOR_TOKEN&lt;/code&gt;, &lt;code&gt;BACKD_CALLBACK_KEY&lt;/code&gt;, &lt;code&gt;BACKD_EGRESS_KEY&lt;/code&gt; and, if any function declares secrets, &lt;code&gt;BACKD_SECRETS_KEY&lt;/code&gt; are random, 32+ characters, kept as secrets (never in &lt;code&gt;CONFIG_DIR&lt;/code&gt;) — the &lt;a href="../production/#functions-executor-egress-and-worker"&gt;production reference&lt;/a&gt;&amp;rsquo;s &lt;code&gt;setup.sh&lt;/code&gt; generates all four; &lt;code&gt;admin: true&lt;/code&gt; functions and &lt;code&gt;network&lt;/code&gt; lists are reviewed.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The executor can reach only &lt;code&gt;backd egress&lt;/code&gt; and &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s internal listener — nothing else, including MongoDB, is on its network.&lt;/strong&gt; This is &lt;a href="../../functions/network/#egress-the-network-allowlist-completed"&gt;layer 3 of the egress design&lt;/a&gt;: without it, a function can reach anything its container&amp;rsquo;s network reaches over raw TCP, bypassing both the per-function allowlist and the egress proxy. The &lt;a href="../production/#functions-executor-egress-and-worker"&gt;production reference&lt;/a&gt; wires this up (its own test proves it from a real function call); if you assemble your own deployment instead, &lt;code&gt;docker/egress-test/&lt;/code&gt; in the repository proves the isolation and is the reference to reproduce.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every function callable anonymously has a &lt;a href="../../functions/calling/#rate-limits"&gt;&lt;code&gt;rate_limit&lt;/code&gt;&lt;/a&gt;.&lt;/strong&gt; Startup warns, once, for any that don&amp;rsquo;t — a public function with no per-caller limit can be abused as a relay (e.g. one that sends email).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every &lt;a href="../../functions/webhooks/"&gt;&lt;code&gt;webhook&lt;/code&gt;&lt;/a&gt; function verifies its sender&amp;rsquo;s signature itself&lt;/strong&gt;, before trusting the body. &lt;code&gt;backd&lt;/code&gt; only enforces that its &lt;code&gt;invoke&lt;/code&gt; rule allows anonymous callers (it has to — a webhook sender has no session or key); it can&amp;rsquo;t tell a genuine request from a forged one.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;BACKD_SECRETS_KEY&lt;/code&gt; (the master key) is backed up separately from database backups&lt;/strong&gt;, never alongside them: losing it loses every stored function secret, and storing it with a backup defeats that backup&amp;rsquo;s own encryption. See &lt;a href="../backup/#what-a-backup-must-include"&gt;Backup and restore&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;functions.log_retention&lt;/code&gt; and &lt;code&gt;functions.job_retention&lt;/code&gt;&lt;/strong&gt; (&lt;a href="../../functions/logs/"&gt;function logs&lt;/a&gt;, &lt;a href="../../functions/jobs/"&gt;async jobs&lt;/a&gt;) match your privacy policy: both keep a function&amp;rsquo;s own console output, and a job additionally keeps its input and output, in the realm&amp;rsquo;s system database until they expire.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Someone reviews the &lt;a href="../../auth/audit/"&gt;audit trail&lt;/a&gt;&lt;/strong&gt; (&lt;code&gt;backd audit --realm &amp;lt;realm&amp;gt; --since 7d&lt;/code&gt;, or the &lt;code&gt;&amp;quot;msg&amp;quot;:&amp;quot;audit&amp;quot;&lt;/code&gt; log lines in your log pipeline), and &lt;code&gt;audit.retention&lt;/code&gt; matches your privacy policy.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Operators&amp;rsquo; machines protect the CLI&amp;rsquo;s sessions&lt;/strong&gt; (&lt;code&gt;~/.config/backd/credentials&lt;/code&gt;): log out with &lt;code&gt;backd logout&lt;/code&gt; on shared machines, and prefer short session lifetimes for realms with administrators.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="credentials"&gt;Credentials&lt;a class="hd-anchor" href="#credentials" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Every API key has an expiry&lt;/strong&gt; (&lt;code&gt;backd apikey create … --expires 90d&lt;/code&gt;), is used by one service, lives in a secret store, and is rotated before it expires. See the &lt;a href="../../auth/api-keys/#expiry-and-rotation-policy"&gt;expiry and rotation policy&lt;/a&gt;. Startup warns about keys without an expiry.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;API keys never reach browsers or mobile apps.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Internet-facing instances serve no admin API&lt;/strong&gt; when an internal instance does: &lt;code&gt;BACKD_ADMIN_API=false&lt;/code&gt; on the public ones (the &lt;a href="../production/#admin-api"&gt;production reference&lt;/a&gt; runs the split), and the CLI&amp;rsquo;s &lt;code&gt;BACKD_URL&lt;/code&gt; points at the internal instance.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The &lt;a href="../../auth/admin-ui/"&gt;admin UI&lt;/a&gt;, if used, is served only by the internal instance&lt;/strong&gt; (&lt;code&gt;BACKD_ADMIN_UI=true&lt;/code&gt; there, nothing on the public one), on its own origin, over TLS, with the proxy passing its &lt;code&gt;Content-Security-Policy&lt;/code&gt; and other headers through unchanged. &lt;code&gt;BACKD_ADMIN_UI_IDLE&lt;/code&gt; is short and the realm&amp;rsquo;s &lt;code&gt;sessions.admin_idle_timeout&lt;/code&gt; and &lt;code&gt;admin_max_lifetime&lt;/code&gt; are shorter still: the page&amp;rsquo;s timer is a convenience, the server&amp;rsquo;s is the limit. See its &lt;a href="../../auth/admin-ui/#security-review"&gt;security review&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Developers who only look hold &lt;code&gt;admin: read&lt;/code&gt;,&lt;/strong&gt; not &lt;code&gt;admin: true&lt;/code&gt;, and &lt;code&gt;admin.read_access.users&lt;/code&gt; / &lt;code&gt;.data&lt;/code&gt; stay &lt;code&gt;false&lt;/code&gt; unless they need personal data. See &lt;a href="../../auth/admin/#the-read-only-level"&gt;The read-only level&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Admin sessions are short:&lt;/strong&gt; &lt;code&gt;sessions.admin_idle_timeout&lt;/code&gt; and &lt;code&gt;sessions.admin_max_lifetime&lt;/code&gt; in each realm&amp;rsquo;s &lt;code&gt;realm.yaml&lt;/code&gt; (for example &lt;code&gt;1h&lt;/code&gt; and &lt;code&gt;12h&lt;/code&gt;), so a stolen administrator&amp;rsquo;s session is worth little. See &lt;a href="../../auth/sessions/#sessions-and-tokens"&gt;Sessions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Keys are scoped to what their service does&lt;/strong&gt; (&lt;code&gt;--scopes read:main/posts,call:main/contact&lt;/code&gt;): a leaked key then exposes that and nothing more. See &lt;a href="../../auth/api-keys/#scopes"&gt;Scopes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Services use &lt;code&gt;data&lt;/code&gt; keys&lt;/strong&gt;; &lt;code&gt;admin&lt;/code&gt; keys (&lt;code&gt;--role admin&lt;/code&gt;) exist only for management tooling.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Secrets&lt;/strong&gt; (MongoDB passwords, TLS keys, the CA key, &lt;code&gt;.env&lt;/code&gt;) are outside version control and readable only by the services that need them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-edge"&gt;The edge&lt;a class="hd-anchor" href="#the-edge" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;TLS everywhere:&lt;/strong&gt; the edge terminates TLS with a trusted certificate that renews automatically, and plain HTTP only redirects. MongoDB connections use TLS too (below).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Rate limits:&lt;/strong&gt; sign-up, login and the account flows (emailed links, password and address changes) have a strict per-address limit, and the rest of &lt;code&gt;/v1/&lt;/code&gt; a general one. Requests over a limit get &lt;code&gt;429&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Body size:&lt;/strong&gt; the edge caps request bodies at &lt;code&gt;MAX_BODY_BYTES&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Client addresses:&lt;/strong&gt; the edge overwrites &lt;code&gt;X-Forwarded-For&lt;/code&gt;, and &lt;code&gt;TRUSTED_PROXIES&lt;/code&gt; names exactly the edge&amp;rsquo;s addresses, never whole private ranges. &lt;code&gt;backd&lt;/code&gt;&amp;rsquo;s access log shows real client addresses.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Only the edge is reachable:&lt;/strong&gt; &lt;code&gt;backd&lt;/code&gt; isn&amp;rsquo;t published directly, and &lt;code&gt;/healthz&lt;/code&gt; and &lt;code&gt;/readyz&lt;/code&gt; aren&amp;rsquo;t public.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The admin API&lt;/strong&gt; is only reachable from your operators&amp;rsquo; network: &lt;code&gt;admin.allowed_networks&lt;/code&gt; in each &lt;code&gt;realm.yaml&lt;/code&gt; (and &lt;code&gt;admin_networks&lt;/code&gt; per admin where useful), plus &lt;code&gt;ADMIN_ALLOW_FROM&lt;/code&gt; at the edge in the reference.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The &lt;a href="../metrics/"&gt;metrics&lt;/a&gt; port is private&lt;/strong&gt; (&lt;code&gt;METRICS_ADDR&lt;/code&gt; is never published or proxied, and &lt;code&gt;METRICS_TOKEN&lt;/code&gt; is set if anything but Prometheus can reach it).&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;API keys of services with fixed addresses are pinned&lt;/strong&gt; to them with &lt;code&gt;--networks&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mongodb"&gt;MongoDB&lt;a class="hd-anchor" href="#mongodb" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;MongoDB requires TLS and authentication&lt;/strong&gt;, and isn&amp;rsquo;t reachable from the internet.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;backd&lt;/code&gt; runs with &lt;code&gt;PROVISION_MODE=verify&lt;/code&gt;&lt;/strong&gt; as a user limited to the configured collections; admin credentials are only available to the provisioning step. See &lt;a href="../production/#least-privilege-provisioning"&gt;least-privilege provisioning&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Encrypted backups&lt;/strong&gt; of every data and &lt;code&gt;&amp;lt;realm&amp;gt;___system&lt;/code&gt; database run on a schedule, are copied off the server, and a restore has been practised. The decryption key isn&amp;rsquo;t on the server. See &lt;a href="../backup/"&gt;Backup and restore&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="operations"&gt;Operations&lt;a class="hd-anchor" href="#operations" aria-hidden="true"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;LOG_LEVEL&lt;/code&gt; is &lt;code&gt;info&lt;/code&gt;&lt;/strong&gt; (the default). &lt;code&gt;debug&lt;/code&gt; logs every access denial with its reason, which is useful while writing rules but noisy and revealing in production.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Logs are collected and kept&lt;/strong&gt;, and startup &lt;code&gt;WARN&lt;/code&gt; lines are reviewed: realms with &lt;code&gt;auth: disabled&lt;/code&gt;, realms without rules, API keys without an expiry or about to expire, anonymous-callable functions with no &lt;code&gt;rate_limit&lt;/code&gt;, and (if you run functions) an executor that isn&amp;rsquo;t configured or doesn&amp;rsquo;t answer.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;The container runs hardened:&lt;/strong&gt; read-only filesystem, no Linux capabilities, CPU and memory limits (they also bound &lt;a href="../../auth/users/#password-storage"&gt;password hashing&lt;/a&gt;), and a stop grace period longer than &lt;code&gt;SHUTDOWN_TIMEOUT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;You know how to report and receive security fixes:&lt;/strong&gt; see &lt;a href="https://github.com/fernandezvara/backd/blob/main/SECURITY.md"&gt;&lt;code&gt;SECURITY.md&lt;/code&gt;&lt;/a&gt;. Only the latest release gets them.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>