The defense is easiest to understand from the attacker's side. Each position below asks the same things: what the attacker must achieve to win, what blocks them, what detects them, and what stays open. The positions follow one another: an internet scanner who finds an open service becomes a workload-level attacker, and a workload that escapes becomes a node-level attacker. Each section assumes the attacker already holds the position, however they reached it, and asks what it is worth. The positions map onto the [platform architecture](/docs/hetzner/apalla/concepts/foundations/architecture), the pieces that make up a cluster. [Security architecture](/docs/hetzner/apalla/security/security-architecture) summarizes this in a table; this page works through each position in detail. > [!NOTE] > Every control named here is on by default unless the text says otherwise. The gaps are named too: a limit you know about is one you can plan around. ## Internet scanner The attacker probes the node's public IP from anywhere. Every node has one; the platform runs directly on public networks with no perimeter in front. **What they must do to win.** Find a listening service on the node and exploit it, or reach the API server and find a credential that works. **What blocks them.** The host firewall runs default-deny on the public interface, enforced in eBPF (which filters packets before they reach a socket). A port is not open or closed by number; each allow rule names its consumer. The kubelet port (`10250`) answers only to other nodes, the local host, and the metrics-server pod; the overlay (`8472`), WireGuard (`51871`), and health ports (`4240`, `4244`) answer only to cluster identities; `6443` answers only to the control-plane load balancer, so the API server is unreachable from here even though the node has a public IP. The management tunnel is dialed outward over mutual TLS, so there is no inbound listener to find. Everything else is dropped. **What detects them.** Dropped probes are not individually alarmed; default-deny makes them noise. Connections that reach the API server's front gate and fail are recorded: the [KubeGate audit log](/docs/hetzner/apalla/security/retrieve-audit-logs) logs each rejected non-TLS or unknown-source connection. **Residual.** SSH on port 100 answers from anywhere until you add a deny policy (authentication is key-only, but the port is reachable); see [Restrict SSH access](/docs/hetzner/apalla/security/restrict-ssh-access). Any service you expose yourself is your attack surface and sits outside this section. ## Compromised workload The attacker runs code inside a non-privileged container, the most likely position they reach. **What they must do to win.** Escape to the host, load kernel code, reach node or control-plane services the pod has no business reaching, or abuse the admission chain to lock administrators out. **What blocks them.** - **Escape:** every non-privileged container runs an enforcing AppArmor profile and the `RuntimeDefault` seccomp filter, cutting the syscall and filesystem surface an exploit needs. - **Kernel code:** `MODULE_SIG_FORCE` means an unsigned kernel module does not load, whatever privileges the attacker gathers. - **Lateral movement to the host:** the host firewall applies identity to pods too. Only named platform pod identities reach a host port (metrics-server to `10250`, hubble-relay to `4244`); no other pod identity is allowed anywhere. The component metrics ports are closed twice: absent from every allow-list, and bound to `127.0.0.1` (etcd `2381`, kube-controller-manager `10257`, kube-scheduler `10259`, KubeGate `8080`, cilium-agent `9962`, cilium-operator `9963`, Hubble `9965`, CSI node `9189`), which a pod's own network namespace can never reach. - **Admission lockout:** a built-in CEL policy, `syself-restrict-tenant-webhooks`, rejects any tenant webhook that targets the admission-registration, authentication, or authorization APIs, cluster-scoped RBAC, or resource wildcards; `syself-protect-platform-policies` keeps tenants from editing the guard itself. `system:masters` can always delete a broken webhook. See [Control admission](/docs/hetzner/apalla/security/control-admission). **What detects them.** The Kubernetes API audit log records every request the workload's service account makes; auditd records command execution in login sessions and changes to identity and control files; Hubble flow export, if enabled, records network-policy verdicts. **Residual.** Pods are default-allow and no Pod Security level is set on a fresh cluster, so a compromised pod reaches every other pod until you add [NetworkPolicies](/docs/hetzner/apalla/security/segment-with-network-policies) and [Pod Security Standards](/docs/hetzner/apalla/security/enforce-pod-security-standards). A privileged container is outside this section entirely: granting `privileged: true` hands over the node. Run hostile code under the [`secure` RuntimeClass](/docs/hetzner/apalla/security/workload-runtimes-and-isolation), which puts the pod in its own VM. ## Node-level attacker The attacker has root on a running node. The question is no longer whether the node is compromised but what the compromise is worth and whether it is visible. **What they must do to win.** Modify the OS, persist unseen, or turn one node into the whole cluster. **What blocks them.** The OS is sealed. The base, the add-on layers, and the per-node config are each dm-verity images, so root cannot edit a sealed layer in place, cannot install software, and cannot load unsigned modules. The model is keyless: authenticity comes from content-hash pinning, so the node stores no signing key an attacker could steal. The provisioner records each layer's root hash in `/var/lib/syself/sealed-layers.json`, and the control plane keeps its own record. **What detects them.** The health daemon compares the kernel's dm-verity state against the pinned hashes and latches `SealedOSTampered` on a mismatch; a change to protected files under `/var` latches `NodeTampered`. Latching means reverting the change does not clear the condition, and both are published to the `Node` object in the workload cluster's API server, so they survive a disk wipe of the worker node. auditd's ruleset is immutable and locked at boot; root can stop the daemon, but that stop is itself an audit event. Ship the logs off-node so the record outlives the node. See [Verify node integrity](/docs/hetzner/apalla/security/verify-node-integrity). **Residual.** `/var` stays writable because Kubernetes needs it, so it is tamper-evident, not sealed; the baseline is taken when the daemon starts, so a change made while it is down is adopted on restart. Root on a node also reads the secrets of pods scheduled there and the node's own credentials. This is inherent to Kubernetes; detection limits how long it goes unseen, but does not stop the read. Recovery is a reprovision, never a repair in place. ## Disk or boot-line attacker The attacker can write to the disk outside the running OS: physical access, a hijacked provisioning flow, or firmware compromise. **What they must do to win.** Rewrite the kernel command line on the EFI partition and point it at a replacement kernel or root hash, booting an OS they control. **What blocks them.** Nothing on the node; this is the platform's stated trust boundary. Below the boot line the protection is complete: the base's dm-verity root hash sits on the kernel command line, and the kernel verifies every block against it from the first read, so every in-place edit to a sealed layer fails. The attacker cannot modify the OS you released; they can only boot a different one. **What detects them.** External comparison, not the node itself. The base image builds reproducibly (fixed salt, filesystem UUIDs, timestamps), so anyone can recompute its root hash from the released image with `veritysetup` and compare it against the node's `/proc/cmdline`. The control plane knows which hashes it handed each node, so a node presenting different pins is a mismatch. See [Verify node integrity](/docs/hetzner/apalla/security/verify-node-integrity). **Residual.** There is no Secure Boot and no TPM-backed measured boot, and neither is planned: a boot-line rewrite is detectable by comparison but not blocked. Physical access to the hardware is Hetzner's layer, covered by their datacenter certifications. ## Supply-chain attacker The attacker compromises something upstream: a source tarball, the image in transit, or a component with a known vulnerability. **What they must do to win.** Get modified or vulnerable code into the image a node boots, or swap the image in transit. **What blocks them.** - **Source:** every component is pinned by sha256 digest. The kernel pin verifies the release tag's PGP signature against the kernel.org maintainer keys at pin time, then locks the sha256, so a compromised mirror fails the hash check. - **Delivery:** the provisioner pins each layer's dm-verity root hash from the image manifest before the node boots it, and the kernel verifies every block at runtime. A swapped image does not match its pin and does not run. - **Known vulnerabilities:** critical CVEs in the image are patched within 72 hours. **What detects them.** Every release ships an SBOM and OpenVEX advisories; the OS is closed source, so the SBOM is the reviewer's evidence to audit, diff, and feed to a scanner. On the node, a delivery-path mismatch surfaces as `SealedOSTampered`. **Residual.** The keyless model has an upside and a downside. The upside is that no signing key exists to steal. The downside is that a compromised build pipeline would pin the compromised bytes, and no signature check would flag them, because there is none. The external checks are the reproducible base build and the per-release SBOM. Verifying the images your own workloads pull is your side of the line; see [Verify image signatures](/docs/hetzner/apalla/security/verify-image-signatures). ## The boot line and the shared-responsibility line The model organizes around the boot line and the shared-responsibility line. The **boot line** on the EFI partition: below it, kernel dm-verity makes the OS unmodifiable and content-hash pinning makes it verifiable; at and above it, the platform offers detection by external comparison, not prevention. The **shared-responsibility line** inside the cluster: the platform locks down the host and control plane by default, while pod-to-pod traffic, Pod Security levels, and image verification start open and are yours to close. An attacker looks for the gaps, so here they are once more: SSH reachable until you restrict it, default-allow pods until you segment them, a `/var` baseline that resets with the health daemon, and a boot line that nothing on the node defends. Each has a page on how to close it or watch it.