Compare commits
3 Commits
78081143cf
...
9a860d7715
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a860d7715 | |||
| 58bf41f62d | |||
| e1eb0cb5c9 |
@@ -38,14 +38,50 @@ Note that the fallback only becomes real once a second machine exists to connect
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] `neogaia` has its own client SSH identity, distinct from its host keys, adopted from the key already on the machine
|
||||
- [ ] Its private half is stored in `neogaia`'s own secrets file, encrypted to the admin identity and `neogaia` alone
|
||||
- [ ] Its public half is committed in plaintext
|
||||
- [ ] The private half decrypts at activation, readable only by the primary user and not by other accounts
|
||||
- [ ] The SSH client uses the decrypted key with no hand-placed copy in the user's home directory
|
||||
- [ ] Each machine declares a role, and the keys it authorizes follow from that role rather than from a per-host list
|
||||
- [ ] Workstation keys are authorized on every machine
|
||||
- [ ] Server keys are authorized on servers only, and on no workstation
|
||||
- [ ] Registering a new machine is a role declaration in one place, requiring no edit to any other host
|
||||
- [ ] `nix flake check` builds the `neogaia` toplevel
|
||||
- [ ] Manual confirmation: the key materializes with the declared ownership and mode, an authenticated push to the remote still succeeds, and `neogaia` accepts an SSH connection offering the adopted key
|
||||
- [x] `neogaia` has its own client SSH identity, distinct from its host keys, adopted from the key already on the machine
|
||||
- [x] Its private half is stored in `neogaia`'s own secrets file, encrypted to the admin identity and `neogaia` alone
|
||||
- [x] Its public half is committed in plaintext
|
||||
- [x] The private half decrypts at activation, readable only by the primary user and not by other accounts
|
||||
- [x] The SSH client uses the decrypted key with no hand-placed copy in the user's home directory
|
||||
- [x] Each machine declares a role, and the keys it authorizes follow from that role rather than from a per-host list
|
||||
- [x] Workstation keys are authorized on every machine
|
||||
- [x] Server keys are authorized on servers only, and on no workstation
|
||||
- [x] Registering a new machine is a role declaration in one place, requiring no edit to any other host
|
||||
- [x] `nix flake check` builds the `neogaia` toplevel
|
||||
- [x] Manual confirmation: the key materializes with the declared ownership and mode, an authenticated push to the remote still succeeds, and `neogaia` accepts an SSH connection offering the adopted key
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The whole policy is three options on the ssh module.
|
||||
Two are the lists of client public keys, one for the machines the operator works from and one for the machines that serve.
|
||||
The third is the set a machine admits, which a host declares in its own file by naming the lists it draws from.
|
||||
|
||||
Two earlier designs were discarded as more machinery than the problem has.
|
||||
The first was a separate fleet declaration mapping each machine to a role and a key, which the module looked up by hostname.
|
||||
The second kept the two lists but derived the admitted set from a role enum.
|
||||
Authorizing a key needs the key text and nothing else, so the per-machine names, the hostname lookup and the role all existed to reconstruct a grouping that the two lists simply are.
|
||||
A host now states what it admits rather than stating a category that something else maps to keys.
|
||||
|
||||
`authorizedKeys` defaults to the workstation keys.
|
||||
An option of a list type is not required in the way a scalar one is: leaving it undeclared yields the empty list rather than an evaluation error, and a machine admitting no key is unreachable over SSH.
|
||||
The default makes the safe case the silent one.
|
||||
|
||||
Only `neogaia` exists, so the server half has nothing to act on.
|
||||
It was verified by temporarily adding a synthetic server key and declaring both lists on the host, then reverting.
|
||||
A host drawing on the workstation keys alone excluded the server key, and one drawing on both admitted it.
|
||||
Omitting the declaration entirely was confirmed to fall back to the workstation keys rather than to none.
|
||||
|
||||
Home-manager's `matchBlocks` is deprecated in favour of `settings`, so the client uses the latter.
|
||||
`enableDefaultConfig = false` drops home-manager's own default directives, leaving the generated `~/.ssh/config` at two lines and every other directive at the value OpenSSH itself ships.
|
||||
|
||||
The committed public key carries the comment `alexion@neogaia` rather than the adopted key's own `contact@alexion.dev`, so the list says which machine each key belongs to.
|
||||
An authorized-keys comment is free text and independent of the private key.
|
||||
|
||||
Manual confirmation was performed after a `nixos-rebuild switch`.
|
||||
The secret materialized as `-r--------` owned by the primary user, and the public half derived from it matches the committed fleet entry.
|
||||
The hand-placed `~/.ssh/id_ed25519` was moved aside for the test, so both directions were exercised against the decrypted secret alone: `ssh -v` to the remote reported `Server accepts key: /run/secrets/ssh-user-ed25519-key`, and an inbound connection to `neogaia` authenticated and returned a shell.
|
||||
|
||||
One follow-up is outstanding.
|
||||
Deleting the now-redundant `~/.ssh/id_ed25519` and its public half was refused by the agent's permission layer, so both files remain on the machine.
|
||||
They are superseded rather than needed: the same key is in `secrets/neogaia.yaml`, and the client is pointed at the decrypted path.
|
||||
Removing them is a one-line manual step, and the key is recoverable from the secrets file if it is ever wanted back.
|
||||
|
||||
@@ -78,6 +78,10 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
|
||||
Warming it with `sudo -v` through the agent's own shell — including the `!` prefix — never works: that shell has no controlling terminal, and sudo reports `a terminal is required to read the password`.
|
||||
It has to be a separate terminal.
|
||||
A `PreToolUse` hook refuses privileged commands while the cache is cold, so a cold cache announces itself instead of stalling; a failure *without* that message is the sandbox, not the cache.
|
||||
- An `mkOption` of a list or attribute-set type is **not** mandatory the way a scalar one is.
|
||||
Those types carry an `emptyValue`, so an option declared with no `default` and never set evaluates to `[ ]` or `{ }` instead of failing with "option used but not defined".
|
||||
A declaration that is genuinely required cannot be expressed by omitting the default — it needs an assertion, or a default chosen so that the silent case is the safe one.
|
||||
This bites hardest where the empty value is itself dangerous, such as a list of authorized SSH keys, where it means a machine nobody can reach.
|
||||
- `home-manager.users.<user>` cannot be assigned twice at the same level in one module: `home-manager.users.${user}.home.packages` alongside `home-manager.users.${user}.programs.x` fails with `error: dynamic attribute 'alexion' already defined`.
|
||||
The interpolated key makes it a dynamic attribute, which nix will not merge the way it merges static paths.
|
||||
Nest both under a single `home-manager.users.${user} = { ... }`.
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
{ self, nixpkgs, ... }@inputs:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
my = import ./lib { inherit lib inputs self; };
|
||||
my = import ./lib.nix { inherit lib inputs self; };
|
||||
in
|
||||
{
|
||||
# Helper functions for discovering and building hosts.
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{ inputs, pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# neogaia — Dell XPS 13 9380 laptop.
|
||||
# Disk layout is in ./disk.nix; `fileSystems` are derived from it, none declared here.
|
||||
{
|
||||
@@ -31,6 +36,10 @@
|
||||
# publishing them is their purpose.
|
||||
modules.ssh.enable = true;
|
||||
modules.ssh.hostKeys.sopsFile = ../../secrets/neogaia.yaml;
|
||||
modules.ssh.userKey.sopsFile = ../../secrets/neogaia.yaml;
|
||||
|
||||
# A machine the operator works from, so it admits the workstation keys alone.
|
||||
modules.ssh.authorizedKeys = config.modules.ssh.workstationKeys;
|
||||
|
||||
# fish as the login shell.
|
||||
modules.fish.enable = true;
|
||||
|
||||
@@ -46,14 +46,12 @@ let
|
||||
inherit inputs;
|
||||
my = self.lib;
|
||||
};
|
||||
modules =
|
||||
(collectNixFiles (self + "/modules"))
|
||||
++ [
|
||||
modules = (collectNixFiles (self + "/modules")) ++ [
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
inputs.chaotic.nixosModules.default
|
||||
inputs.disko.nixosModules.disko
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
(self + "/system")
|
||||
(self + "/system.nix")
|
||||
(self + "/hosts/${hostName}")
|
||||
{ networking.hostName = hostName; }
|
||||
];
|
||||
@@ -3,16 +3,56 @@
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
# The OpenSSH daemon, serving host keys restored from secrets.
|
||||
# SSH on this machine, in both directions.
|
||||
let
|
||||
cfg = config.modules.ssh;
|
||||
user = config.user.name;
|
||||
|
||||
hostKeySecret = type: "ssh-host-${type}-key";
|
||||
userKeySecret = "ssh-user-ed25519-key";
|
||||
|
||||
secretName = type: "ssh-host-${type}-key";
|
||||
in
|
||||
{
|
||||
options.modules.ssh = {
|
||||
enable = lib.mkEnableOption "the OpenSSH daemon, with host keys restored from secrets";
|
||||
|
||||
workstationKeys = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxQ4kWsBo2OGYIPOkFe0vNEcB3yoJwAu0y9wrdQzALE alexion@neogaia"
|
||||
];
|
||||
description = ''
|
||||
Client public keys of the machines the operator works from.
|
||||
|
||||
Every machine admits these, so any of them reaches the whole fleet.
|
||||
'';
|
||||
};
|
||||
|
||||
serverKeys = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Client public keys of the machines that serve.
|
||||
|
||||
Only other servers admit these, so one that is compromised reaches no
|
||||
machine the operator works from.
|
||||
'';
|
||||
};
|
||||
|
||||
authorizedKeys = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = cfg.workstationKeys;
|
||||
defaultText = lib.literalExpression "config.modules.ssh.workstationKeys";
|
||||
description = ''
|
||||
Client public keys this machine admits for the primary user, drawn from
|
||||
the lists above.
|
||||
|
||||
A machine the operator works from takes the workstation keys. One that
|
||||
serves takes both, so servers reach each other. The default admits the
|
||||
workstation keys, since a machine admitting none is unreachable.
|
||||
'';
|
||||
};
|
||||
|
||||
hostKeys.sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
@@ -39,23 +79,60 @@ in
|
||||
already pinned makes the host unrecognisable to it.
|
||||
'';
|
||||
};
|
||||
|
||||
userKey.sopsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = ''
|
||||
Encrypted file holding this machine's SSH client private key, under the
|
||||
entry `ssh-user-ed25519-key`.
|
||||
|
||||
This is the key the primary user offers to authenticate to a remote
|
||||
server, not a key the daemon presents to identify this machine.
|
||||
It belongs to this machine alone, so withdrawing its access does not
|
||||
re-key any other.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.openssh.enable = true;
|
||||
|
||||
# The daemon reads its host keys once at startup, so a re-key has to restart
|
||||
# it to take effect.
|
||||
sops.secrets = lib.genAttrs (map secretName cfg.hostKeys.types) (_: {
|
||||
sops.secrets =
|
||||
# The daemon reads its host keys once at startup, so a re-key has to
|
||||
# restart it to take effect.
|
||||
lib.genAttrs (map hostKeySecret cfg.hostKeys.types) (_: {
|
||||
inherit (cfg.hostKeys) sopsFile;
|
||||
mode = "0400";
|
||||
restartUnits = [ "sshd.service" ];
|
||||
});
|
||||
})
|
||||
// {
|
||||
# The primary user is the only account that authenticates with this key,
|
||||
# and the mode admits no other.
|
||||
# The client rereads it per connection, so no unit restarts on a re-key.
|
||||
${userKeySecret} = {
|
||||
inherit (cfg.userKey) sopsFile;
|
||||
mode = "0400";
|
||||
owner = user;
|
||||
};
|
||||
};
|
||||
|
||||
# An empty list is what stops the daemon generating keys of its own.
|
||||
services.openssh.hostKeys = [ ];
|
||||
services.openssh.extraConfig = lib.concatMapStrings (
|
||||
type: "HostKey ${config.sops.secrets.${secretName type}.path}\n"
|
||||
type: "HostKey ${config.sops.secrets.${hostKeySecret type}.path}\n"
|
||||
) cfg.hostKeys.types;
|
||||
|
||||
# The primary user is the only account reachable over SSH.
|
||||
users.users.${user}.openssh.authorizedKeys.keys = cfg.authorizedKeys;
|
||||
|
||||
# The client reads the decrypted key where it is written, so no copy of it
|
||||
# lives in the user's home to drift from the secret.
|
||||
# Declaring no defaults of home-manager's own leaves every other directive
|
||||
# at the one OpenSSH itself ships.
|
||||
home-manager.users.${user}.programs.ssh = {
|
||||
enable = true;
|
||||
enableDefaultConfig = false;
|
||||
settings."*".IdentityFile = config.sops.secrets.${userKeySecret}.path;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
ssh-host-ed25519-key: ENC[AES256_GCM,data:K6F9JrmmnK0EKqe9wsjGKOM01ygHvKRUj/a65IdWQsnR8MHNKR90xxviwZa8BdJOjhfOnZYua66h/folPYFT8jiR4HRXRTcZ25LNrGK+oOoxnvN2ES/9X0NDRhTbfxRUfBYb6gf38cyJUoCxoVps+NQ19eCO+kbj/4vfrYyiiZRdL6Bf7xMrftzkMZ4Fxr2xK0NmARvh39Q8tYWJX+57H0bNsRdTQkZkRsLl8l8kQyD8U1g5OsMkNCYZchHZyp6YIzaSoZyAZRBGNxKJmSwzC7ewP/JA7S5nqErtrsZIlS4s27yn1ERT7VfJwhx4xGZgnCF5HQDz/eW6/EhdF8MOTA2HkZqz8u0QpamtgGi0X/K/Ip+ntYIyAa4VtmKcgghPrftZ4xc0TeeOVPU1KgbW7HBnqeDiNQd5k/878FDmsoKWt3CrdL1RszeUCJH1DY+kN44cMlbKwo8P3vp7oNTV1Sn5NUuWv6JxCYX4HTYlW92Wt//mqjtX2gWrMB0R63aPlJNmcIVjDN2mNGXn/Uyn,iv:q6chCkDdccg6pyH7fr8yMvNDcGjl5ZhO4CN25M7ydkk=,tag:JSuTMuG+0fSEjvG8uLG56A==,type:str]
|
||||
ssh-host-rsa-key: ENC[AES256_GCM,data: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,iv:xbUR7YIZKQd6qlzsEZT4aur5xZuRBgQvmSSTdpc05ag=,tag:UIuoHsgMYWmhdJR/GDB0iA==,type:str]
|
||||
ssh-user-ed25519-key: ENC[AES256_GCM,data:p8HGPQrlLLj57Zlkbe/g4HzMRnEucrrl/Aeh/iG/3nSM8I73cCEC8lZXcQgU9BtBP3rrY5F0BvT8MKVlkgCVqJ63EVPj/xjZqmmyKQvf9VH7tx8IoqAA3K/eSt9F7HhUPR3f2NQtce4xeOf7qYHFvP1LNEtv0UgmgROhUfYPaHb7JZ3p4kZ8V8LhVttmIJi9k45P182TtZJkgt36Ys6gyBp7A6UOT7Ha/HdhB6fkqt3H5HWqSq786NXUhqXgzPSDFvH4Iy+aBczAehjYRVNPJ8LTnDovmr8fMzuTJ1t8WCEnljkDWen645ToCObfJTmCIphGfD/VkST0ZphvmV8MY4/z+Ch+CUxI2O2XgJYQVsD7qBtKKxlAS4P4U28JgLt01Y4tPK6Qs+VTXEsBqKxYS99AUVjNAfxE2d7c0IpQSNHqm8mw0d41OKiphIAxgJ14RtUQDxsytAlEl8giQKeJ3yFf8xVYKJ0NTvKyf2X0+d7k3ZiEP0FGbjBwA484kCUzfYuTX+bRoKLd7EcaJkdHSuqQprzUfYpAGgS/,iv:f8YPwuXpP94FMhCIgkLpR1PVufl3PPQVnNaEn56WXPE=,tag:8YnJjtOCEleEZdKQu0bBfA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -20,7 +21,7 @@ sops:
|
||||
cbQ5nwrYjI7FapucJQQFebB7FrLKCDgZRyw+/nhCmEJSWFz7iZKLQA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe
|
||||
lastmodified: "2026-07-20T03:45:30Z"
|
||||
mac: ENC[AES256_GCM,data:ThhDV+a054Z32fFkgkJ40I/Wr9EY9gMa/Wc8uToMaqGGdMT87xJH1QEJxdKHuzgWAXFLH9+4Rl/t2uRFC6AVyOGyBou/NDy0G3WzIfrJn1raTYwIvNm4I/0vycRvLyOi4ERPpDERqgoDczeE9i7U7Qves/KdLFPqOlGHDBI/QWE=,iv:oBNu3jeM7S41+mhKYzpYUzyynrJ8hQ7uA8Msd0UrIcA=,tag:HWV32TmxfKZlLcDWo4iTiA==,type:str]
|
||||
lastmodified: "2026-07-20T16:29:45Z"
|
||||
mac: ENC[AES256_GCM,data:XLrro73Z4hA4ntA2uFlBj+eNJJXlYw3YUFFLI/it9KvvEbxlvAVka++HQpby2rRZm+VjSXa7BDtzSnLA4Jpue2csUQUMVpKCGvWBNxTBnZpkKpi7fJd2AOFcMA+mXk19OS9YdnfZy9VLpr4+XiM8e+zxNEijZmHXn0gsGUotd8I=,iv:6FWSG86Ix764vD/BMlnEWDdQX74yIg0wkmz5Zz343VQ=,tag:tIeODybScPRVuLBpj7gzVw==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.13.2
|
||||
|
||||
@@ -93,7 +93,7 @@ in
|
||||
# satisfy the secret below.
|
||||
# Clearing both `sshKeyPaths` defaults keeps the SSH host keys out of the
|
||||
# decryption path.
|
||||
sops.defaultSopsFile = ../secrets/shared.yaml;
|
||||
sops.defaultSopsFile = ./secrets/shared.yaml;
|
||||
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||
sops.age.sshKeyPaths = [ ];
|
||||
sops.gnupg.sshKeyPaths = [ ];
|
||||
Reference in New Issue
Block a user