Home / Security

Security

Secure by default, and clear about what is built

Security in Keel is a set of defaults an operator does not have to remember: no shared secrets in an image, a filter in front of every web application, and no port open that nobody declared. Each part below says whether it is in the code or decided and on the roadmap.

Keys and identity: none in the image

An image that carries a private key hands that key to everyone who downloads it. Keel removes them in three places, so that none depends on another being remembered. In the current testing images

  • The build removes them. SSH host keys, the TLS key and certificate, Webmin's key and the snakeoil pair are removed before a layer is exported, and every layer is exported with an empty machine-id.
  • The export refuses them. The layer export audits the tree and refuses one that still holds a private key.
  • A scan checks every layer and image. It verifies that none carries SSH host keys, TLS private keys, a Webmin certificate or snakeoil keys, on every build.
  • Every boot replaces them. A unit that runs before SSH, Webmin, the web servers and PostgreSQL generates what is missing and replaces any key on the list of published ones.
Fixed

The test images and layers published before the fix carried keys made at build time. They were withdrawn on 2 October 2026 and replaced by signed rebuilds that pass the scan; the notice on the mirror says how to give a machine made from an old one its own keys. The ISO build now leaves the machine-id empty too (tracker#24).

The request pipeline

Keel Web is an appliance of its own and the base of every web appliance: Nginx, the Coraza WAF and Anubis. In an advanced installation a request passes them in a fixed order. Decided, 0029 and 0030; Phases 1 and 3

  1. InternetA request arrives, over IPv6 first.
  2. CrowdSecBans known bad addresses first, with its firewall bouncer.
  3. Nginx + WAFTerminates TLS, serves static files and runs Coraza inline with the OWASP Core Rule Set.
  4. AnubisProof of work, behind Nginx and never at the edge. Googlebot and Bingbot are exempt.
  5. ApplicationOnly what passed the gates reaches it.
banned by CrowdSec refused by the WAF challenged by Anubis exempt crawler An illustration, not a measurement.

Why this order

  • CrowdSec bans first, so an address already known to be hostile costs nothing further. It ships in Keel Core, installed in every mode and disabled in a simple installation.
  • The WAF runs inside Nginx. Its health check is not that it loaded but that it blocks: a Core Rule Set test payload must get 403. If Coraza's Nginx connector proves unusable, the known fallbacks are ModSecurity v3 with the same Rule Set, or Coraza as a proxy; choosing one would be a new decision.
  • Anubis sits behind Nginx. It listens on the loopback only, and one signing key is shared by every node of a set, so a failover does not make every visitor solve the challenge again.

What each installation mode enables

The image is the same in every mode; the mode decides what runs. These are the default states of the version 1 manifests of Keel Core and Keel Web. Decided, 0041

OverlayApplianceSimpleCloud simpleCloud advanced
installerCoreenabledenabledenabled
wireguardCoredisabledenabledenabled
etcdCoredisableddisabledenabled
crowdsecCoredisabledenabledenabled
nginxWebenabledenabledenabled
corazaWebdisabledenabledenabled
anubisWebdisabledenabledenabled

An operator can depart from a default on one machine, in the spec. A disabled overlay is still installed, so turning it on is a change to the spec, not a new image.

In the current testing image, Keel Web in simple mode serves HTTPS out of the box with the machine's own certificate, a Keel placeholder page and Keel error pages. Let's Encrypt is set up from the console, prefilled from the instance spec. In testing

Every port has an exposure class

Every listening port in a manifest carries one of three classes, and the firewall is derived from them rather than written by hand. A port is reserved across the whole chain of appliances, whatever its state, since an operator can turn a disabled overlay on. Decided, 0041

loopback

Loopback

Only the machine itself: postfix on 25, Anubis behind Nginx.

mesh

Mesh

Only other nodes, over WireGuard: etcd on 2379 and 2380.

public

Public

Anyone: SSH on 22, the web shell on 12320, Webmin on 12321, Nginx on 80 and 443.

# /usr/share/keel/overlays/nginx.yaml, from keel-overlay-nginx
manifest_version: 1
kind: overlay
name: nginx
processes:
  - name: nginx
    unit: nginx.service
    listen:
      - {port: 80, protocol: tcp, expose: public}
      - {port: 443, protocol: tcp, expose: public}
checks:
  - {name: nginx, process: nginx, type: http, address: loopback, port: 80,
     path: /keel-health, expect: 204, on_failure: restart}

Network changes that undo themselves

A machine reached only over SSH is one bad network change away from being unreachable. In keel

  • On a VM, a network change made by keel spec apply --system reverts by itself unless keel network confirm arrives from a new session over the new configuration. In an LXC container the host owns the network; Keel compares it and does not change it.
  • The WireGuard overlay is converged under the same confirmation window. The one exception, decided in 0029, is a move of a database appliance's service address on the mesh, which changes neither the node's uplink nor its own overlay address.
  • The network is changed in the console or at the command line, never through Webmin, which shows it read-only.

Secrets are references, never values

  • The spec names a secret by the file that holds it, and keel spec validate checks that the file exists with the right owner and mode.
  • keel inspect and keel diff never read a secret, on either side, and a private WireGuard key is never printed.
  • A manifest declares each secret by name with a policy: whether it may, must or must never be generated, and whether every node of a set shares it. The primary generates what the replica needs. Decided, 0028 and 0041

Where packages come from

Every image rebuilds from the project's own archive: a dated, pinned pool, verified by the build before it is read. What Debian 13 does not carry, Keel builds from source and signs in the same repository: Coraza's Nginx connector, Anubis and Garage. Anubis and Garage have packaging in progress in Debian, and Keel coordinates with those efforts rather than packaging in parallel. Decided, 0039

The testing images are built from Debian and the Keel archive only, with no TurnKey archive. The TurnKey tools Keel still uses, turnkey-ssl, netinfo, conffile, sysinfo, version, tkl-installer and the dhcpcd glue, are Keel forks published there, and turnkey-ssl no longer ships a key. In testing

The archive, archive.keellinux.org, has two tracks: trixie-testing, where everything lands first, and trixie, the stable track. Promotion to stable is a maintainer's act. Images and the archive are signed, and the keyring is at archive.keellinux.org/keel-archive-keyring.asc.