Skip to main content

Security architecture

Who's actually behind an action once they're past the network gate SSH sits at, and what they're allowed to do.

Credentials​

Five separate places identity actually lives here, not one — and they don't nest cleanly: a control at one layer doesn't contain a compromise at another.

Infrastructure credentials​

Three separate credentials, not one — and they don't share the same strength:

  • Infomaniak account (<prefix>-infomaniak-user-admin) — the master login, with a second factor, and the actual root of trust behind both: it can reset Horizon's own password directly, not just reach it.
  • OpenStack Horizon (<prefix>-openstack-user) — a separate credential day to day, but not truly independent, since the Infomaniak account can reset it at will. TOTP now covers the gap a bare password left open — see Provider Setup for the procedure.
  • OpenTofu's own credential (<prefix>-openstack-app-credential) — held in Proton Pass, a scoped application credential for API-level provisioning only, not a human login at all.

None of this is something this server's own configuration touches — it's the provider account's own security.

Object storage credentials​

<prefix>-openstack-s3-access-key — shared by OpenTofu's state backend and Coolify's backup destination, with no way to scope it to just one bucket — Infomaniak's Object Storage ACLs are additive-only, no per-bucket restriction exists. Accepted tradeoff, not an oversight.

SSH credentials​

Three kinds of key worth distinguishing — what each is actually authorized to do turns out to vary more than the key material itself suggests. See Securing the system → SSH Hardening for the protocol-level settings (AllowUsers, PermitRootLogin) that govern which of these can actually connect at all.

  • Infrastructure key (<prefix>-ssh-infrastructure-key) — used to set up a server, meant to go unused afterward. Full access as debian (sudo needed for root); also authorized against root, but a forced-command restriction blocks a shell there. Private key lives only in Proton Pass, never on a server. The only one of the keys with reach beyond a specific box.
  • Server keys (<prefix>-ssh-server-<name>-key) — authorized directly against root, unrestricted, used exclusively by Coolify's own automated server-management connections. The only kind of key here that actually grants a real root shell today. A different key per server is a deliberate choice worth making, not something Coolify does automatically.
    • <prefix>-ssh-server-main-key — this server, currently the only one.
  • Integrations keys (<prefix>-ssh-integration-<service>-key) — Coolify-managed, one per service integration rather than reusing a single key across all of them. Reach is limited to whatever's inside a container, nothing beyond — not authorized against this server directly, and less risky as a result.
    • <prefix>-ssh-integration-forgejo-key — pulls from Forgejo over SSH.

authorized_keys files accumulate entries over time — migrations, app deployments — without anything auditing what's actually there or why. That's exactly how the Forgejo key ended up sitting in root's file unnoticed, doing far more than it was created for — and, just as easily, how a key everyone assumes is fully privileged (the infrastructure key, against root) turns out to be quietly boxed in instead.

Todo — move off static keys. Coolify's Teams boundary is UI policy, not cryptographic: a key shared across multiple servers grants that same reach to anyone who extracts it, regardless of which team they belong to in the dashboard. An SSH certificate authority — OpenBao's SSH secrets engine, for instance — would replace static keypairs with short-lived certificates issued per connection, closing that gap for every server except whichever one hosts the CA itself, which still needs its own durable, non-delegated way in — the same root-of-trust problem the Infomaniak account already is for everything above.

Root password​

Not SSH, not provider access — local console recovery only, if the box ever drops into emergency mode (a failed mount, for instance). PermitRootLogin and PasswordAuthentication stay whatever they're configured as regardless; this doesn't touch network access at all.

  • Root password (<prefix>-server-<name>-root-password) — set once via sudo passwd root, saved in Proton Pass immediately rather than left only in memory. Without it, emergency mode is a genuine dead end: sulogin has no password to check against, and the only way back in is a bootloader-level workaround (interrupting GRUB, booting with init=/bin/bash).

Maintenance service credentials​

Four services, four separate credential stores running on top of the OS entirely — not one identity system, and they don't share risk evenly:

  • Authentik (<prefix>-authentik-user-admin) — the identity provider itself, with its own local admin account. The actual root of trust behind every service that delegates login to it — a compromise here cascades to Forgejo (and anything else wired up to SSO later), though Coolify and Beszel's own local accounts wouldn't be touched by it.
  • Coolify — its own local admin account, created at first install, 2FA enabled. Reaches further than the others: admin access here can read every other service's environment variables, trigger any deployment, and reconfigure the server connection's own SSH settings — reaching into SSH credentials, not just sitting beside them. Someone with Coolify admin access could add a new SSH key for server management without ever touching /root/.ssh/authorized_keys by hand.
    • <prefix>-coolify-user-root-team — the admin login itself.
    • <prefix>-coolify-env — the whole /data/coolify/source/.env, generated at first install: APP_KEY (encrypts everything Coolify stores in its own database, every managed service's environment variables included — without it, a database backup is encrypted data with no way to decrypt it, not a usable restore), plus DB_PASSWORD, REDIS_PASSWORD, and Pusher keys. See Machine Prep for the backup step this comes from.
  • Forgejo and Beszel — both delegated to Authentik SSO rather than a local account each. Forgejo's password login can be disabled entirely once SSO is confirmed working, leaving Authentik as the sole gate. See Forgejo → Install → Authentik SSO.