| 1 |
/* Alloy: the installer, reached over ssh, becoming root. |
| 2 |
* |
| 3 |
* `alloy install` does not escalate. On tty1 it does not need to — |
| 4 |
* alloy-installer.service runs it as root — and over ssh it lands in the |
| 5 |
* unprivileged `installer` account, where every privileged stage fails: |
| 6 |
* |
| 7 |
* error: Installing to disk: Querying root privilege: This command must be |
| 8 |
* executed as the root user |
| 9 |
* wipefs: error: /dev/vda: probing initialization failed: Permission denied |
| 10 |
* |
| 11 |
* Measured 2026-08-26 (GO alloy 2cf04f20). The wizard drew and answered all six |
| 12 |
* steps and the erase confirmation before dying, so the headless flow the whole |
| 13 |
* minting design rests on — boot the ISO, `ssh installer@<name>.local`, answer |
| 14 |
* the questions — reached a wizard that could not write a disk. |
| 15 |
* |
| 16 |
* So etc/ssh/sshd_config.d/20-alloy-installer.conf runs the wizard under |
| 17 |
* `run0`, and run0 asks polkit for org.freedesktop.systemd1.manage-units. |
| 18 |
* Without a rule it asks a human, and there is no human on the far end of a |
| 19 |
* headless install. This is that rule. |
| 20 |
* |
| 21 |
* WHY THIS GRANTS OVER SSH WHEN ITS SIBLING REFUSES TO. 50-alloy-settings.rules |
| 22 |
* requires `active && local` and says so at length: its justification is "the |
| 23 |
* person sitting at the machine", which an ssh login is not. That reasoning is |
| 24 |
* untouched here because this grant rests on something else entirely — not on |
| 25 |
* where the caller is, but on the fact that the account cannot exist anywhere a |
| 26 |
* grant would matter. |
| 27 |
* |
| 28 |
* THE GATE IS STILL THE ACCOUNT. `installer` is created by |
| 29 |
* alloy-installer-ssh.service, which carries |
| 30 |
* ConditionKernelCommandLine=alloy.installer, which only the ISO's GRUB entries |
| 31 |
* set. On an installed machine there is no such user, so the rule below matches |
| 32 |
* nobody and this file is inert — exactly as the sshd drop-in beside it is |
| 33 |
* inert for the same reason. A polkit rule cannot read the kernel command line, |
| 34 |
* so it cannot check that itself; the Containerfile asserts the three files |
| 35 |
* agree instead. |
| 36 |
* |
| 37 |
* WHAT THIS IS WORTH TO SOMEONE WHO HAS THE KEY, stated plainly rather than |
| 38 |
* elided: manage-units is a password-less path to root, so anyone who can open |
| 39 |
* this session can run anything as root on that medium. They could already |
| 40 |
* partition its disks and write an OS to them, which is what the session is |
| 41 |
* for. The exposure is a live installer medium on a LAN, and the credential is |
| 42 |
* a key baked in at mint time. Widening it further would be a different |
| 43 |
* decision; this does not. |
| 44 |
* |
| 45 |
* Ruled (a) by Max on 2026-08-26, against giving the account uid 0 and against |
| 46 |
* a NOPASSWD sudoers line. Both would have worked. This one keeps the account |
| 47 |
* unprivileged in its own right and puts the grant in a file somebody can read. |
| 48 |
* |
| 49 |
* Ordering: polkit reads /etc/polkit-1/rules.d and /usr/share/polkit-1/rules.d |
| 50 |
* as one lexically sorted set, and the first rule to return a value wins. 50- |
| 51 |
* so a user drop-in in /etc with a lower number overrides it outright. |
| 52 |
*/ |
| 53 |
|
| 54 |
polkit.addRule(function (action, subject) { |
| 55 |
/* One action and one user. run0 asks for manage-units and nothing else, and |
| 56 |
* the only thing that can use the grant is the ForceCommand that needs it: |
| 57 |
* sshd replaces whatever the client asked to run, so there is no command |
| 58 |
* line to get wrong and no shell to fall back to. |
| 59 |
* |
| 60 |
* Returning nothing rather than NOT for everything else, so this file |
| 61 |
* grants and never denies — same rule as its sibling, and for the same |
| 62 |
* reason: a NOT here would override a later rule with a better reason to |
| 63 |
* allow something. |
| 64 |
*/ |
| 65 |
if (action.id !== "org.freedesktop.systemd1.manage-units") { |
| 66 |
return; |
| 67 |
} |
| 68 |
if (subject.user === "installer") { |
| 69 |
return polkit.Result.YES; |
| 70 |
} |
| 71 |
}); |
| 72 |
|